Canvas 是什么?

  • Canvas 中文名叫 “画布”,是 HTML5 新增的一个标签。
  • Canvas 允许开发者通过 JS在这个标签上绘制各种图案。
  • Canvas 拥有多种绘制路径、矩形、圆形、字符以及图片的方法。
  • Canvas 在某些情况下可以 “代替” 图片。
  • Canvas 可用于动画、游戏、数据可视化、图片编辑器、实时视频处理等领域。

    Canvas 和 SVG 的区别

    | Canvas | SVG | | —- | —- | | 用JS动态生成元素(一个HTML元素) | 用XML描述元素(类似HTML元素那样,可用多个元素来描述一个图形) | | 位图(受屏幕分辨率影响) | 矢量图(不受屏幕分辨率影响) | | 不支持事件 | 支持事件 | | 数据发生变化需要重绘 | 不需要重绘 |

就上面的描述而言可能有点难懂,你可以打开 AntV 旗下的图形编辑引擎做对比。G6[1] 是使用 canvas 开发的,X6[2] 是使用 svg 开发的。
我的建议是:如果要展示的数据量比较大,比如一条数据就是一个元素节点,那使用 canvas 会比较合适;如果用户操作的交互比较多,而且对清晰度有要求(矢量图),那么使用 svg 会比较合适。

canvas基本的画布功能

创建 元素时至少要设置其 width 和 height 属 性,这样才能告诉浏览器在多大面积上绘图。

出现在开始和结束标签 之间的内容是后备数据,会在浏览器不支持 元素时显 示。

  1. <canvas id="drawing" width="200" height="200">Adrawing of something.</canvas>

getContext() 获取绘图上下文

要在画布上绘制图形,首先要取得绘图上下文。使用 getContext() 方法可以获取对绘图上下文的引用。对于平面图 形,需要给这个方法传入参数 “2d” ,表示要获取2D上下文对象。

  1. let drawing = document.getElementById("drawing");
  2. // 确保浏览器支持<canvas>
  3. if (drawing.getContext) {
  4. let context = drawing.getContext("2d");
  5. // 其他绘制代码
  6. }

使用 元素时,最好先测试一下 getContext() 方 法是否存在。有些浏览器对HTML规范中没有的元素会创建默认 HTML元素对象。这就意味着即使 drawing 包含一个有效的元素引 用, getContext() 方法也未必存在。

2D绘图上下文

2D绘图上下文提供了绘制2D图形的方法,包括矩形、弧形和路 径。2D上下文的坐标原点(0, 0)在 元素的左上角。所有坐 标值都相对于该点计算。默认情况下, width 和 height 表示两个方向上像 素的最大值。

填充fillStyle和描边strokeStyle

2D上下文有两个基本绘制操作:填充和描边。填充以指定样式 (颜色、渐变或图像)自动填充形状,而描边只为图形边界着色。大 多数2D上下文操作有填充和描边的变体,显示效果取决于两个属 性: fillStyle 和 strokeStyle 。

这两个属性可以是字符串、渐变对象或图案对象,默认值都 为 “#000000” 。字符串表示颜色值,可以是CSS支持的任意格式: 名称、十六进制代码、 rgb 、 rgba 、 hsl 或 hsla 。

  1. context.strokeStyle = "red";
  2. context.fillStyle = "#0000ff";

绘制矩形

矩形是唯一一个可以直接在2D绘图上下文中绘制的形状。与绘制 矩形相关的方法有3个: fillRect() 、 strokeRect() 和 clearRect() 。这些方法都接收4个参数:矩形 x 坐标、矩形 y 坐标、矩形宽度和矩形高度。这几个参数的单位都是像素。

fillRect() 填充矩形

fillRect() 方法用于以指定颜色在画布上绘制并填充矩形。 填充的颜色使用 fillStyle 属性指定。

  1. // 绘制红色矩形
  2. context.fillStyle = "#ff0000";
  3. context.fillRect(10, 10, 50, 50);
  4. // 绘制半透明蓝色矩形
  5. context.fillStyle = "rgba(0,0,255,0.5)";
  6. context.fillRect(30, 30, 50, 50);

strokeRect() 描边(轮廓)矩形

strokeRect() 方法使用通过 strokeStyle 属性指定的颜色 绘制矩形轮廓。

  1. // 绘制红色轮廓的矩形
  2. context.strokeStyle = "#ff0000";
  3. context.strokeRect(10, 10, 50, 50);
  4. // 绘制半透明蓝色轮廓的矩形
  5. context.strokeStyle = "rgba(0,0,255,0.5)";
  6. context.strokeRect(30, 30, 50, 50);

clearRect() 方法可以擦除画布中某个区域

使用 clearRect() 方法可以擦除画布中某个区域。该方法用于 把绘图上下文中的某个区域变透明。通过先绘制形状再擦除指定区 域,可以创建出有趣的效果,比如从已有矩形中开个孔。

  1. let drawing =document.getElementById("drawing");
  2. // 确保浏览器支持<canvas>
  3. if (drawing.getContext) {
  4. let context = drawing.getContext("2d");
  5. /*
  6. * 引自MDN文档
  7. */
  8. // 绘制红色矩形
  9. context.fillStyle = "#ff0000";
  10. context.fillRect(10, 10, 50, 50);
  11. // 绘制半透明蓝色矩形
  12. context.fillStyle = "rgba(0,0,255,0.5)";
  13. context.fillRect(30, 30, 50, 50);
  14. // 在前两个矩形重叠的区域擦除一个矩形区域
  15. context.clearRect(40, 40, 10, 10);
  16. }

绘制路径

2D绘图上下文支持很多在画布上绘制路径的方法。通过路径可以 创建复杂的形状和线条。

beginPath()开始绘制

要绘制路径,必须首先调用 beginPath() 方法以表示要开始绘制新路径。然后,再调用下列方法来绘制路径。
image.png
image.png

closePath()返回起点的线

下面这个例子使用前面提到的方法绘制了一个不带数字的表盘:

  1. let drawing =
  2. document.getElementById("drawing");
  3. // 确保浏览器支持<canvas>
  4. if (drawing.getContext) {
  5. let context = drawing.getContext("2d");
  6. // 创建路径
  7. context.beginPath();
  8. // 绘制外圆
  9. context.arc(100, 100, 99, 0, 2 * Math.PI,
  10. false);
  11. // 绘制内圆
  12. context.moveTo(194, 100);
  13. context.arc(100, 100, 94, 0, 2 * Math.PI,
  14. false);
  15. // 绘制分针
  16. context.moveTo(100, 100);
  17. context.lineTo(100, 15);
  18. // 绘制时针
  19. context.moveTo(100, 100);
  20. context.lineTo(35, 100);
  21. // 描画路径
  22. context.stroke();
  23. }

这个例子使用 arc() 绘制了两个圆形,一个外圆和一个内圆, 以构成表盘的边框。外圆半径99像素,原点为(100,100),也就是画布 的中心。要绘制完整的圆形,必须从0弧度绘制到2π弧度(使用数学常 量 Math.PI )。而在绘制内圆之前,必须先把路径移动到内圆上的 一点,以避免绘制出多余的线条。第二次调用 arc() 时使用了稍小 一些的半径,以呈现边框效果。然后,再组合运用 moveTo() 和 lineTo() 分别绘制分针和时针。最后一步是调用 stroke()

绘制文本

文本和图像混合也是常见的绘制需求,因此2D绘图上下文还提供 了绘制文本的方法,即 fillText() 和 strokeText() 。这两个 方法都接收4个参数:要绘制的字符串、 x 坐标、 y 坐标和可选的最大像素宽度。
image.png
这些属性都有相应的默认值,因此没必要每次绘制文本时都设置 它们。 fillText() 方法使用 fillStyle 属性绘制文本,而 strokeText() 方法使用 strokeStyle 属性。

fillText填充文本

strokeText描边文本

measureText()

由于绘制文本很复杂,特别是想把文本绘制到特定区域的时候, 因此2D上下文提供了用于辅助确定文本大小的 measureText() 方 法。这个方法接收一个参数,即要绘制的文本,然后返回一个 TextMetrics 对象。这个返回的对象目前只有一个属性 width , 不过将来应该会增加更多度量指标。 measureText() 方法使用 font 、 textAlign 和 textBaseline 属性当前的值计算绘制指定文本后的大小。

变换

上下文变换可以操作绘制在画布上的图像。2D绘图上下文支持所 有常见的绘制变换。在创建绘制上下文时,会以默认值初始化变换矩 阵,从而让绘制操作如实应用到绘制结果上。对绘制上下文应用变 换,可以导致以不同的变换矩阵应用绘制操作,从而产生不同的结 果。 以下方法可用于改变绘制上下文的变换矩阵。
image.png

绘制图像

2D绘图上下文内置支持操作图像。如果想把现有图像绘制到画布 上,可以使用 drawImage() 方法。这个方法可以接收3组不同的参 数,并产生不同的结果。最简单的调用是传入一个HTML的 元素,以及表示绘制目标的 x 和 y 坐标,结果是把图像绘制到指定位置。

  1. let image = document.images[0];
  2. context.drawImage(image, 10, 10);

以上代码获取了文本中的第一个图像,然后在画布上的坐标(10, 10)处将它绘制了出来。绘制出来的图像与原来的图像一样大。如果想 改变所绘制图像的大小,可以再传入另外两个参数:目标宽度和目标 高度。这里的缩放只影响绘制的图像,不影响上下文的变换矩阵。

  1. context.drawImage(image, 50, 10, 20, 30);

还可以只把图像绘制到上下文中的一个区域。此时,需要给 drawImage() 提供9个参数:要绘制的图像、源图像 x 坐标、源图像 y 坐标、源图像宽度、源图像高度、目标区域 x 坐标、目标区域 y 坐标、目标区域宽度和目标区域高度。这个重载后的 drawImage() 方法可以实现最大限度的控制

  1. context.drawImage(image, 0, 10, 50, 50, 0, 100,40, 60);

最终,原始图像中只有一部分会绘制到画布上。这一部分从(0, 10)开始,50像素宽、50像素高。而绘制到画布上时,会从(0, 100)开 始,变成40像素宽、60像素高。

阴影

2D上下文可以根据以下属性的值自动为已有形状或路径生成阴 影。
image.png
这些属性都可以通过 context 对象读写。只要在绘制图形或路 径前给这些属性设置好适当的值,阴影就会自动生成。

  1. let context = drawing.getContext("2d");
  2. // 设置阴影
  3. context.shadowOffsetX = 5;
  4. context.shadowOffsetY = 5;
  5. context.shadowBlur = 4;
  6. context.shadowColor = "rgba(0, 0, 0, 0.5)";
  7. // 绘制红色矩形
  8. context.fillStyle = "#ff0000";
  9. context.fillRect(10, 10, 50, 50);
  10. // 绘制蓝色矩形
  11. context.fillStyle = "rgba(0,0,255,1)";
  12. context.fillRect(30, 30, 50, 50);

渐变 CanvasGradient

渐变通过 CanvasGradient 的实例表示,在2D上下文中创建和 修改都非常简单。要创建一个新的线性渐变,可以调用上下文的 createLinearGradient() 方法。这个方法接收4个参数:起点 x 坐标、起点 y 坐标、终点 x 坐标和终点 y 坐标。调用之后,该方法会以指定大小创建一个新的 CanvasGradient 对象并返回实例。

有了 gradient 对象后,接下来要使用 addColorStop() 方 法为渐变指定色标。这个方法接收两个参数:色标位置和CSS颜色字 符串。色标位置通过0~1范围内的值表示,0是第一种颜色,1是最后 一种颜色。

  1. let gradient = context.createLinearGradient(30,30, 70, 70);
  2. gradient.addColorStop(0,"white");
  3. gradient.addColorStop(1,"black");

这个 gradient 对象现在表示的就是在画布上从(30, 30)到(70, 70)绘制一个渐变。渐变的起点颜色为白色,终点颜色为黑色。可以把 这个对象赋给 fillStyle 或 strokeStyle 属性,从而以渐变填 充或描画绘制的图形

  1. // 绘制红色矩形
  2. context.fillStyle = "#ff0000";
  3. context.fillRect(10, 10, 50, 50);
  4. // 绘制渐变矩形
  5. context.fillStyle = gradient;
  6. context.fillRect(30, 30, 50, 50);

图案

图案是用于填充和描画图形的重复图像。要创建新图案,可以调 用 createPattern() 方法并传入两个参数:一个HTML 元素和一个表示该如何重复图像的字符串。第二个参数的值与CSS的 background-repeat 属性是一样的,包 括 “repeat” 、 “repeat-x” 、 “repeat-y” 和 “no-repeat” 。

  1. let image = document.images[0],
  2. pattern = context.createPattern(image,
  3. "repeat");
  4. // 绘制矩形
  5. context.fillStyle = pattern;
  6. context.fillRect(10, 10, 150, 150);

