用Vue.js和Webpack開發(fā)Web在線鋼琴

緣起

由于童心未泯,之前在手機(jī)上玩過鋼琴模擬App证逻,但是手機(jī)屏幕太小乐埠,始終覺得不過癮。其實(shí)對于我這個連基本樂理都不懂的“樂盲”來說囚企,就算給我一臺真正的鋼琴丈咐,我也玩不轉(zhuǎn)。不過是圖個新鮮龙宏、權(quán)當(dāng)娛樂罷了棵逊。最近剛好入手一臺帶觸摸屏的Lenovo Yoga 4 Pro,這倒給了我新的想象空間:大屏幕玩起來是不是更帶感银酗?在Win10應(yīng)用商店里搜了下辆影,還真有各種模擬鋼琴的應(yīng)用,隨便選了一款安裝黍特。結(jié)果非常令人失望蛙讥,音效慘不忍聽,還各種閃退灭衷。這里順便吐槽下win10的應(yīng)用商店次慢,里面的很多應(yīng)用不是經(jīng)常安裝失敗,就是經(jīng)常閃退今布,簡直沒法用啊经备。作為一名前端開發(fā)和堅(jiān)定的Web支持者,客戶端不好用果斷轉(zhuǎn)向Web啊部默。本著盡量不重造輪子的原則侵蒙,先在網(wǎng)上搜了一下。百度的搜索結(jié)果幾乎都是那一個例子傅蹂,也不知道是哪位哥們寫的纷闺,被到處引用。就那么幾個鍵份蝴,怎么玩犁功?Google的結(jié)果也不盡如人意,不是打不開就是加載半天婚夫。算了浸卦,還是自己動手吧。

準(zhǔn)備

我們知道案糙,HTML5有音頻接口限嫌,播放聲音自然不在話下靴庆。這模擬鋼琴自然需要各種音階的音頻文件吧,于是在網(wǎng)上搜了一通怒医,找齊了88鍵鋼琴的音頻文件炉抒。為什么鋼琴有88個鍵?別問我稚叹,我是樂盲焰薄。看看這張鋼琴示意圖就知道了:


開工

最近一直在用Vue.js開發(fā)項(xiàng)目扒袖,配合Webpack神器構(gòu)建打包塞茅,開發(fā)前端項(xiàng)目從來沒有如此方便。在此要特別感謝Vue.js的作者Evan You尤雨溪(知乎)僚稿, 給我們貢獻(xiàn)了這么好用的框架凡桥。
新建一個Vue.js項(xiàng)目非常簡單蟀伸,可以用官方推薦的腳手架命令行工具vue-cli創(chuàng)建新工程蚀同。首先安裝這個工具:

npm install -g vue-cli

安裝好后執(zhí)行命令生成工程模板:

vue init webpack piano

這里我們用webpack作為構(gòu)建工具,你也可以使用browserify啊掏。
就這么簡單蠢络,一個Vue.js project誕生了,而且Webpack已經(jīng)配置好迟蜜。接下來執(zhí)行命令安裝相關(guān)的node模塊:

npm install

如果一切順利的話刹孔,項(xiàng)目就可以跑起來了:

npm run dev

訪問http://localhost:8080就可以看到默認(rèn)的歡迎界面。至此娜睛,項(xiàng)目的搭建算是完成了髓霞。

界面

現(xiàn)在開始寫界面。雖然是樂盲畦戒,鋼琴鍵盤上有哪些鍵還是要搞清楚的方库。對于標(biāo)準(zhǔn)的88鍵鋼琴,總共有88個鍵障斋,其中52個白色鍵纵潦,36個黑色鍵。分為低音區(qū)垃环、中音區(qū)和高音區(qū)邀层,每個區(qū)有三組。對于我們畫界面來說遂庄,重要的是找出其中的規(guī)律寥院。最兩端的兩組先不管,其他的分組看上去都是一樣的:三白夾兩黑跟著四白夾三黑涛目。


