HTML5 Canvas

HTML5定义的 <canvas> 元素可以使用 javascript 在网页上绘制图像,canvas(可以称作“画布”)是网页上的一个矩形区域,我们就是在这个区域内进行绘制;在这个矩形区域内,我们可以控制其每个像素,而且 <canvas> 拥有多种绘制路径、矩形、圆形、字符以及添加图像的方法。

<canvas> 元素的浏览器兼容情况:

元素 Chrome IE Firefox Safari Opera
<canvas> 4.0 9.0 2.0 3.1 9.0

canvas 元素基础知识

<canvas> 元素用起来很简单,但有几个需要注意的地方:

  • <canvas> 元素默认是没有内容和边框的
  • <canvas> 元素一般需要设置一个 id 属性,方便 javascript 代码引用
  • <canvas> 元素需要定义宽度和高度,不然不能正常显示

我们定义一个带有边框的画布,如下所示:

1
<canvas id="canvas-1" width="300" height="40" style="border: 1px solid green"></canvas>

可以看到,这与使用普通的HTML DOM元素没什么区别,其实除了有绘制能力外,<canvas> 元素就是一个普通的DOM元素;我们也可以同时定义多个画布,使用DOM元素的 id 属性来区分。

使用 JavaScript 绘制

<canvas> 元素本身没有任何绘制功能,它只是一个绘图的容器;要在 <canvas> 元素上绘制内容必须使用JavaScript,下面通过一个最简单的示例来说明这个过程。

1
2
3
4
let canvas = document.getElementById('canvas-2');
let ctx = canvas.getContext('2d');
ctx.fillStyle = "#FF0000";
ctx.fillRect(10, 10, 150, 45);

可以看到,在 <canvas> 元素中绘制图形的过程共分成三步。

第一步,得到 canvas 元素

要在画布上绘制内容首先要拿到画布元素,这一步跟其它DOM一样,通过文档对象的 getElementById 方法来获取就可以了。

1
let canvas = document.getElementById('canvas-2');

第二步,创建绘制对象

拿到画布元素后,我们还需要需要通过调用 <canvas> 元素的 getContext() 方法来获取该元素的绘制对象,绘制对象是一个定义了绘制属性与方法的HTML内置对象。

1
let ctx = canvas.getContext('2d');

getContext 方法中传了2d字符串作为参数可能会让人误解 <canvas> 元素是否也支持3d绘制,其实现阶段它是不支持的,这个方法现在只能接受2d字符串作为参数,但HTML5规范中对此也有说明:

A future version of this specification will probably define a 3d context.

第三步,开始绘制

经过上面两步准备,我们可以在画布上绘制内容了,这一步很复杂,因为我们可以在画布上绘制任何内容,而绘制对象也包含多种绘制属性和方法,这些内容会在接下来的部分介绍。

在上面的示例中,我们将绘制对象的填充颜色设置为红色(默认是黑色),然后调用绘制对象的 fillRect(x,y,width,height) 方法绘制了一个矩形。

1
2
ctx.fillStyle = "#FF0000";
ctx.fillRect(10, 10, 150, 45);

canvas 坐标系

canvas元素是一个二维网格,画布的左上角是坐标系原点(0,0),横轴向右延伸,纵轴向下延伸;在上面的简单示例中,我们调用了 fillRect(10, 10, 150, 45) 这么一个绘制矩形方法,意思是从(10,10)坐标点开始画一个150x45像素的矩形。

canvas API 参考

下面以分类的形式列举canvas绘图相关的所有API,在上面解释过了,canvas 元素本身没有任何绘制能力,因此这些API主要是绘制对象的属性和各种绘制方法。

除了列举API,还会对一些比较复杂或难以理解的属性或方法进一步解释,较为简单的API就不再具体介绍了。

颜色、样式及阴影

属性 说明
fillStyle 设置或返回用于填充绘制的颜色、渐变和图案
strokeStyle 设置或返回用于线条绘制的颜色、渐变和图案
shadowColor 设置或返回阴影的颜色
shadowBlur 设置或返回阴影的模糊级别
shadowOffsetX 设置或返回阴影的水平距离
shadowOffsetY 设置或返回阴影的垂直距离
方法 说明
createLinearGradient() 创建一个线性渐变
createPattern() 在指定方向上重复一个元件
createRadialGradient() 创建径向渐变
addColorStop() 指定梯度对象的颜色和停止位置

createLinearGradient()方法

createLinearGradient() 方法可以创建一个线性渐变对象,该对象可以用于填充矩形、圆、线条及文本等形状,也就用于 fillStylestrokeStyle 属性的值。

使用 addColorStop() 方法指定不同的颜色及该颜色在渐变对象的位置。

1
context.createLinearGradient(x0,y0,x1,y1);

上面是 createLinearGradient() 方法的语法,四个参数分别用于指定起点(x0,y0)和终点(x1,y1)。

createPattern()方法

createPattern() 方法用于在指定的方向上重复一个元件,该元件可以是图像、视频或其它的canvas元素,并可以用于填充矩形、线条等形状。

1
context.createPattern(image,"repeat|repeat-x|repeat-y|no-repeat");

在上面 createPattern() 方法的语法定义中,image用于指定图案要使用的图像、视频或其它的canvas元素,第二个参数指定重复方向。

createRadialGradient()方法

createRadialGradient() 方法可以创建一个径向渐变对象,其行为及用途与 createLinearGradient() 方法创建的线性渐变一样。

1
context.createRadialGradient(x0,y0,r0,x1,y1,r1);

在上面 createRadialGradient() 方法的语法定义中,x0,y0是径向渐变起始圆形圆心的坐标,r0起始圆形的半径;x1,y1是结束圆形圆心的坐标,r1是结束圆形的半径。

addColorStop() 方法

addColorStop() 方法用于指定渐变对象中颜色和其位置,必要要配合 createRadialGradient() 方法或 createLinearGradient() 方法使用;该方法可以调用多次以实现多色渐变(至少调用一次),如果我们创建了渐变对象却没有调用 addColorStop() 方法,则该渐变并不会显示出来。

1
gradient.addColorStop(stop,color);

从定义语法上看,addColorStop() 方法接受两个参数:stop是一个[0,1]区间的值,表示从渐变起点到终点之间的位置比例;color就是指的颜色喽!

线条样式

属性 说明
lineCap
lineJoin 设置或返回当两线会合时产生角的类型
lineWidth 设置或返回当前的线条宽度
miterLimit

矩形

方法 说明
rect(x,y,width,height) 创建一个矩形,该方法不会真正绘制,需要调用 fill()stroke 方法完成绘制
fillRect(x,y,width,height) 绘制一个填充的矩形(绘制矩形后使用 fillStyle 属性值填充)
strokeRect(x,y,width,height) 绘制一个矩形(不填充,仅是绘制 strokeStyle 定义的边框)
clearRect(x,y,width,height) 清除指定矩形区域的像素

路径

方法 说明
fill() 填充当前的绘图(路径),默认使用黑色
stroke() 绘制定义好的路径,不填充
beginPath() 开始一个路径,或重置当前路径
moveTo() 将画笔移到画布的指定坐标点,该方法不会绘制线条
closePath() 创建一个路径,从当前坐标点回到起始坐标点
lineTo(x,y) 添加一个新的坐标点并创建一条到上一个点的线,该方法不会真正绘制线条
clip() 从原始画布上裁剪任何形状和尺寸的区域
quadraticCurveTo() 创建一个二次贝塞尔曲线
bezierCurveTo() 创建一个三次贝塞尔曲线
arc() 创建一个弧线,用于创建圆或圆的一部分圆弧
ellipse() 创建椭圆弧线
arcTo() 在两条切线中间创建弧线
isPointInPath(x,y) 判断指定的点是否在当前路径上

arc()方法

arc() 方法用于创建一段弧线(圆或圆的一部分),但该方法与 rect() 类似,它只是描述了路径并不会真正绘制,因此还是需要调用 fill() 方法填充或 stroke() 方法绘制。

1
context.arc(x,y,r,sAngle,eAngle,anticlockwise);

上面的 arc() 方法语法定义中,参数还是比较多的;x和y两个参数组成一个点的坐标指定圆心,r是该圆的半径,sAngle和eAngle分别指定圆弧的起始角度和结束角度(用 弧度 表示,0度在时钟3点位置),anticlockwise是可选的,指定是否逆时针绘制,默认是false即顺时针绘制。

ellipse()方法

ellipse() 方法用于创建一段椭圆弧线(椭圆或椭圆的一部分),用法与 arc() 方法基本一致,当 ellipse() 方法的两个半径相等时,其结果与 arc() 方法一样。

