这是我参与8月更文挑战的第25天,活动详情查看:8月更文挑战
钢琴键盘
最近很喜欢挺‘JoJo黄金之风’中的那一段钢琴处刑曲,所以准备自己写一个钢琴的键盘,也算是自己重新练习一下canvas。
其实这里是承接上一篇文章《简单的前端项目配置》继续走下去的,不过其实不去看也没有关系,毕竟主要还是以写canvas为主。
<canvas> 看起来和 <img> 元素很相像,唯一的不同就是它并没有 src 和 alt 属性。实际上,<canvas> 标签只有两个属性 —— width和height。这些都是可选的,并且同样利用 DOM properties 来设置。当没有设置宽度和高度的时候,canvas会初始化宽度为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上点击的位置,来判断到底是点击了什么按键。
至于可能遇到的画布上按键颜色重置,就是直接重绘了键盘出来的。
最后的效果: