基本画布功能

  • 基本

    • 设置width和height

    • 使用getContext()方法,传入“2d”获取绘图上下文引用

    • 使用时最好先测试一下getContext是否存在

    • 示例

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      //html
      <canvas id="drawing" width="200" height="200">A drawing of something.</canvas>

      //js
      let drawing = document.getElementById("drawing");
      // 确保浏览器支持<canvas>
      if (drawing.getContext) {
      let context = drawing.getContext("2d");
      // 其他代码
      }
  • toDataURL()方法导出canvas元素上的图像,这个方法接收一个参数:要生成的图像的MIME类型

    • 示例

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      let drawing = document.getElementById("drawing"); 
      // 确保浏览器支持<canvas>
      if (drawing.getContext) {
      // 取得图像的数据 URI
      let imgURI = drawing.toDataURL("image/png");
      // 显示图片
      let image = document.createElement("img");
      image.src = imgURI;
      document.body.appendChild(image);
      }
      浏览

绘图上下文

填充和描边

  • 2d上下文有两个基本绘制操作:填充fillStyle和描边strokeStyle

  • 示例

    1
    2
    3
    4
    5
    6
    7
    let drawing = document.getElementById("drawing"); 
    // 确保浏览器支持<canvas>
    if (drawing.getContext) {
    let context = drawing.getContext("2d");
    context.strokeStyle = "red";
    context.fillStyle = "#0000ff";
    }

    所有与描边和填充相关的操作都会使用这两种样式,除非再次修改。

绘制矩形

  • 矩形是一个唯一可以直接在2d绘图上下文中绘制的形状

  • 与绘制矩形相关的三个方法,这些方法接收4个参数:矩形x坐标、矩形y坐标、矩形宽度、矩形高度

    • fillRect()绘制矩形内容区

      1
      2
      3
      4
      5
      6
      7
      8
      //...同上
      //绘制红色矩形
      context.fillStyle = "#ff0000"
      context.fillRect(10,10,50,50)

      //绘制半透明蓝色矩形
      context.fillStyle = "rgba(0,0,255,0.5)"
      context.fillRect(30, 30, 50, 50);
    • strokeRect() 绘制矩形轮廓

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      // 绘制红色轮廓的矩形
      context.strokeStyle = "#ff0000";
      //绘制
      context.strokeRect(10, 10, 50, 50);

      // 绘制半透明蓝色轮廓的矩形
      context.strokeStyle = "rgba(0,0,255,0.5)";
      //描边宽度 值为任意整数值
      context.lineWidth = 10
      //控制线条端点的形状
      context.lineCap = "butt" //"butt"平头 "round"出圆头 "square"出方头
      //控制线条交点形状
      context.lineJoin = "round" //"round"圆转 "bevel"取平 "miter" 出尖
      //绘制
      context.strokeRect(30, 30, 50, 50);

绘制路径

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

  • 方法

    • arc(x, y, radius, startAngle, endAngle, counterclockwise):以坐标(x, y)为圆心,以 radius 为半径绘制一条弧线,起始角度为 startAngle,结束角度为 endAngle(都是弧度)。最后一个参数 counterclockwise 表示是否逆时针计算起始角度和结束角度(默认为顺时针)。

    • arcTo(x1, y1, x2, y2, radius):以给定半径 radius,经由(x1, y1)绘制一条从上一点到(x2, y2)的弧线。

    • bezierCurveTo(c1x, c1y, c2x, c2y, x, y):以(c1x, c1y)和(c2x, c2y)为控制点,绘制一条从上一点到(x, y)的弧线(三次贝塞尔曲线)。

    • lineTo(x, y):绘制一条从上一点到(x, y)的直线。

    • moveTo(x, y):不绘制线条,只把绘制光标移动到(x, y)。

    • quadraticCurveTo(cx, cy, x, y):以(cx, cy)为控制点,绘制一条从上一点到(x, y)

    的弧线(二次贝塞尔曲线)。

    • rect(x, y, width, height):以给定宽度和高度在坐标点(x, y)绘制一个矩形。这个方法与 strokeRect()和 fillRect()的区别在于,它创建的是一条路径,而不是独立的图形。创建路径之后,可以使用 closePath()方法绘制一条返回起点的线。如果路径已经完成,则既可以指定 fillStyle 属性并调用 fill()方法来填充路径,也可以指定 strokeStyle 属性并调用stroke()方法来描画路径,还可以调用 clip()方法基于已有路径创建一个新剪切区域。
  • 示例,绘制一个不带数字的表盘

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

  • 一个 isPointInPath()方法,接收 x 轴和 y 轴坐标作为参数。这个方法用于确定指定的点是否在路径

    上,可以在关闭路径前随时调用

    1
    2
    3
    if (context.isPointInPath(100, 100)) { 
    alert("Point (100, 100) is in the path.");
    }

绘制文本

  • 绘制文本有fillText()和strokeText()两个方法,接收4个参数:绘制的字符串、x坐标轴、y坐标轴和最大可选像素宽度,而且这两个方法最终绘制的结果都取决于以下3个属性,这些值都有默认值,不需要每次都指定

    • font 以 CSS 语法指定的字体样式、大小、字体族等,比如”10px Arial”。
    • textAlign 可能的值包括”start”、”end”、”left”、”right”和 “center”。
    • textBaseLine 可能的值包括 “top” 、 “hanging” 、 “middle” 、”alphabetic”、”ideographic”和”bottom”
  • 示例

    1
    2
    3
    4
    context.font = "bold 14px Arial"; 
    context.textAlign = "center";
    context.textBaseline = "middle";
    context.fillText("12", 100, 20);

  • 辅助确定文本大小的measureText()方法,该方法接收一个参数,即要绘制的文本,然后返回一个TextMetrics对象,这个返回对象只有一个width属性,measureText()方法使用font、textAlign和textBaseline属性当前的值计算绘制指定文本大小

    • 假设要把文本”Hello world!”放到一个 140 像素宽的矩形中,可以使用以下代码,

      从 100 像素的字体大小开始计算,不断递减,直到文本大小合适:

      1
      2
      3
      4
      5
      6
      7
      let fontSize = 100
      context.font = fontSize + "px Arial"
      while(context.measureText('Hello world!').width > 140){
      fontSize--
      context.font = fontSize + "px Arial"
      }
      context.fillText("Hello world!"1010)

      fillText()和 strokeText()方法还有第四个参数,即文本的最大宽度。这个参数是可选的(Firefox 4 是第一个实现它的浏览器),如果调用 fillText()和 strokeText()时提供了此参数,但要绘制的字符串超出了最大宽度限制,则文本会以正确的字符高度绘制,这时字符会被水平压缩,以达到限定宽度

变换

  • 以下方法可用于改变绘制上下文的变换矩阵。

    • rotate(angle):围绕原点把图像旋转 angle 弧度。

    • scale(scaleX, scaleY):通过在 x 轴乘以 scaleX、在 y 轴乘以 scaleY 来缩放图像。scaleX和 scaleY 的默认值都是 1.0。

    • translate(x, y):把原点移动到(x, y)。执行这个操作后,坐标(0, 0)就会变成(x, y)。

    • transform(m1_1, m1_2, m2_1, m2_2, dx, dy):像下面这样通过矩阵乘法直接修改矩阵。

      m1_1 m1_2 dx

      m2_1 m2_2 dy

      0 0 1

    • setTransform(m1_1, m1_2, m2_1, m2_2, dx, dy):把矩阵重置为默认值,再以传入的参数调用 transform()。

  • 示例,在前面绘制表盘的例子中,如果把坐标原点移动到表盘中心,那再绘制表针就非常简单了:

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

    把原点移动到(100, 100),也就是表盘的中心后,要绘制表针只需简单的数学计算即可。这是因为所有计算都是基于(0, 0),而不是(100, 100)了。当然,也可以使用 rotate()方法来转动表针:

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

  • save() 可以保留变换,调用这个方法后,所有的这一时刻的设置会被放到一个暂存栈中,保存之后,可以继续修改上下文,而需要恢复之前的上下文时,可以调用restore()方法,从暂存栈中取出并恢复之前保存的设置,多次调用 save()方法可以在暂存栈中存储多套设置,然后通过 restore()可以系统地恢复。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    context.fillStyle = "#ff0000"; 
    context.save();
    context.fillStyle = "#00ff00";
    context.translate(100, 100);
    context.save();
    context.fillStyle = "#0000ff";
    context.fillRect(0, 0, 100, 200); // 在(100, 100)绘制蓝色矩形
    context.restore();
    context.fillRect(10, 10, 100, 200); // 在(100, 100)绘制绿色矩形
    context.restore();
    context.fillRect(0, 0, 100, 200); // 在(0, 0)绘制红色矩形

    注意,save()方法只保存应用到绘图上下文的设置和变换,不保存绘图上下文的内容。

绘制图像

  • 使用drawImage()方法可以把现有的图像绘制到画布上,传入3个参数:要绘制的图像、绘制目标的x坐标,y坐标

  • //最简单的调用是传入一个HTML的img元素
    let image = document.images[0]; 
    context.drawImage(image, 10, 10);
    <!--12-->
  • 还可以只把图像绘制到上下文中的一个区域。此时,需要给 drawImage()提供 9 个参数:要绘制的图像、源图像 x 坐标、源图像 y 坐标、源图像宽度、源图像高度、目标区域 x 坐标、目标区域 y 坐标、

    目标区域宽度和目标区域高度。

    1
    context.drawImage(image, 0, 10, 50, 50, 0, 100, 40, 60);
  • 第一个参数除了可以是 HTML 的img元素,还可以是另一个canvas元素,这样就会把另一个画布的内容绘制到当前画布上

  • 操作的结果可以使用toDataURL()方法获取。不过有一种情况例外:如果绘制的图像来自其他域而非当前页面,则不能获取其数据。此时,调用 toDataURL()将抛出错误。比如,如果来自 www.example.com 的页面上绘制的是来自 www.wrox.com 的图像,则上下文就是“脏的”,获取数据时会抛出错误。

阴影

  • 2D 上下文可以根据以下属性的值自动为已有形状或路径生成阴影。

    • shadowColor:CSS 颜色值,表示要绘制的阴影颜色,默认为黑色。

    • shadowOffsetX:阴影相对于形状或路径的 x 坐标的偏移量,默认为 0。

    • shadowOffsetY:阴影相对于形状或路径的 y 坐标的偏移量,默认为 0。

    • shadowBlur:像素,表示阴影的模糊量。默认值为 0,表示不模糊。

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

渐变

基础渐变

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

  • 有了 gradient 对象后,接下来要使用 addColorStop()方法为渐变指定色标。这个方法接收两

    个参数:色标位置和 CSS 颜色字符串。色标位置通过 0~1 范围内的值表示,0 是第一种颜色,1 是最后

    一种颜色

  • 示例

    1
    2
    3
    4
    一种颜色。比如:
    let gradient = context.createLinearGradient(30, 30, 70, 70);
    gradient.addColorStop(0, "white");
    gradient.addColorStop(1, "black");

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

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

    保持渐变与形状的一致非常重要,有时候可能需要写个函数计算相应的坐标

    1
    2
    3
    function createRectLinearGradient(context, x, y, width, height) { 
    return context.createLinearGradient(x, y, x+width, y+height);
    }

    这个函数会基于起点的 xy 坐标和传入的宽度、高度创建渐变对象,之后调用 fillRect()方法时可以使用相同的值:

    1
    2
    3
    4
    5
    6
    let gradient = createRectLinearGradient(context, 30, 30, 50, 50); 
    gradient.addColorStop(0, "white");
    gradient.addColorStop(1, "black");
    // 绘制渐变矩形
    context.fillStyle = gradient;
    context.fillRect(30, 30, 50, 50);

径向渐变

  • 径向渐变(或放射性渐变)要使用 createRadialGradient()方法来创建。这个方法接收 6 个参数,分别对应两个圆形圆心的坐标和半径。前 3 个参数指定起点圆形中心的 xy 坐标和半径,后 3 个参数指定终点圆形中心的 xy 坐标和半径。

    1
    2
    3
    4
    5
    6
    7
    8
    let gradient = context.createRadialgradient.addColorStop(0, "white"); 
    gradient.addColorStop(1, "black");
    // 绘制红色矩形
    context.fillStyle = "#ff0000";
    context.fillRect(10, 10, 50, 50);
    // 绘制渐变矩形
    context.fillStyle = gradient;
    context.fillRect(30, 30, 50, 50);

图案

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

    1
    2
    3
    4
    5
    6
    let image = document.images[0], 
    //传给 createPattern()方法的第一个参数也可以是<video>元素或者另一个<canvas>元素。
    pattern = context.createPattern(image, "repeat");
    // 绘制矩形
    context.fillStyle = pattern;
    context.fillRect(10, 10, 150, 150);

图像数据

  • 2D 上下文中比较强大的一种能力是可以使用 getImageData()方法获取原始图像数据。

    1
    2
    3
    4
    5
    6
    7
    let imageData = context.getImageData(10, 5, 50, 50);
    //其中,data 属性是包含图像的原始像素信息的数组。每个像素在 data 数组中都由 4 个值表示,分别代表红、绿、蓝和透明度值。换句话说,第一个像素的信息包含在第 0 到第 3 个值中,比如:
    let data = imageData.data,
    red = data[0],
    green = data[1],
    blue = data[2],
    alpha = data[3];

    这个数组中的每个值都在 0~255 范围内(包括 0 和 255)。对原始图像数据进行访问可以更灵活地操作图像。例如,通过更改图像数据可以创建一个简单的灰阶过滤器:

    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
    let drawing = document.getElementById("drawing"); 
    // 确保浏览器支持<canvas>
    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);
    }

    这个例子首先在画布上绘制了一个图像,然后又取得了其图像数据。for 循环遍历了图像数据中的每个像素,注意每次循环都要给 i 加上 4。每次循环中取得红、绿、蓝的颜色值,计算出它们的平均值。然后再把原来的值修改为这个平均值,实际上相当于过滤掉了颜色信息,只留下类似亮度的灰度信息。之后将 data 数组重写回 imageData 对象。最后调用 putImageData()方法,把图像数据再绘制到画布上。结果就得到了原始图像的黑白版。

    注意:只有在画布没有加载跨域内容时才可以获取图像数据。如果画布上绘制的是跨域内
    容,则尝试获取图像数据会导致 JavaScript 报错。

合成

  • globalAlpha 属性是一个范围在 0~1 的值(包括 0 和 1),用于指定所有绘制内容的透明度,默认值为 0。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    // 绘制红色矩形
    context.fillStyle = "#ff0000";
    context.fillRect(10, 10, 50, 50);
    // 修改全局透明度
    context.globalAlpha = 0.5;
    // 绘制蓝色矩形
    context.fillStyle = "rgba(0,0,255,1)";
    context.fillRect(30, 30, 50, 50);
    // 重置
    context.globalAlpha = 0;
  • globalCompositionOperation 属性表示新绘制的形状如何与上下文中已有的形状融合。

    • source-over:默认值,新图形绘制在原有图形上面。

    • source-in:新图形只绘制出与原有图形重叠的部分,画布上其余部分全部透明。

    • source-out:新图形只绘制出不与原有图形重叠的部分,画布上其余部分全部透明。

    • source-atop:新图形只绘制出与原有图形重叠的部分,原有图形不受影响。

    • destination-over:新图形绘制在原有图形下面,重叠部分只有原图形透明像素下的部分可见。

    • destination-in:新图形绘制在原有图形下面,画布上只剩下二者重叠的部分,其余部分完全透明。

    • destination-out:新图形与原有图形重叠的部分完全透明,原图形其余部分不受影响。

    • destination-atop:新图形绘制在原有图形下面,原有图形与新图形不重叠的部分完全透明。

    • lighter:新图形与原有图形重叠部分的像素值相加,使该部分变亮。

    • copy:新图形将擦除并完全取代原有图形。

    • xor:新图形与原有图形重叠部分的像素执行“异或”计算。