怎么實(shí)現(xiàn)這個界面布局呢秸谢?很簡單经磅,黑白鍵都用button元素表示,設(shè)置好寬高钮追、背景色和邊框预厌。白色的自然定位并排鋪開,黑色的用絕對定位元媚,計(jì)算出對應(yīng)的坐標(biāo)轧叽。這里有個小細(xì)節(jié),就是黑白鍵的DOM元素排列最好跟各音階的先后順序?qū)?yīng)刊棕,這樣在計(jì)算黑鍵坐標(biāo)就比較方便炭晒。
既然有七個組的界面是一模一樣的,我們就把一組設(shè)計(jì)成一個組件好了甥角。用Vue.js開發(fā)組件真的是太方便了网严,一個.vue文件包含HTML template、script和style嗤无,就構(gòu)成了一個獨(dú)立的組件震束。每組的音階范圍不一樣,通過組件的props設(shè)定当犯。來看組件的源碼文件Group.vue

<template>
    <div class="group">
        <button :class="{'white': whites.indexOf(n) > -1, 'black': blacks.indexOf(n) > -1}" v-for="n in 12" :style="{ left: calcLeft(n) + '%' }" data-note="{{start+n}}" @click="play(start+n)"><span v-show="n === 0">C</span></button>
    </div>
</template>

<script>
import {notes} from '../notes.js';
const prefix = 'data:audio/mpeg;base64,';
const base = 3;
const keys = 12;
export default {
    props: {
        group: {
            type: Number,
            default: 0
        }
    },
    data() {
        return {
            // note: changing this line won't causes changes
            // with hot-reload because the reloaded component
            // preserves its current state and we are modifying
            // its initial state.
            blacks: [1, 3, 6, 8, 10],
            whites: [0, 2, 4, 5, 7, 9, 11]
        }
    },
    computed: {
        start() {
            return this.group * keys;
        }
    },
    methods: {
        play(index) {
            var audio = new Audio(prefix + notes[index + base]);
            audio.play();
        },
        calcLeft(index) {
            var unit = 14.29;
            var i = this.blacks.indexOf(index);
            if(i < 2) {
                return unit * (0.75 + i);
            }
            return unit * (1.75 + i);
        },
        click(index) {

        }
    }
}
</script>

<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
    .group {
        font-size: 0;
        position: relative;
        display: flex;
        flex-grow: 1;
    }
    button {
        width: 14.29%;
        flex: 1;
        height: 300px;
        display: inline-block;
        border: 1px solid #ccc;
        outline: 0;
        padding: 0;
        box-sizing: border-box;
    }
    button > span {
        position: absolute;
        bottom: 10px;
    }
    .white:active,
    .white.active {
        background: #ececec;
    }
    .white {
        background: #fff;
    }
    .black {
        background: #000;
        border-color: #000;
        height: 150px;
        width: 7.15%;
        position: absolute;
    }
</style>

邏輯并不復(fù)雜垢村,關(guān)鍵是處理細(xì)節(jié)。按鍵的寬度是用百分比的嚎卫,高度固定嘉栓。黑鍵的坐標(biāo)計(jì)算邏輯在方法calcLeft里,具體看代碼好了拓诸,code will talk.
你可能有個疑問:音頻內(nèi)容哪來的侵佃?繼續(xù)看。

音頻處理

前面提到過奠支,我從網(wǎng)上找到了鋼琴的88音階的音頻文件馋辈,都是mp3格式的。但是我不想讓88個音分散在88個.mp3文件里胚宦,不然在彈奏的時候一個個文件下載首有,可不太好。怎么辦呢枢劝?我們知道圖片可以轉(zhuǎn)成base64的字符串顯示在DOM里井联。其實(shí)音頻文件也一樣,用data:audio/mpeg;base64,XXXXXX就可以了您旁。寫了個Node程序烙常,一次性將所有Mp3文件都轉(zhuǎn)成了base64字符串?dāng)?shù)組備用:

var fs = require('fs');
var file = 'notes.json';

