图像填充

第一章中,我们学习了 Canvas 能够使用颜色跟渐变填充路径,也能够使用图案(pattern)来填充路径。 能像CSS 中的背景图一样控制 Canvas 中的图案(pattern)的平铺方式。

跟渐变一样,图案的绘制也是相对当前的坐标系。 这就是我为什么在绘制第二个矩形的时候向右平移200像素。第二个矩形 X 方向不会平铺,Y 方向才会, 设置填充区域变大并不会绘制更多的图案。 拖拽下面的数值看看它是如何工作的。


            
        

在canvas上绘制图像必须等到图片加载完毕,确保绘制图像代码放在图像的 onload 回调函数中。

透明度

Canvas API 通过globalAlpha属性控制绘制函数的透明度。 下一个例子绘制了两个部分重叠的红色正方形,在绘制前改变了透明度。


            
        

透明度的设置对所有的绘制操作都生效。 改变上面的透明度值观察变化影响。 当你完成操作后确保设置回1.0,不影响后续绘制操作。 globalAlpha 属性取值范围在0~1之间,其他值会忽略(在某些平台还会异常)。

形变

在条形图章节中,我们通过设置不同的x,y坐标来绘制同样的矩形。 这次我们使用形变函数而不是修改坐标。 每次循环我们平移并增加100像素,移动条形到右边。


            
        

拖拽x平移量观察对上面图表的影响。

与很多2D接口一样,Canvas 也支持标准的平移、旋转和缩放。 这次在屏幕上绘制矩形不需要手动计算新坐标。 Canvas 已经帮你计算了。 同样可以通过顺序调用来组合转换。 例如,绘制一个矩形,平移到中心,然后旋转30度:


            
        

每次调用平移、旋转或者缩放都相对与上一次的变换中。 变换多次之后就会混乱了。你可以像这样撤销变换:

for(var i=0; i<data.length; i++) {
    c.translate(40+i*100, 460-dp*4);
    var dp = data[i];
    c.fillRect(0,0,50,dp*4);
    c.translate(-40-i*100, -460+dp*4);
}

但是这样就写了很多冗余代码。如果忘记撤销恢复一次,那你可能抓狂了并花费数个小时来排除问题(当然,我曾经试过)。 还好 Canvas 提供了一个状态保存的API。

状态保存

context2D 上下文对象代表了当前的绘制状态。 在本书中我经常使用 ctx 变量来表示该对象。状态包括当前的形变、填充和描边的颜色、当前的字体以及其他变量。 可以通过 save() 函数将状态保存到栈结构中。 当你保存状态之后,进行修改,然后使用 restore() 函数恢复之前的状态。 Canvas 认真地帮你做笔记。这个是通过状态保存修改过的之前的例子。注意的是我们没有撤销形变这步。

for(var i=0; i<data.length; i++) {
    c.save();
    c.translate(40+i*100, 460-dp*4);
    var dp = data[i];
    c.fillRect(0,0,50,dp*4);
    c.restore();
}

裁切

有时可能只需要绘制图像的一部分。那么可以使用 clip 函数。 它使用当前的路径作为之后绘制图像的蒙层。 这就意味着之后任何的图像绘制都只出现在裁切的形状里。 在蒙层之外的绘制的图像都不会显示在屏幕上。 当你想通过组合形状来创建一个复杂的图形或者由于某些原因需要更新画布上的某一部分这就相当有用。 以下的一个例子是:绘制了被三角形裁切掉了部分的长方形。


            
        

注意黄色的矩形填充原来红色矩形得到的三角形。 同时注意三角形的下半部拥有较粗的边框,上部分拥有较细的边框。 因为三角形的边框是在正中心的。裁切的时候黄色路径覆盖了区域内的边框,区域外的没有覆盖。

事件

Canvas 没有定义新事件。你能够在其他任何地方监听相同的鼠标以及触控事件。这有好有坏。

Canvas 画布看上去就是浏览器的一个矩形区域。 浏览器不知道画布上你绘制了什么。 在canvas画布上拖拽鼠标,浏览器返回标准的整个canvas元素的拖拽事件,而不是canvas画布里边绘制的某一部分。 这就意味着如果你需要绘制按钮或者作图工具,你不得不独自将浏览器提供的原始鼠标事件转换成个人数据模型。

计算当前鼠标所在Canvas的路径十分困难,还好 Canvas 提供了一个帮助性的 API:isPointInPath 该函数的功能是判断一个给定的坐标是否在当前路径中。以下是一个例子:

c.beginPath();
c.arc(
    100,100, 40,  // 圆心在(100,100)半径40像素的圆
    0,Math.PI*2,  // 0 ~ 360 °的整圆
);
c.closePath();
var a = c.isPointInPath(80,0);     // 返回 true
var b = c.isPointInPath(200,100);  // 返回 false

另一个选项是使用三维类库,例如 Amino 让你使用路径而非像素工作。它为用户提供了事件处理程序及绘制。