1
context.ellipse(x, y, radiusX, radiusY, rotation, startAngle, endAngle, anticlockwise)

ellipse() 方法参数比 arc() 方法要多,其中radiusX和radiusY分别指定X轴和Y轴的半径,rotation指定旋转角度(弧度),其余参数与 arc() 方法一样。

arcTo()方法

arcTo() 方法理解起来略显复杂,它是向当前子路径添加一个弧线(该弧线会连接到画笔当前位置点上),该弧线由指定的控制点和半径定义,实际上是以通过两个控制点(实际上是三个点)定义的两条直线为切线绘制弧线。

1
context.arcTo(x1, y1, x2, y2, r);

在上面的语法定义中,(x1,y1)是第一个控制点坐标,(x2,y2)是第二个控制点坐标,r是圆的半径;这里的两个点必须与画笔当前所在点联合起来,也就是三个点形成两条线,这两条线作为圆弧的切线。

这个过程描述起来很费劲且难以理解,下面的图示演示了三个点的位置关系及两条切线的形成,以及圆弧最终的效果:

转换

方法 说明
scale() 缩放当前绘制的内容
rotate() 旋转当前绘制的内容
translate() 重置画布原点(0,0)的位置
transform() Replaces the current transformation matrix for the drawing
setTransform()

文本

属性 说明
font 设置或返回当前的字体
textAlign 设置或返回文本内容当前的对齐方式,相对绘制时指定珠锚点来说,默认值为start
textBaseline 设置或返回当前用于绘制文本的基线,默认值是alphabetic

font属性

font 属性用于定义或返回当前绘制文本的字体属性,其语法与CSS中的定义一样:

1
context.font="italic small-caps bold 12px arial";

font 属性的默认值是 10px sans-serif

方法 说明
fillText(text,x,y) 在画布上绘制填充的文本
strokeText(text,x,y) 绘制文本(非填充)
measureText(text) 返回一个包含指定文本宽度的对象

绘制图片

方法 说明
drawImage() 在画布上绘制图片、其它画布或视频

drawImage()方法

drawImage() 方法有三种形式,如下所示:

1
2
3
4
5
6
// 仅指定图像绘制位置
context.drawImage(img,x,y);
// 指定图像绘制位置和图像的宽度高度,会导致图像拉伸/压缩
context.drawImage(img,x,y,width,height);
// 裁剪图像并将裁剪后的部分绘制到画布上
context.drawImage(img,sx,sy,swidth,sheight,x,y,width,height);

前两种形式比较简单,这里不再细说;第三种形式参数较多,(sx,sy)是开始裁剪的坐标点,swidth和sheight表示裁剪的宽和高,另外几个参数与第一种形式一样。

像素操作

属性 说明
width 返回ImageData对象的宽度
height 返回ImageData对象的高度
data 返回一个指定ImageData对象的包含图像数据的对象
方法 说明
createImageData() 创建一个新的空白ImageData对象
getImageData() 返回一个画布上指定矩形像素数据的ImageData对象
putImageData() 将指定ImageData对象的图像数据放回到画布上

createImageData()方法

createImageData() 方法创建一个新的空ImageData对象,该对象包含指定尺寸内所有的像素数据,其中每个像素都包含4条信息,也就是RGBA值(红绿蓝三色加一个alpha通道值,值都是0~255);新对象的像素数据(RGBA值)默认是黑色透明(即(0,0,0,0));这些RGBA值会放在一个数组存储在ImageData对象的 data 属性中,因为数组包含每个像素的4条信息,因此该数组的长度是ImageData对象的4倍: width*height*4。

1
2
context.createImageData(width,height);
context.createImageData(anotherImageData);

由上面的语法定义可以看出 createImageData() 方法有两种形式,第一种是通过指定尺寸(以像素为单位)来创建,第二种是使用与另一个ImageData对象相同的尺寸来创建(并不会复制该对象的像素数据)。

getImageData()方法

getImageData() 方法可以返回一个ImageData对象,该对象复制了画布上指定矩形区域的像素数据;但ImageData对象并不是图像,它只是持有画布指定区域(矩形)每个像素的信息。

1
context.getImageData(x,y,width,height);

在上面的语法定义中,(x,y)是 getImageData() 方法复制起始点坐标,width和height分别是要复制区域的宽和高。

putImageData()

