Canvas钢琴琴键弹起来

网友投稿 657 2022-05-28

钢琴键盘

最近很喜欢挺‘JoJo黄金之风’中的那一段钢琴处刑曲,所以准备自己写一个钢琴的键盘,也算是自己重新练习一下canvas。

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

其实这里是承接上一篇文章《简单的前端项目配置》继续走下去的,不过其实不去看也没有关系,毕竟主要还是以写canvas为主。

 看起来和  元素很相像,唯一的不同就是它并没有 src 和 alt 属性。实际上, 标签只有两个属性 ——  width和height。这些都是可选的,并且同样利用 DOM properties 来设置。当没有设置宽度和高度的时候,canvas会初始化宽度为300像素和高度为150像素。

琴键分析

在开始动手制作之前,先要规划一下

首先我去看了一下钢琴的琴键。钢琴的琴键分为黑白两种,白键一共有52个,黑键一共有36个。 黑键比白键略短,但是基本和白键都是靠顶边对齐的。 白键是平均填满了整个键盘,黑键是有规律分布,虽然一般看起来没有规律

Html

在html当中写入canvas标签,可以提前设置好宽高,也可以在JavaScript中设置(我是写在TypeScript中的,用TS来替代JS,所以和普通JavaScript写法略有不同)。

您的浏览器不支持 HTML5 canvas 标签, 建议换一个

在TS文件中编写:使用document.body的宽度来为canvas设置宽度以求做到自适应,这里需要注意的是,很多人都习惯自适应写成100%,但是在canvas当中,这种做法是错误的,这回导致之后绘制的图形产生形变。 canvas的大小也不能设置在style当中。

const canvas:any = document.getElementById('mycanvas'); const ctx:any = canvas.getContext('2d'); let width:number = document.body.clientWidth - 20; // canvas不能设置成百分比,不能在style里设置宽高 canvas.width = width; canvas.height = 500;

TS

之前已经将canvas基本大小设定好了,接下来可以计算一下黑键和白键的大小了

我是以高度是宽度的5.5倍来设置黑白键的,所以只需要计算出宽度即可。

下面bw是黑键的宽度,ww是白键的宽度

let bw = width * 0.015, ww = width / 52;

不管黑键还是白键,都可以设置一个同一的公共类KeysSize,然后创建黑键和白键独立的类BlackKeys和WhiteKeys

钢琴按键需要的属性有宽度、高度、类别和距离左边的距离

在钢琴按键中需要有pressDown琴键按下方法以及draw绘制琴键方法。

这里使用了canvas中的绘制矩形fillRect方法,这里绘制出来的填充矩形是黑色的,那就当作正常的黑键即可。 黑键直接使用extends继承KeysSize类即可。

不过白键就要对其中的一些方法进行重写,并且添加一些其他的方法

// 钢琴按键 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); } }

在白键当中,不能使用fillRect方法去绘制一个填充的矩形,所以我使用stroke绘制了一个有圆角的白键。 这里面使用了lineTo和arcTo这些绘制线的方法。绘制的白键边框线颜色可以使用strokeStyle进行更改

draw(left: number, backgroundColor: string = '#333') { this.left = left; if (typeof this.width == 'number'){ ctx.lineWidth = 0.5; // 边框粗细 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(); };

之后可以生成黑键和白键的实例来填充在canvas界面上了。

白键的生成比较简单:直接排列生成52个即可

for (let i = 0; i < 52; i++) { let wkey = new WhiteKeys(ww, 'white'); wkey.draw(i*ww); }

而黑键的生成就需要寻找到规律,黑键除了第一个之外,后面的都是2、3、2、3、2、3这样的间隔排列。

Canvas钢琴琴键弹起来

所以36个黑键是如下绘制的,这里使用的ww是白键的宽度,bw是黑键的宽度。这一点在上面也提到过。

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); } }

当前绘制出的效果:

目前的效果当中,我还添加鼠标点击白键事件,按下白键,白键变色,鼠标抬起,颜色变回白色。

