本文为我在学习高程四canvas2D绘图上下文的过程中,经实践得出的高程四未提及或描述不清内容的总结,也发现了部分技术错误。如有错漏,欢迎斧正,本文配合高程四食用效果更佳。(为了便于观察,背景设定为一格宽高50px的黑白格,所有图片均为我用canvas画出并截图,经过实测,可放心食用。)
基本的画布功能+requestAnimationFrame
0.使用requestAnimationFrame(简称rAF)制作动画,性能更好。因为屏幕60帧每秒,而rAF的调用会跟屏幕刷新保持一致,而setTimeout或者setInterval都是宏任务,需要在任务队列进行排队,如果有任务执行时间过长,动画的播放会卡顿。另外,如果在播放动画的同时使用toDataURL并赋值给img.src的话,img的画面会同步,如果修改img.width并将img进行绝对定位放在角落,就可以做出略缩图的效果。
1. 使用width和height属性确定canvas的宽高而非使用style,两者区别如下。
图1.canvas宽高的设置
左侧<canvas width="500px" height="500px" id="k-leftImage">你的浏览器不支持canvas</canvas>
右侧<canvas style="width:500px;height:500px;" id="k-rightImage">你的浏览器不支持canvas</canvas>
绘制矩形
2. 线的宽度context.lineWidth是从线中间向两边扩展的,而非类似border的向外扩展。context.lineCap属性控制线条断点的形状,context.lineJoin属性值控制线条交点的形状,三个context属性取值效果如下。
图2.lineWidth的设置
图3.lineCap的设置
图4.lineJoin的设置
绘制路径
3. 使用context.arc()画的弧的角度从3点钟方向开始计算,角度用Math.PI来表示,比如90°为Math.PI/2,以同样的起始角度(0)和结束角度(Math.PI/2)绘图,最后一个参数分别为true(逆),false(顺)结果如下。
图5.arc
代码如下:
context.strokeStyle = 'red';
context.lineWidth = 6;
context.beginPath();
context.arc(100, 100, 50, 0, Math.PI / 2, true);
context.moveTo(350, 100);
context.arc(300, 100, 50, 0, Math.PI / 2, false);
context.stroke();
4. 高程四556页原文“context.arcTo(x1, y1, x2, y2, radius):以给定半径radius,经由(x1, y1)绘制一条从上一点到(x2, y2)的弧线。”这个说法是错的,下图的弧线是我用如下代码生成的:
context.beginPath();
context.moveTo(150, 50);
context.arcTo(150, 150, 50, 150, 100);
context.stroke();
图6.arcTo的简单使用
可见弧线并不经过(150, 150),那经不经过(50, 150)呢?不一定,此时我把控制点(x1, y1)改成(200, 200),产生路径如下图,说明这个路径必定经过的,只有上一点(150, 50)而已,至于控制点(x1, y1)的作用,就是连接上一点和(x2, y2),形成两条切线,然后从上一点出发,生成半径为radius的与两线相切的圆的圆弧。所以这3个点其实都是控制点。
图7.arcTo用歪了
5. 贝塞尔曲线只是柔化的折线罢了,初见贝塞尔曲线,总有些不明觉厉,不知道上一点和另外3个控制点究竟怎么画线,但看了如下几张图,你应该就懂了
图8.贝塞尔曲线的花式用法
代码如下:
context.beginPath();
context.moveTo(sx, sy);
context.bezierCurveTo(x1, y1, x2, y2, ex, ey);
context.stroke();
可见曲线的走势与四个控制点形成的折线走势相近,只是更加柔和。
6. context.rect(x, y, width, height):从(x, y)开始画宽width高height的方形,终点也在(x,y),效果等同使用moveTo()和lineTo()画了4条直线。
7. context.fill()的效果为,连接路径的起点和终点,并填充被包围的区域,将上面的贝塞尔曲线的context.stroke()改成context.fill()的效果如下:
图9.被填充的贝塞尔曲线
8. context.closePath()用于画一条连接起点和终点的线,而不是在画完路径后调用表示路径结束。效果如下:
图10.封闭的贝塞尔曲线
9. context.clip()效果是,连接路径的起点和终点,并将被包围的区域作为允许绘画的区域,在使用了clip()使得只有部分区域可以画图之后画贝塞尔曲线效果如下:
图11.没画完的贝塞尔曲线
绘制文本
10. font属性与CSS的font属性相同,必须有大小(font-size)和字体族(font-family),其他随意。textAlign默认值为start,表示x值作为文字的起始。textBaseline默认值为”alphabetic”,就是写字母的四线本上自上向下数的第三条线,表示y值作为文字的alphabetic线。固定fillText的x值,改变textAlign的值效果如下:
图12.textAlign不同取值的效果
固定fillText的y值,修改textBaseline的值,效果如下:
图13.textBaseline不同取值的效果
变换
11. 使用canvas时可以使用context.save()和context.restore()放在一个函数的头尾,可以使得在使用函数时,对全局设置的修改(如fillStyle,font,rotate()等)不会影响到外界,相当于形成了作用域,这在多次缩放,平移或旋转时尤为有用。
绘制图像
12. context.drawImage()的三种重载
-
context.drawImage(image, top, left)
-
context.drawImage(image, top, left, width, height)
-
context.drawImage(image, offsetX, offsetY, w, h, top, left, width, height)。
其中第2种重载的后4个参数变成了第3种重载的后4个参数,而第3种重载的offsetX, offsetY, w, h这4个参数是基于image.width和image.height的,换言之,w 等于image.width时,图像没有被缩放,w为image.width的一半时,图像的宽度被放大至200%。w, h与image.width,image.height的比例决定了横轴和纵轴的缩放比。
offsetX等于image.width时,图像向右平移width的一半,offsetX为image.width的一半时,图像向右平移width的四分之一。offsetX和offsetY与image.width,image.height的比例决定了图像在两个方向的偏移。
建议在使用offsetX, offsetY, w, h这4个参数时,先获取到image.width和image.height,并进行换算。
13. context.drawImage()在画图时,资源必须加载完,建议使用image.onload进行处理,注意不要每次画图都加载,图片会闪烁,一个推荐的方式是使用闭包:
const drawPhoto = (() => {
const drawSth = () => { context.drawImage(img, 10, 10); }
const img = document.createElement('img');
img.onload = drawSth;// 不希望一开始就绘制的话,那这行可以不写
img.src = 'url';
return drawSth;
})();
阴影
14. 阴影 shadowColor的默认值为rgba(0, 0, 0, 0)而不是黑色(书上写错了),且随本体的变淡而变淡,与CSS的box-shadow不同,效果如下,黑线下方的是HTML元素,上方的是canvas:
图14.canvas阴影与box-shadow的对比
合成
15. 合成 globalAlpha默认值其实为1,书上写错了。
16. globalCompositionOperation各个取值效果如下,下图出自菜鸟教程:
图15.globalCompositionOperation的各种取值的效果