// function to encode file data to base64 encoded string
function base64_encode(file) {
    // read binary data
    var bitmap = fs.readFileSync(file);
    // convert binary data to base64 encoded string
    return new Buffer(bitmap).toString('base64');
}

fs.readdir('.', function(error, files) {
    var content = "";
    files.forEach((f, index) => {
        if(/^\d/.test(f)) {
            var data = base64_encode(f);
            content += `"${data}",\n`;
        }
    });
    fs.writeFileSync(file, content);
});

數(shù)組內(nèi)容放在一個單獨(dú)的文件里,作為模塊引入。數(shù)組元素的順序就是音階從低到高的順序蚕脏。HTML5的Audio對象侦副,支持從構(gòu)造函數(shù)傳入base64數(shù)據(jù),然后調(diào)用play()就可以播放聲音了驼鞭。
沒有觸摸屏咋玩秦驯?還有鍵盤啊。簡單起見挣棕,用三排字母按鍵對應(yīng)中音區(qū)的三個組译隘。監(jiān)聽鍵盤keydown事件,通過keyCode區(qū)分不同的鍵洛心,播放對應(yīng)的音頻內(nèi)容就好了固耘。

總結(jié)

這個過程并不復(fù)雜,就是布局和音頻處理需要處理一些細(xì)節(jié)词身。代碼寫得很倉促厅目,有些地方可以重構(gòu)下。完整的源碼可以在我的Github找到法严。喜歡的歡迎star损敷,有閑工夫也可自己改進(jìn)。最終效果點(diǎn)擊這里:http://kaysonli.github.io/piano/dist/

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末渐夸,一起剝皮案震驚了整個濱河市嗤锉,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌墓塌,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,185評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件奥额,死亡現(xiàn)場離奇詭異苫幢,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)垫挨,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,652評論 3 393
  • 文/潘曉璐 我一進(jìn)店門韩肝,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人九榔,你說我怎么就攤上這事哀峻。” “怎么了哲泊?”我有些...
    開封第一講書人閱讀 163,524評論 0 353
  • 文/不壞的土叔 我叫張陵剩蟀,是天一觀的道長。 經(jīng)常有香客問我切威,道長育特,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,339評論 1 293
  • 正文 為了忘掉前任先朦,我火速辦了婚禮缰冤,結(jié)果婚禮上犬缨,老公的妹妹穿的比我還像新娘。我一直安慰自己棉浸,他們只是感情好怀薛,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,387評論 6 391
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著迷郑,像睡著了一般乾戏。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上三热,一...
    開封第一講書人閱讀 51,287評論 1 301
  • 那天鼓择,我揣著相機(jī)與錄音,去河邊找鬼就漾。 笑死呐能,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的抑堡。 我是一名探鬼主播摆出,決...
    沈念sama閱讀 40,130評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼首妖!你這毒婦竟也來了偎漫?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,985評論 0 275
  • 序言:老撾萬榮一對情侶失蹤有缆,失蹤者是張志新(化名)和其女友劉穎象踊,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體棚壁,經(jīng)...
    沈念sama閱讀 45,420評論 1 313
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡杯矩,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,617評論 3 334
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了袖外。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片史隆。...
    茶點(diǎn)故事閱讀 39,779評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖曼验,靈堂內(nèi)的尸體忽然破棺而出泌射,到底是詐尸還是另有隱情,我是刑警寧澤鬓照,帶...
    沈念sama閱讀 35,477評論 5 345
  • 正文 年R本政府宣布熔酷,位于F島的核電站,受9級特大地震影響颖杏,放射性物質(zhì)發(fā)生泄漏纯陨。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,088評論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望翼抠。 院中可真熱鬧咙轩,春花似錦、人聲如沸阴颖。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,716評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽量愧。三九已至钾菊,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間偎肃,已是汗流浹背煞烫。 一陣腳步聲響...
    開封第一講書人閱讀 32,857評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留累颂,地道東北人滞详。 一個月前我還...
    沈念sama閱讀 47,876評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像紊馏,于是被迫代替她去往敵國和親料饥。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,700評論 2 354

推薦閱讀更多精彩內(nèi)容