这个效果中,我对canvas的鼠标点击事件是使用了addEventListener去绑定mousedown和mouseup两种方法,然后根据在canvas上点击的位置,来判断到底是点击了什么按键。

至于可能遇到的画布上按键颜色重置,就是直接重绘了键盘出来的。

当前的效果:

再起

不过光是点击白键还不够,钢琴也是需要点击黑键的,并且最重要的是钢琴怎么能够没有声音呢!

项目Gitee的地址:https://gitee.com/wzckongchengji/node_study/tree/master/pianoDemo

因为之前已经写过白键的点击方法,所以本次就用只需要稍作改变,即可完成黑键的点击。

首先对鼠标点击位置的(x, y)进行判断。大家如果想看可以去上面Gitee上main.ts中查看

// 判断点击的位置是否在按键上 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) } }

因为是在TypeScript中编写的,所以会有类型定义,xy是之前定义的坐标类型

type xy = { x: number, y: number }

通过这一步,能够做到不同类别的按键判断。

其中的whiteKeyClick是白键按下的方法,白键和黑键按下在类中都有pressDown按下事件,各自会重写一下此方法

当前的效果是鼠标点击黑键和白键都会变色

钢琴音效

钢琴如果没有琴键的声音又怎么能称为钢琴呢,所以每一个按键都要有对应的声音,一共88个琴键,就要有88中音效。

说实话,找这些音效资源花费了我很久的时间…

最后经过一系列的寻找,还是被我找到了

地址就在上面的项目中, https://gitee.com/wzckongchengji/node_study/tree/master/nodeDemoIO/music

注意: 我使用audio标签去播放音乐,由于音频的资源比较大,所以我就不存放在前端项目中了,把这些音频资源放在我平时练习的node里,使用node来给出url去调用音频。

node运行命令: nodemon IO.js

这样测试一下,在网页中输入URL: http://localhost:3001/music/8A.mp3 。 发现能够访问到音频了

之后就可以在钢琴按下事件中调用音频调用方法了,注意这里需要根据不同的index去判断调用不同琴键的音效。

下面是白键的音调判断方法:

黑键判断:

当然了,这些判断需要对钢琴有一定了解,钢琴中是存在不同分区的。下图很重要,这也是我在编写时的重要依据:

可以看出,钢琴琴键排布具有如下特点。

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)。

图中的那些汉字是每组的名称(从左向右依次为大字二组、大字一组、大字组、小字组、小字一组、小字二组、小字三组、小字四组、小字五组,其中大字二组和小字五组是不完全音组)

经过这一系列的操作之后,点击琴键就能发出不同的音效声音了。

键盘绑定

做到这里,突然觉得还是有些美中不足。 光是使用鼠标点击好像有些过于单调了,那么让打键盘变成弹钢琴吧

将键盘上的按键对应钢琴的琴键匹配绑定起来。

这里的内容我主要写在keyWord.ts当中了,使用export抛出

使用Map定义的keyWord来存放对应的联系

interface keyObj { keyname: string; type: number; // 0:白键 1:黑键 index: number; // 对应的黑键和白键index represent: string } let keyWord:Map = new Map();

keyWord中的内容:

通过回调函数来设置电脑键盘的按下与抬起事件对应的操作,这里设置的依据是根据键盘按下的键值,每个按键按下都会有不同的键值:

// 监听键盘点击 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对应的回调方法写入即可。

// 匹配按下的电脑键盘和对应的琴键 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); } } // 监听电脑键盘按下 keyWordDown(matchKey, ()=>{ ctx.clearRect(0,0, width, 500); //清理画布 drawAll(); // 重绘键盘 });

完成

当然下面的GIF中是没有声音的,挺可惜的,毕竟重点就本文在于钢琴能响了

键盘弹奏:

Canvas TypeScript web前端

版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:10.6 Linux mount命令详解:挂载Linux系统外的文件
下一篇:华为云华北-北京一可用区3的普通IO云硬盘于2019年10月31日 20:00(北京时间)停售通知
相关文章