使用样式和颜色
在绘制图形的篇章里,我只用到默认的线条和填充样式。而在这一章里,我们将会探讨 canvas 全部的可选项,来绘制出更加吸引人的内容。
色彩 Colors
到目前为止,我们只看到过绘制内容的方法。如果我们想要给图形上色,有两个重要的属性可以做到:fillStyle
和 strokeStyle
。
fillStyle = color
设置图形的填充颜色
strokeStyle = color
设置图形轮廓的颜色
color
可以是表示CSS
颜色值的字符串,渐变对象或者图案对象。默认情况下,线条和填充颜色都是黑色(#000000
)。
fillStyle
示例
使用两层for
循环来绘制一个 6*6 的渐变色方格色卡效果,具体代码如下:
1 | function draw() { |
效果如下:
strokeStyle
示例
与上面的示例比较相似,我们使用arc
结合strokeStyle
来绘制一面洞洞板。具体代码如下:
1 | function draw() { |
效果如下:
透明度 Transparency
想要在 canvas 中绘制一个具有透明度的图形通常有两种方式:
- 使用
globalAlpha
属性。 - 使用
rgba()
方法。
两种方法接受的透明度参数规则是一样的。有效范围是从 0.0(完全透明)到 1.0(完全不透明)。
globalAlpha
示例
实现一个四色格作为背景,设置 globalAlpha
为 0.2
后,在上面画一系列半径递增的半透明圆。最终结果是一个径向渐变效果。圆叠加得越更多,原先所画的圆的透明度会越低。通过增加循环次数,画更多的圆,从中心到边缘部分,背景图会呈现逐渐消失的效果。
1 | function draw() { |
效果如下:
rgba()
示例
和上面那个类似,不过不是画圆,而是画矩形。这里还可以看出,rgba() 可以分别设置轮廓和填充样式,因而具有更好的可操作性和使用灵活性。
1 | function draw() { |
效果如下:
线型 Line styles
可以通过一系列属性来设置线的样式
lineWidth = value
设置线条宽度lineCap = type
设置线条末端样式lineJoin = type
设置线条与先条件结合处的样式miterLimit = value
限制当两条线相交时交接处最大长度;所谓交接处长度(斜街长度)是指线条交接处内角定点到外角顶点的长度。getLineDash()
返回一个包含当前虚线样式,长度为非负偶数的数组。setLineDash(segments)
设置当前虚线样式。lineDashOffset = value
设置虚线样式的起始偏移量。
lineWidth
属性的例子
这个属性设置当前绘线的粗细。属性值必须为正数。默认值是 1.0。
线宽是指给定路径的中心到两边的粗细。换句话说就是在路径的两边各绘制线宽的一半。因为画布的坐标并不和像素直接对应,当需要获得精确的水平或垂直线的时候要特别注意。
在下面的例子中,用递增的宽度绘制了 10 条直线。最左边的线宽 1.0 单位。并且,最左边的以及所有宽度为奇数的线并不能精确呈现,这就是因为路径的定位问题。
1 | function draw() { |
效果如下:
想要获得精确的线条,必须对线条是如何描绘出来的有所理解。见下图,用网格来代表 canvas
的坐标格,每一格对应屏幕上一个像素点。在第一个图中,填充了 (2,1)
至(5,5)
的矩形,整个区域的边界刚好落在像素边缘上,这样就可以得到的矩形有着清晰的边缘。
如果你想要绘制一条从(3,1)
到 (3,5)
,宽度是 1.0 的线条,你会得到像第二幅图一样的结果。实际填充区域(深蓝色部分)仅仅延伸至路径两旁各一半像素。而这半个像素又会以近似的方式进行渲染,这意味着那些像素只是部分着色,结果就是以实际笔触颜色一半色调的颜色来填充整个区域(浅蓝和深蓝的部分)。这就是上例中为何宽度为 1.0
的线并不准确的原因。
要解决这个问题,你必须对路径施以更加精确的控制。已知粗 1.0
的线条会在路径两边各延伸半像素,那么像第三幅图那样绘制从 (3.5,1)
到 (3.5,5)
的线条,其边缘正好落在像素边界,填充出来就是准确的宽为 1.0 的线条
lineCap
属性的例子
属性 lineCap
的值决定了线段端点显示的样子。它可以为下面的三种的其中之一:butt
,round
和 square
。默认是 butt。
在这个例子里面,我绘制了三条直线,分别赋予不同的 lineCap
值。还有两条辅助线,为了可以看得更清楚它们之间的区别,三条线的起点终点都落在辅助线上。
最左边的线用了默认的 butt
。可以注意到它是与辅助线齐平的。中间的是 round
的效果,端点处加上了半径为一半线宽的半圆。右边的是 square
的效果,端点处加上了等宽且高度为一半线宽的方块。
1 | function draw() { |
效果如下:
lineJoin
属性的例子
lineJoin
的属性值决定了图形中两线段连接处所显示的样子。它可以是这三种之一:round
, bevel
和 miter。
默认是 miter
。
这里我同样用三条折线来做例子,分别设置不同的 lineJoin
值。最上面一条是 round
的效果,边角处被磨圆了,圆的半径等于线宽。中间和最下面一条分别是 bevel
和 miter
的效果。当值是 miter
的时候,线段会在连接处外侧延伸直至交于一点,延伸效果受到下面将要介绍的 miterLimit
属性的制约。
1 | function draw() { |
效果如下:
miterLimit
属性的例子
就如上一个例子所见的应用 miter
的效果,线段的外侧边缘会被延伸交汇于一点上。线段之间夹角比较大时,交点不会太远,但随着夹角变小,交点距离会呈指数级增大。
miterLimit
属性就是用来设定外延交点与连接点的最大距离,如果交点距离大于此值,连接效果会变成了 bevel。注意,最大斜接长度(即交点距离)是当前坐标系测量线宽与此miterLimit
属性值(默认为 10.0)的乘积,所以miterLimit
可以单独设置,不受显示比例改变或任何仿射变换的影响:它只影响线条边缘的有效绘制形状。
更准确的说,斜接限定值(miterLimit
)是延伸长度(在 HTML Canvas 中,这个值是线段外连接点与路径中指定的点之间的距离)与一半线宽的最大允许比值。它也可以被等效定义为线条内外连接点距离(miterLength
)与线宽(lineWidth
)的最大允许比值(因为路径点是内外连接点的中点)。这等同于相交线段最小内夹角(θ )的一半的余割值,小于此角度的斜接将不会被渲染,而仅渲染斜边连接:
miterLimit
= maxmiterLength
/lineWidth
= 1 / sin ( min θ / 2 )- 斜接限定值默认为 10.0,这将会去除所有小于大约 11 度的斜接。
- 斜接限定值为 √2 ≈ 1.4142136 (四舍五入)时,将去除所有锐角的斜接,仅保留钝角或直角。
- 1.0 是合法的斜接限定值,但这会去除所有斜接。
- 小于 1.0 的值不是合法的斜接限定值。
在此示例中,当miterLimit
的值小于 4.2 时,图形可见部分的边角不会延伸相交,而是在蓝色线条边呈现斜边连接效果;当miterLimit
的值大于 10.0 时,此例中大部分的边角都会在远离蓝线的位置相交,且从左至右,距离随着夹角的增大而减小;而介于上述值之间的值所呈现的效果,也介于两者之间。
1 | function draw() { |
效果如下:
使用虚线
用 setLineDash
方法和 lineDashOffset
属性来制定虚线样式. setLineDash
方法接受一个数组,来指定线段与间隙的交替;lineDashOffset
属性设置起始偏移量.
在这个例子中,我们要创建一个蚂蚁线的效果。它往往应用在计算机图形程序选区工具动效中。它可以帮助用户通过动画的边界来区分图像背景选区边框。
1 | const ctx = document.getElementById('canvas').getContext('2d'); |
效果如下:
渐变
和一般的绘图软件一样,我们可以用线性或者径向的渐变来填充或描边。我们用下面的方法新建一个 canvasGradient
对象,并且赋给图形的 fillStyle
或 strokeStyle
属性。
createLinearGradient(x1, y1, x2, y2)
createLinearGradient
方法接受 4 个参数,表示渐变的起点 (x1,y1)
与终点 (x2,y2
)。
createRadialGradient(x1, y1, r1, x2, y2, r2)
createRadialGradient
方法接受 6 个参数,前三个定义一个以(x1,y1)
为原点,半径为 r1
的圆,后三个参数则定义另一个以(x2,y2)
为原点,半径为 r2
的圆。
1 | const lineargradient = ctx.createLinearGradient(0, 0, 150, 150); |
gradient.addColorStop(position, color)
addColorStop
方法接受 2 个参数,position
参数必须是一个 0.0 与 1.0 之间的数值,表示渐变中颜色所在的相对位置。例如,0.5 表示颜色会出现在正中间。color
参数必须是一个有效的 CSS
颜色值(如 #FFF
, rgba(0,0,0,1)
,等等)。
你可以根据需要添加任意多个色标(color stops)。下面是最简单的线性黑白渐变的例子。
1 | const lineargradient = ctx.createLinearGradient(0, 0, 150, 150); |
createLinearGradient
的例子
本例中,我弄了两种不同的渐变。第一种是背景色渐变,你会发现,我给同一位置设置了两种颜色,你也可以用这来实现突变的效果,就像这里从白色到绿色的突变。一般情况下,色标的定义是无所谓顺序的,但是色标位置重复时,顺序就变得非常重要了。所以,保持色标定义顺序和它理想的顺序一致,结果应该没什么大问题。
第二种渐变,我并不是从 0.0 位置开始定义色标,因为那并不是那么严格的。在 0.5 处设一黑色色标,渐变会默认认为从起点到色标之间都是黑色。
你会发现,strokeStyle
和 fillStyle
属性都可以接受 canvasGradient
对象。
1 | function draw() { const ctx = document.getElementById('canvas').getContext('2d'); // Create gradients const lingrad = ctx.createLinearGradient(0,0,0,150); lingrad.addColorStop(0, '#00ABEB'); lingrad.addColorStop(0.5, '#fff'); lingrad.addColorStop(0.5, '#26C000'); lingrad.addColorStop(1, '#fff'); const lingrad2 = ctx.createLinearGradient(0,50,0,95); lingrad2.addColorStop(0.5, '#000'); lingrad2.addColorStop(1, 'rgba(0,0,0,0)'); // assign gradients to fill and stroke styles ctx.fillStyle = lingrad; ctx.strokeStyle = lingrad2; // draw shapes ctx.fillRect(10,10,130,130); ctx.strokeRect(50,50,50,50);} |
效果如下:
createRadialGradient
的例子
这个例子,我定义了 4 个不同的径向渐变。由于可以控制渐变的起始与结束点,所以我们可以实现一些比(如在 Photoshop 中所见的)经典的径向渐变更为复杂的效果。(经典的径向渐变是只有一个中心点,简单地由中心点向外围的圆形扩张)
1 | function draw() { var ctx = document.getElementById('canvas').getContext('2d'); // 创建渐变 var radgrad = ctx.createRadialGradient(45,45,10,52,50,30); radgrad.addColorStop(0, '#A7D30C'); radgrad.addColorStop(0.9, '#019F62'); radgrad.addColorStop(1, 'rgba(1,159,98,0)'); var radgrad2 = ctx.createRadialGradient(105,105,20,112,120,50); radgrad2.addColorStop(0, '#FF5F98'); radgrad2.addColorStop(0.75, '#FF0188'); radgrad2.addColorStop(1, 'rgba(255,1,136,0)'); var radgrad3 = ctx.createRadialGradient(95,15,15,102,20,40); radgrad3.addColorStop(0, '#00C9FF'); radgrad3.addColorStop(0.8, '#00B5E2'); radgrad3.addColorStop(1, 'rgba(0,201,255,0)'); var radgrad4 = ctx.createRadialGradient(0,150,50,0,140,90); radgrad4.addColorStop(0, '#F4F201'); radgrad4.addColorStop(0.8, '#E4C700'); radgrad4.addColorStop(1, 'rgba(228,199,0,0)'); // 画图形 ctx.fillStyle = radgrad4; ctx.fillRect(0,0,150,150); ctx.fillStyle = radgrad3; ctx.fillRect(0,0,150,150); ctx.fillStyle = radgrad2; ctx.fillRect(0,0,150,150); ctx.fillStyle = radgrad; ctx.fillRect(0,0,150,150);} |
这里,我让起点稍微偏离终点,这样可以达到一种球状 3D 效果。
效果如下:
4 个径向渐变效果的最后一个色标都是透明色。如果想要两色标直接的过渡柔和一些,只要两个颜色值一致就可以了。代码里面看不出来,是因为我用了两种不同的颜色表示方法,但其实是相同的,#019F62 = rgba(1,159,98,1)。
图案样式 Patterns
createPattern(image, type)
用于实现图案的效果是非常简单的方法。
该方法接受两个参数。Image 可以是一个 Image
对象的引用,或者另一个 canvas 对象。Type
必须是下面的字符串值之一:repeat
,repeat-x
,repeat-y
和 no-repeat
。
createPattern
的例子
创建一个图案然后赋给了 fillStyle
属性。唯一要注意的是,使用 Image 对象的 onload
handler 来确保设置图案之前图像已经装载完毕。
1 | function draw() { var ctx = document.getElementById('canvas').getContext('2d'); // 创建新 image 对象,用作图案 var img = new Image(); img.src = 'https://mdn.mozillademos.org/files/222/Canvas_createpattern.png'; img.onload = function() { // 创建图案 var ptrn = ctx.createPattern(img, 'repeat'); ctx.fillStyle = ptrn; ctx.fillRect(0, 0, 150, 150); }} |
效果如下:
阴影 Shadows
-
shadowOffsetX
和shadowOffsetY
用来设定阴影在 X 和 Y 轴的延伸距离,它们是不受变换矩阵所影响的。负值表示阴影会往上或左延伸,正值则表示会往下或右延伸,它们默认都为0
。 -
shadowOffsetX 和
shadowOffsetY
用来设定阴影在 X 和 Y 轴的延伸距离,它们是不受变换矩阵所影响的。负值表示阴影会往上或左延伸,正值则表示会往下或右延伸,它们默认都为0
。 -
shadowBlur 用于设定阴影的模糊程度,其数值并不跟像素数量挂钩,也不受变换矩阵的影响,默认为
0
。 -
shadowColor 是标准的 CSS 颜色值,用于设定阴影颜色效果,默认是全透明的黑色。
文字阴影的例子
这个例子绘制了带阴影效果的文字。
1 | function draw() { |
效果如下:
Canvas 填充规则
当我们用到 fill
(或者 clip
和isPointinPath
)你可以选择一个填充规则,该填充规则根据某处在路径的外面或者里面来决定该处是否被填充,这对于自己与自己路径相交或者路径被嵌套的时候是有用的。
两个可能的值:
"nonzero"
: 顾名思意就是“非零规则”,用通俗的话讲,就算计算某些东西是不是0
,如果不是0
则内部,填充;如果是0
则外部,不填充。"evenodd"
: 顾名思意就是“奇偶规则”,用通俗的话讲,就算计算某些东西是不是奇数,如果是是奇数则内部,填充;如果是偶数则外部,不填充。
下面关键来了,这里的“计算某些东西”究竟计算的是什么东西呢?
nonzero 规则和 evenodd 规则计算的东西还不一样,nonzero 是计算顺时针逆时针数量,evenodd 是交叉路径数量。
下面例子分别查看一下两种效果:
1 | // evenodd |
1 | function draw() { |
两段代码效果均如下所示: