Canvas钢琴琴键弹起来
钢琴键盘
最近很喜欢挺‘JoJo黄金之风’中的那一段钢琴处刑曲,所以准备自己写一个钢琴的键盘,也算是自己重新练习一下canvas
。
音乐地址:https://y.qq.com/n/ryqq/player
其实这里是承接上一篇文章《简单的前端项目配置》继续走下去的,不过其实不去看也没有关系,毕竟主要还是以写canvas
为主。
<canvas>
看起来和 <img>
元素很相像,唯一的不同就是它并没有 src 和 alt 属性。实际上,<canvas>
标签只有两个属性 —— width
和height
。这些都是可选的,并且同样利用 DOM properties
来设置。当没有设置宽度和高度的时候,canva
s会初始化宽度为300像素和高度为150像素。
琴键分析
在开始动手制作之前,先要规划一下
首先我去看了一下钢琴的琴键。钢琴的琴键分为黑白两种,白键一共有52个,黑键一共有36个。 黑键比白键略短,但是基本和白键都是靠顶边对齐的。 白键是平均填满了整个键盘,黑键是有规律分布,虽然一般看起来没有规律
Html
在html
当中写入canvas
标签,可以提前设置好宽高,也可以在JavaScript中设置(我是写在TypeScript
中的,用TS来替代JS,所以和普通JavaScript写法略有不同)。
<canvas id="mycanvas" >
您的浏览器不支持 HTML5 canvas 标签, 建议换一个
</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这样的间隔排列。
所以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<string|number, keyObj> = 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中是没有声音的,挺可惜的,毕竟重点就本文在于钢琴能响了
键盘弹奏:
- 点赞
- 收藏
- 关注作者
评论(0)