跟渐变一样,图案的起点实际上是画布的原点(0, 0)。将填 充样式设置为图案,表示在指定位置而不是开始绘制的位置显示图 案。

图像数据

getImageData()

返回 ImageData 对象,该对象为画布上指定矩形区域的复制像素数据。
2D上下文中比较强大的一种能力是可以使用 getImageData() 方法获取原始图像数据。这个方法接收4个参数:要取得数据中第一个 像素的左上角坐标和要取得的像素宽度及高度。例如,要从(10, 5)开 始取得50像素宽、50像素高的区域对应的数据

  1. let imageData = context.getImageData(10, 5, 50,50);

返回的对象是一个 ImageData 的实例。每个 ImageData 对象 都包含3个属性: width 、 height 和 data ,其中, data 属性 是包含图像的原始像素信息的数组。每个像素在 data 数组中都由4 个值表示,分别代表红、绿、蓝和透明度值。换句话说,第一个像素 的信息包含在第0到第3个值中

  • R - 红色(0-255)
  • G - 绿色(0-255)
  • B - 蓝色(0-255)
  • A - alpha 通道(0-255; 0 是透明的,255 是完全可见的)
    1. let data = imageData.data,
    2. red = data[0],
    3. green = data[1],
    4. blue = data[2],
    5. alpha = data[3];
    这个数组中的每个值都在0~255范围内(包括0和255)。对原始 图像数据进行访问可以更灵活地操作图像。例如,通过更改图像数据 可以创建一个简单的灰阶过滤器 ```javascript let drawing = document.getElementById(“drawing”); // 确保浏览器支持 if (drawing.getContext) { let context = drawing.getContext(“2d”), image = document.images[0], imageData, data, i, len, average, red, green, blue, alpha; // 绘制图像 context.drawImage(image, 0, 0); // 取得图像数据 imageData = context.getImageData(0, 0, image.width, image.height); data = imageData.data; for (i=0, len=data.length; i < len; i+=4) { red = data[i]; green = data[i+1]; blue = data[i+2]; alpha = data[i+3]; // 取得RGB平均值 average = Math.floor((red + green + blue) / 3); // 设置颜色,不管透明度 data[i] = average; data[i+1] = average; data[i+2] = average; } // 将修改后的数据写回ImageData并应用到画布上显示出来 imageData.data = data; context.putImageData(imageData, 0, 0); }
  1. 这个例子首先在画布上绘制了一个图像,然后又取得了其图像数 据。 for 循环遍历了图像数据中的每个像素,注意每次循环都要给 i 加上4。每次循环中取得红、绿、蓝的颜色值,计算出它们的平均 值。然后再把原来的值修改为这个平均值,实际上相当于过滤掉了颜 色信息,只留下类似亮度的灰度信息。之后将 data 数组重写回 imageData 对象。最后调用 putImageData() 方法,把图像数据 再绘制到画布上。结果就得到了原始图像的黑白版。 当然,灰阶过滤只是基于原始像素值可以实现的其中一种操作。
  2. <a name="L5M1g"></a>
  3. #### createImageData()
  4. 创建新的、空白的 ImageData 对象
  5. <a name="ufI9s"></a>
  6. #### putImageData()
  7. 把图像数据(从指定的 ImageData 对象)放回画布上
  8. <a name="swwk2"></a>
  9. ## toDataURL() 导出图像
  10. 可以使用 toDataURL() 方法导出 元素上的图像。 这个方法接收一个参数:要生成图像的MIME类型(与用来创建图形 的上下文无关)。例如,要从画布上导出一张PNG格式的图片,可以 这样做:
  11. ```javascript
  12. let drawing = document.getElementById("drawing");
  13. // 确保浏览器支持<canvas>
  14. if (drawing.getContext) {
  15. // 取得图像的数据URI
  16. let imgURI = drawing.toDataURL("image/png");
  17. // 显示图片
  18. let image = document.createElement("img");
  19. image.src = imgURI;
  20. document.body.appendChild(image);
  21. }

浏览器默认将图像编码为PNG格式,除非另行指定。Firefox和 Opera还支持传入 “image/jpeg” 进行JPEG编码。因为这个方法是 后来才增加到规范中的,所以支持的浏览器也是在后面的版本实现 的,包括IE9、Firefox 3.5和Opera 10。