putImageData() 方法可以将图像数据(指定的ImageData对象)放回画布上,图像数据一般是由 getImageData() 方法获取或 createImageData() 方法生成。

1
context.putImageData(imgData,x,y,dirtyX,dirtyY,dirtyWidth,dirtyHeight)

上面的语法定义中,前三个参数是必须的,后面四个参数是可选的;(x,y)是ImageData对象的左上角坐标,以像素为单位;(dirtyX,dirtyY)是ImageData要放置的画布位置坐标;dirtyWidth和dirtyHeight分别指定图像要绘制的宽度和高度。

混合

属性 说明
globalAlpha 设置或返回当前绘图的透明度,设置后绘制的内容都应用该透明度,默认值为1.0
globalCompositeOperation 设置或返回如何在已有图像上绘制新图像,默认值为“source-over”

globalCompositeOperation属性

globalCompositeOperation 属性设置或返回源图像(准备绘制到画布上的内容)如何绘制到目标图像(画布上已存在的内容,即原来绘制的内容)上;该属性提供了非常强大的绘图功能,可以让绘制的图片产生诸如互相叠加、互相剪切等效果。

其取值及其解释参见文档:HTML canvas globalCompositeOperation Property,该页面的解释比较详细,也有不同取值的演示效果图。

其它

方法 说明
save() 保存当前上下文状态
restore() 返回上次保存的路径状态和属性
createEvent()
getContext()
toDataURL()

示例

“纸上得来终觉浅,绝知此事要躬行”,上面一堆都是对canvas相关知识的理论解释,看起来很容易让人烦躁,而且并不能很容易理解其用法与效果,这部分就用一些例子来说明。

绘制矩形

该示例绘制了三个矩形,主要演示了填充矩形及非填充矩形的绘制、线性渐变定义与应用等知识。需要注意 rect() 方法仅是创建一个矩形,还需要调用 fill()stroke() 来完成绘制才能显示在画布上;也就是说 rect() + fill() 两个方法效果等于 fillRect()rect() + stroke() 两个方法效果等于 strokeRect()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
(function () {
let canvas = document.getElementById('canvas-3');
let ctx = canvas.getContext('2d');
ctx.strokeStyle = "red";
ctx.strokeRect(10, 10, 220, 60);
})();
(function () {
let canvas = document.getElementById('canvas-4');
let ctx = canvas.getContext('2d');
let lg = ctx.createLinearGradient(0,0,100,0);
lg.addColorStop(0, "yellow");
lg.addColorStop(0.5, "blue");
lg.addColorStop(1, "#FF0000");
ctx.fillStyle = lg;
ctx.fillRect(10, 10, 220, 60); // 也可以先调用rect()再调用fill()完成
})();
(function () {
let canvas = document.getElementById('canvas-5');
let ctx = canvas.getContext('2d');
let lg = ctx.createLinearGradient(20,0,200,0);
lg.addColorStop(0, "yellow");
lg.addColorStop(0.5, "blue");
lg.addColorStop(1, "#FF0000");
ctx.strokeStyle = lg;
ctx.lineWidth = 12;
ctx.rect(20, 20, 200, 40);
ctx.stroke();
})();

绘制弧线/圆形

该部分示例绘制了三个弧线形状,第一个用 arcTo() 方法,该方法很难控制,需要多查资料;第二个比较简单,直接用 ellipse() 方法绘制半个椭圆并旋转;第三个使用 arc() 方法绘制了一个圆并用径向渐变填充。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
(function () {
let canvas = document.getElementById('canvas-6');
let ctx = canvas.getContext('2d');
ctx.moveTo(100, 5);
ctx.arcTo(160, 8, 120, 60, 40);
ctx.stroke();
})();
(function () {
let canvas = document.getElementById('canvas-7');
let ctx = canvas.getContext('2d');
ctx.ellipse(120, 40, 64, 24, 0.1*Math.PI, 0.4*Math.PI, 1.6*Math.PI);
ctx.fill();
})();
(function () {
let canvas = document.getElementById('canvas-8');
let ctx = canvas.getContext('2d');
let rg = ctx.createRadialGradient(120, 40, 0, 120, 40, 32);
rg.addColorStop(0, "#F36666");
rg.addColorStop(0.5, 'blue');
rg.addColorStop(1, 'red');
ctx.fillStyle = rg;
ctx.arc(120, 40, 36, 0, 2*Math.PI);
ctx.fill();
})();

