canvas 制作钢琴琴键

852 阅读4分钟

这是我参与8月更文挑战的第25天,活动详情查看:8月更文挑战

钢琴键盘

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

音乐地址:y.qq.com/n/ryqq/play…

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

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

琴键分析

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

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

image.png



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,然后创建黑键和白键独立的类BlackKeysWhiteKeys

钢琴按键需要的属性有宽度、高度、类别和距离左边的距离 在钢琴按键中需要有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);
    } 
}

当前绘制出的效果:

image.png


目前的效果当中,我还添加鼠标点击白键事件,按下白键,白键变色,鼠标抬起,颜色变回白色。 这个效果中,我对canvas的鼠标点击事件是使用了addEventListener去绑定mousedown和mouseup两种方法,然后根据在canvas上点击的位置,来判断到底是点击了什么按键。

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

最后的效果:

223.gif