Canvas鋼琴琴鍵彈起來
鋼琴鍵盤
最近很喜歡挺‘JoJo黃金之風(fēng)’中的那一段鋼琴處刑曲,所以準(zhǔn)備自己寫一個鋼琴的鍵盤,也算是自己重新練習(xí)一下canvas。
音樂地址:https://y.qq.com/n/ryqq/player
其實(shí)這里是承接上一篇文章《簡單的前端項(xiàng)目配置》繼續(xù)走下去的,不過其實(shí)不去看也沒有關(guān)系,畢竟主要還是以寫canvas為主。
琴鍵分析
在開始動手制作之前,先要規(guī)劃一下
首先我去看了一下鋼琴的琴鍵。鋼琴的琴鍵分為黑白兩種,白鍵一共有52個,黑鍵一共有36個。 黑鍵比白鍵略短,但是基本和白鍵都是靠頂邊對齊的。 白鍵是平均填滿了整個鍵盤,黑鍵是有規(guī)律分布,雖然一般看起來沒有規(guī)律
Html
在html當(dāng)中寫入canvas標(biāo)簽,可以提前設(shè)置好寬高,也可以在JavaScript中設(shè)置(我是寫在TypeScript中的,用TS來替代JS,所以和普通JavaScript寫法略有不同)。
在TS文件中編寫:使用document.body的寬度來為canvas設(shè)置寬度以求做到自適應(yīng),這里需要注意的是,很多人都習(xí)慣自適應(yīng)寫成100%,但是在canvas當(dāng)中,這種做法是錯誤的,這回導(dǎo)致之后繪制的圖形產(chǎn)生形變。 canvas的大小也不能設(shè)置在style當(dāng)中。
const canvas:any = document.getElementById('mycanvas'); const ctx:any = canvas.getContext('2d'); let width:number = document.body.clientWidth - 20; // canvas不能設(shè)置成百分比,不能在style里設(shè)置寬高 canvas.width = width; canvas.height = 500;
TS
之前已經(jīng)將canvas基本大小設(shè)定好了,接下來可以計算一下黑鍵和白鍵的大小了
我是以高度是寬度的5.5倍來設(shè)置黑白鍵的,所以只需要計算出寬度即可。
下面bw是黑鍵的寬度,ww是白鍵的寬度
let bw = width * 0.015, ww = width / 52;
不管黑鍵還是白鍵,都可以設(shè)置一個同一的公共類KeysSize,然后創(chuàng)建黑鍵和白鍵獨(dú)立的類BlackKeys和WhiteKeys
鋼琴按鍵需要的屬性有寬度、高度、類別和距離左邊的距離
在鋼琴按鍵中需要有pressDown琴鍵按下方法以及draw繪制琴鍵方法。
這里使用了canvas中的繪制矩形fillRect方法,這里繪制出來的填充矩形是黑色的,那就當(dāng)作正常的黑鍵即可。 黑鍵直接使用extends繼承KeysSize類即可。
不過白鍵就要對其中的一些方法進(jìn)行重寫,并且添加一些其他的方法
// 鋼琴按鍵 class KeysSize { width: number; //寬 height: number; // 高 type: string|undefined; // 按鍵類別 left: number = 0; constructor(width:number, type:string) { this.width = width; this.height = width * 5.5; this.type = type; }; // 按下事件 pressDown(){ }; draw(left: number) { this.left = left; ctx.fillRect(left, 0, this.width, this.height); } }
在白鍵當(dāng)中,不能使用fillRect方法去繪制一個填充的矩形,所以我使用stroke繪制了一個有圓角的白鍵。 這里面使用了lineTo和arcTo這些繪制線的方法。繪制的白鍵邊框線顏色可以使用strokeStyle進(jìn)行更改
draw(left: number, backgroundColor: string = '#333') { this.left = left; if (typeof this.width == 'number'){ ctx.lineWidth = 0.5; // 邊框粗細(xì) ctx.strokeStyle = backgroundColor; // 修改邊線顏色 this.rectdraw(left); // 繪制白鍵 } }; rectdraw(start:number) { ctx.beginPath(); ctx.moveTo(start, 0); ctx.lineTo(start, this.height - 8); ctx.arcTo(start, this.height, start + this.width, this.height, 8); // 繪制圓角 ctx.lineTo(start + this.width - 16, this.height); ctx.arcTo(start + this.width, this.height, start + this.width, 0, 8); ctx.lineTo(start + this.width, 0); ctx.stroke(); };
之后可以生成黑鍵和白鍵的實(shí)例來填充在canvas界面上了。
白鍵的生成比較簡單:直接排列生成52個即可
for (let i = 0; i < 52; i++) { let wkey = new WhiteKeys(ww, 'white'); wkey.draw(i*ww); }
而黑鍵的生成就需要尋找到規(guī)律,黑鍵除了第一個之外,后面的都是2、3、2、3、2、3這樣的間隔排列。
所以36個黑鍵是如下繪制的,這里使用的ww是白鍵的寬度,bw是黑鍵的寬度。這一點(diǎn)在上面也提到過。
for (let i:number = 1; i <= 36; i++) { let bkey = new BlackKeys(bw, 'black'); let nindex:number = Math.floor(i/5); if (i == 1) { bkey.draw(ww - bw/2); } else if (i%5 == 2) { bkey.draw(ww*(2 + nindex*7) + ww - bw/2); } else if (i%5 == 3) { bkey.draw(ww*(3 + nindex*7) + ww - bw/2); } else if (i%5 == 4) { bkey.draw(ww*(5 + nindex*7) + ww - bw/2); } else if (i%5 == 0) { bkey.draw(ww*(6 + nindex*7) + ww - bw/2); } else if (i%5 == 1) { bkey.draw(ww*(7 + (nindex - 1)*7) + ww - bw/2); } }
當(dāng)前繪制出的效果:
目前的效果當(dāng)中,我還添加鼠標(biāo)點(diǎn)擊白鍵事件,按下白鍵,白鍵變色,鼠標(biāo)抬起,顏色變回白色。
這個效果中,我對canvas的鼠標(biāo)點(diǎn)擊事件是使用了addEventListener去綁定mousedown和mouseup兩種方法,然后根據(jù)在canvas上點(diǎn)擊的位置,來判斷到底是點(diǎn)擊了什么按鍵。
至于可能遇到的畫布上按鍵顏色重置,就是直接重繪了鍵盤出來的。
當(dāng)前的效果:
再起
不過光是點(diǎn)擊白鍵還不夠,鋼琴也是需要點(diǎn)擊黑鍵的,并且最重要的是鋼琴怎么能夠沒有聲音呢!
項(xiàng)目Gitee的地址:https://gitee.com/wzckongchengji/node_study/tree/master/pianoDemo
因?yàn)橹耙呀?jīng)寫過白鍵的點(diǎn)擊方法,所以本次就用只需要稍作改變,即可完成黑鍵的點(diǎn)擊。
首先對鼠標(biāo)點(diǎn)擊位置的(x, y)進(jìn)行判斷。大家如果想看可以去上面Gitee上main.ts中查看
// 判斷點(diǎn)擊的位置是否在按鍵上 function isTrueKey(xy: xy) { // 如果在鍵盤外 if (xy.y > ww * 5.5) { return false; } // 判斷是否在黑鍵上 if (xy.y <= bw * 5.5) { for (let index in bArr ) { let item = bArr[index]; if(item.left <= xy.x && item.left + bw > xy.x) { item.pressDown(parseInt(index)); break; } if (index == '35') { whiteKeyClick(xy); } } } else { whiteKeyClick(xy) } }
因?yàn)槭窃赥ypeScript中編寫的,所以會有類型定義,xy是之前定義的坐標(biāo)類型
type xy = { x: number, y: number }
通過這一步,能夠做到不同類別的按鍵判斷。
其中的whiteKeyClick是白鍵按下的方法,白鍵和黑鍵按下在類中都有pressDown按下事件,各自會重寫一下此方法
當(dāng)前的效果是鼠標(biāo)點(diǎn)擊黑鍵和白鍵都會變色
鋼琴音效
鋼琴如果沒有琴鍵的聲音又怎么能稱為鋼琴呢,所以每一個按鍵都要有對應(yīng)的聲音,一共88個琴鍵,就要有88中音效。
說實(shí)話,找這些音效資源花費(fèi)了我很久的時間…
最后經(jīng)過一系列的尋找,還是被我找到了
地址就在上面的項(xiàng)目中, https://gitee.com/wzckongchengji/node_study/tree/master/nodeDemoIO/music
注意: 我使用audio標(biāo)簽去播放音樂,由于音頻的資源比較大,所以我就不存放在前端項(xiàng)目中了,把這些音頻資源放在我平時練習(xí)的node里,使用node來給出url去調(diào)用音頻。
node運(yùn)行命令: nodemon IO.js
這樣測試一下,在網(wǎng)頁中輸入URL: http://localhost:3001/music/8A.mp3 。 發(fā)現(xiàn)能夠訪問到音頻了
之后就可以在鋼琴按下事件中調(diào)用音頻調(diào)用方法了,注意這里需要根據(jù)不同的index去判斷調(diào)用不同琴鍵的音效。
下面是白鍵的音調(diào)判斷方法:
黑鍵判斷:
當(dāng)然了,這些判斷需要對鋼琴有一定了解,鋼琴中是存在不同分區(qū)的。下圖很重要,這也是我在編寫時的重要依據(jù):
可以看出,鋼琴琴鍵排布具有如下特點(diǎn)。
1、共有52個白鍵和36個黑鍵。
2、黑鍵的長度和寬度均小于白鍵。
3、每個黑鍵都位于兩個白鍵中間(但不一定是正中間)。
4、琴鍵分為若干組,每組有12個琴鍵(7個白鍵和5個黑鍵)。
5、最左邊的組只有3個琴鍵(2個白鍵和1個黑鍵),最后邊的組只有1個琴鍵(1個白鍵),這兩個組都是不完整的組。
每組的這12個琴鍵中,7個白鍵從左向右依次為do、re、mi、fa、sol、la、si,5個黑鍵從左向右依次為升do(降re)、升re(降mi)、升fa(降sol)、升sol(降la)、升la(降si)。
圖中的那些漢字是每組的名稱(從左向右依次為大字二組、大字一組、大字組、小字組、小字一組、小字二組、小字三組、小字四組、小字五組,其中大字二組和小字五組是不完全音組)
經(jīng)過這一系列的操作之后,點(diǎn)擊琴鍵就能發(fā)出不同的音效聲音了。
鍵盤綁定
做到這里,突然覺得還是有些美中不足。 光是使用鼠標(biāo)點(diǎn)擊好像有些過于單調(diào)了,那么讓打鍵盤變成彈鋼琴吧
將鍵盤上的按鍵對應(yīng)鋼琴的琴鍵匹配綁定起來。
這里的內(nèi)容我主要寫在keyWord.ts當(dāng)中了,使用export拋出
使用Map定義的keyWord來存放對應(yīng)的聯(lián)系
interface keyObj { keyname: string; type: number; // 0:白鍵 1:黑鍵 index: number; // 對應(yīng)的黑鍵和白鍵index represent: string } let keyWord:Map
keyWord中的內(nèi)容:
通過回調(diào)函數(shù)來設(shè)置電腦鍵盤的按下與抬起事件對應(yīng)的操作,這里設(shè)置的依據(jù)是根據(jù)鍵盤按下的鍵值,每個按鍵按下都會有不同的鍵值:
// 監(jiān)聽鍵盤點(diǎn)擊 function keyWordDown(callback:Function, callback2:Function) { document.onkeydown = (event)=>{ let e = event || window.event; callback(e.keyCode) } document.onkeyup = ()=>{ callback2(); } } export { keyWord, keyWordDown, keyObj }
之后回到main.ts對應(yīng)的回調(diào)方法寫入即可。
// 匹配按下的電腦鍵盤和對應(yīng)的琴鍵 function matchKey(keyCode: number) { let res:keyObj = keyWord.get(keyCode) as keyObj; if (!res) return ; if (res?.type == 0) { wArr[res.index].pressDown(res.index); } else { bArr[res.index].pressDown(res.index); } } // 監(jiān)聽電腦鍵盤按下 keyWordDown(matchKey, ()=>{ ctx.clearRect(0,0, width, 500); //清理畫布 drawAll(); // 重繪鍵盤 });
完成
當(dāng)然下面的GIF中是沒有聲音的,挺可惜的,畢竟重點(diǎn)就本文在于鋼琴能響了
鍵盤彈奏:
Canvas TypeScript web前端
版權(quán)聲明:本文內(nèi)容由網(wǎng)絡(luò)用戶投稿,版權(quán)歸原作者所有,本站不擁有其著作權(quán),亦不承擔(dān)相應(yīng)法律責(zé)任。如果您發(fā)現(xiàn)本站中有涉嫌抄襲或描述失實(shí)的內(nèi)容,請聯(lián)系我們jiasou666@gmail.com 處理,核實(shí)后本網(wǎng)站將在24小時內(nèi)刪除侵權(quán)內(nèi)容。
版權(quán)聲明:本文內(nèi)容由網(wǎng)絡(luò)用戶投稿,版權(quán)歸原作者所有,本站不擁有其著作權(quán),亦不承擔(dān)相應(yīng)法律責(zé)任。如果您發(fā)現(xiàn)本站中有涉嫌抄襲或描述失實(shí)的內(nèi)容,請聯(lián)系我們jiasou666@gmail.com 處理,核實(shí)后本網(wǎng)站將在24小時內(nèi)刪除侵權(quán)內(nèi)容。