绘制文本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
(function () {
let canvas = document.getElementById('canvas-9');
let ctx = canvas.getContext('2d');
ctx.font = "32px Arial";
ctx.strokeText('你好,Canvas', 60, 40);
})();
(function () {
let canvas = document.getElementById('canvas-10');
let ctx = canvas.getContext('2d');
ctx.font = "36px Comic Sans MS";
ctx.fillStyle = "red";
ctx.textAlign = "center";
ctx.fillText('Hello Canvas', canvas.width/2, canvas.height/2);
})();

绘制图像

1
2
3
4
5
6
7
8
9
10
11
12
(function () {
let canvas = document.getElementById('canvas-12');
let ctx = canvas.getContext('2d');
let img = document.images[0];
ctx.drawImage(img, 30, 0, 240, 180, 10, 0, 240, 180);
})();
(function () {
let canvas = document.getElementById('canvas-13');
let ctx = canvas.getContext('2d');
let anotherCanvas = document.getElementById('canvas-8');
ctx.drawImage(anotherCanvas, 0, 0);
})();

像素操作

像素操作是非常复杂也极其强大的功能,它可以在画布上做任何事情,但由于ImageData将所有像素数据放在一个一维数组中,因此使用起来很麻烦。

下面是一个比较复杂的示例,我想了差不多一天才理清如何实现,具体过程很难用文字描述清楚,因此我在代码中添加了少量注释。我觉得将画布相像成二维网格,每个像素都是一个格子去分析比较简单。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
(function() {
let canvas = document.getElementById('canvas-14');
let ctx = canvas.getContext('2d');
let imgData = ctx.createImageData(canvas.width, canvas.height);
let refreshBtn = document.getElementById('refresh-btn');
refreshBtn.addEventListener("click", function() {
// 定义多少个像素同色,这里20的意思是横向10个像素纵向10个像素组成的矩形区域内共100个像素同色
let base = 20;
// 循环像素,每经过base个像素换一种颜色
for (let i = 0; i < canvas.width * canvas.height; i = i + base) {
let r = Math.floor(Math.random() * 255);
let g = Math.floor(Math.random() * 255);
let b = Math.floor(Math.random() * 255);
// 每次外部循环都会有base^2次内部循环,将同色区域填充相同颜色
for (let j = 0; j < base; j++) {
for (let k = 0; k < base; k++) {
imgData.data[4 * (i + k + canvas.width * j) + 0] = r;
imgData.data[4 * (i + k + canvas.width * j) + 1] = g;
imgData.data[4 * (i + k + canvas.width * j) + 2] = b;
imgData.data[4 * (i + k + canvas.width * j) + 3] = 255;
}
}
// 横向到头后跳过base - 1行继续循环
if (i > 0 && i % canvas.width == 0)
i = i + (canvas.width * (base - 1));
}
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.putImageData(imgData, 0, 0);
});
refreshBtn.click();
})();

这个示例没有涉及到 getImageData() 方法,由于篇幅关系就不再写相关示例了。

文章目录
  1. 1. canvas 元素基础知识
  2. 2. 使用 JavaScript 绘制
  3. 3. canvas 坐标系
  4. 4. canvas API 参考
    1. 4.1. 颜色、样式及阴影
      1. 4.1.1. createLinearGradient()方法
      2. 4.1.2. createPattern()方法
      3. 4.1.3. createRadialGradient()方法
      4. 4.1.4. addColorStop() 方法
    2. 4.2. 线条样式
    3. 4.3. 矩形
    4. 4.4. 路径
      1. 4.4.1. arc()方法
      2. 4.4.2. ellipse()方法
      3. 4.4.3. arcTo()方法
    5. 4.5. 转换
    6. 4.6. 文本
      1. 4.6.1. font属性
    7. 4.7. 绘制图片
      1. 4.7.1. drawImage()方法
    8. 4.8. 像素操作
      1. 4.8.1. createImageData()方法
      2. 4.8.2. getImageData()方法
      3. 4.8.3. putImageData()
    9. 4.9. 混合
      1. 4.9.1. globalCompositeOperation属性
    10. 4.10. 其它
  5. 5. 示例
    1. 5.1. 绘制矩形
    2. 5.2. 绘制弧线/圆形
    3. 5.3. 绘制文本
    4. 5.4. 绘制图像
    5. 5.5. 像素操作
|