Canvas鋼琴琴鍵彈起來

      網(wǎng)友投稿 836 2025-04-02

      鋼琴鍵盤

      最近很喜歡挺‘JoJo黃金之風(fēng)’中的那一段鋼琴處刑曲,所以準(zhǔn)備自己寫一個鋼琴的鍵盤,也算是自己重新練習(xí)一下canvas。

      音樂地址:https://y.qq.com/n/ryqq/player

      其實(shí)這里是承接上一篇文章《簡單的前端項(xiàng)目配置》繼續(xù)走下去的,不過其實(shí)不去看也沒有關(guān)系,畢竟主要還是以寫canvas為主。

      ?看起來和??元素很相像,唯一的不同就是它并沒有 src 和 alt 屬性。實(shí)際上,?標(biāo)簽只有兩個屬性 —— ?width和height。這些都是可選的,并且同樣利用?DOM?properties?來設(shè)置。當(dāng)沒有設(shè)置寬度和高度的時候,canvas會初始化寬度為300像素和高度為150像素。

      琴鍵分析

      在開始動手制作之前,先要規(guī)劃一下

      首先我去看了一下鋼琴的琴鍵。鋼琴的琴鍵分為黑白兩種,白鍵一共有52個,黑鍵一共有36個。 黑鍵比白鍵略短,但是基本和白鍵都是靠頂邊對齊的。 白鍵是平均填滿了整個鍵盤,黑鍵是有規(guī)律分布,雖然一般看起來沒有規(guī)律

      Html

      在html當(dāng)中寫入canvas標(biāo)簽,可以提前設(shè)置好寬高,也可以在JavaScript中設(shè)置(我是寫在TypeScript中的,用TS來替代JS,所以和普通JavaScript寫法略有不同)。

      您的瀏覽器不支持 HTML5 canvas 標(biāo)簽, 建議換一個

      在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、每個黑鍵都位于兩個白鍵中間(但不一定是正中間)。

      Canvas鋼琴琴鍵彈起來

      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 = new 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)容。

      上一篇:Excel表格中多個區(qū)域數(shù)據(jù)重疊在一起的方法(為什么兩個excel表格重疊在一起)
      下一篇:如何把我的文檔轉(zhuǎn)移到d盤
      相關(guān)文章
      亚洲精品视频专区| ZZIJZZIJ亚洲日本少妇JIZJIZ | 亚洲乱亚洲乱淫久久| 中文字幕亚洲一区| 亚洲一区二区三区免费| 久久亚洲精品成人无码| 亚洲精品中文字幕| 亚洲成AV人片在WWW| 亚洲无码一区二区三区| 99久久国产亚洲综合精品| 久久精品国产亚洲AV天海翼| 亚洲AV香蕉一区区二区三区| 亚洲国产精品成人午夜在线观看 | 99ri精品国产亚洲| 精品亚洲成a人片在线观看| 亚洲欧洲日韩国产综合在线二区| 水蜜桃亚洲一二三四在线| 亚洲天堂一区二区| 亚洲性色高清完整版在线观看| 亚洲视频一区二区三区| 亚洲毛片在线免费观看| 亚洲国产精品无码久久久| 亚洲首页国产精品丝袜| 亚洲日本在线电影| 国产精品亚洲综合一区在线观看| 亚洲?V无码乱码国产精品| 久久久久亚洲AV无码专区桃色 | 亚洲精品伊人久久久久| 亚洲午夜福利在线视频| 春暖花开亚洲性无区一区二区| 亚洲AV无码乱码在线观看性色扶 | 亚洲欧洲日韩综合| 在线亚洲高清揄拍自拍一品区| 亚洲色大成网站www永久男同| 国产精品亚洲专区无码WEB| 亚洲情侣偷拍精品| 亚洲AV无码国产精品色午友在线| 久久精品国产精品亚洲毛片| 亚洲另类春色校园小说| 亚洲欧美成人综合久久久| 亚洲国产精品碰碰|