Canvas 02

canvas画布栅格

默认canvas画布就一个高150px,宽300px空间区域。 我们说的网格中一个单位就相当于一个像素,我们可以把他看成一个 二维空间坐标系,以左上角为原点,所有元素的位置都是相对于原点进行 定位。我们可以平移原点,旋转,缩放我们的canvas栅格。

canvas图像绘制

不同于SVG,canvas只支持两种形式的图形绘制:矩形和路径。

矩形绘制 fillRect(x,y,width,height) 绘制一个填充矩形 strokeRect(x,y,width,height) 绘制一个矩形边框 clearRect(x,y,width,height) 清除指定矩形区域,使清除部分完全透明。(类似橡皮擦)

x,y 相当我们指定相对原点位置绘制矩形,width、height就是为矩形绘制宽高

function draw() {
  const canvas = document.getElementById("canvas");
  if (canvas.getContext) {
    const ctx = canvas.getContext("2d");

    ctx.fillRect(25, 25, 100, 100);
    ctx.clearRect(45, 45, 60, 60);
    ctx.strokeRect(50, 50, 50, 50);
  }
}

不同于路径函数,这三个矩形函数执行完之后,图像会立刻在canvas画布上生效。

绘制路径

路径就是一系列点的集合。图形的基本元素也是路径,路径绘制图形需要注意

  • 创建路径起始点
  • 使用命令画出路径
  • 封闭路径
  • 使用描边或者填充函数进行图形渲染

beginPath() —— 新建路径 closePath() —— 闭合路径 strock() —— 只绘制图形的轮廓 fill() —— 填充图形

路径本质上是由很多子路径构成的,我们可以认为一组子路径都是在一个列表中,由其构成我们的图形。beginPath调用列表就会清空,我们就可以绘制新的图形了。 closePath函数调用会自动绘制一条从当前点到起始点的直线来闭合图形。

这里我们需要注意的是fill调用时,会自动闭合图形,stroke函数调用不会自动闭合图形。

三角形绘制
function draw() {
  var canvas = document.getElementById("canvas");
  if (canvas.getContext) {
    var ctx = canvas.getContext("2d");

    ctx.beginPath();
    ctx.moveTo(75, 50);
    ctx.lineTo(100, 75);
    ctx.lineTo(100, 25);
    ctx.fill();
  }
}

moveTo(x,y) —— 移动绘制点(画笔的移动) 笑脸绘制

function draw() {
  var canvas = document.getElementById("canvas");
  if (canvas.getContext) {
    var ctx = canvas.getContext("2d");

    ctx.beginPath();
    ctx.arc(75, 75, 50, 0, Math.PI * 2, true); // 绘制大圆脸
    ctx.moveTo(110, 75); // 鼠标移动到小嘴绘制右边点 避免出现多余线条
    ctx.arc(75, 75, 35, 0, Math.PI, false); // 口 (顺时针)
    ctx.moveTo(65, 65); //避免出现多余线条
    ctx.arc(60, 65, 5, 0, Math.PI * 2, true); // 左眼
    ctx.moveTo(95, 65);
    ctx.arc(90, 65, 5, 0, Math.PI * 2, true); // 右眼
    ctx.stroke();
  }
}

lineTo(x,y) —— 直线绘制 起始点就是当前路径绘制的结束点,或者可以手动moveTo移动起始点

function draw() {
  var canvas = document.getElementById("canvas");
  if (canvas.getContext) {
    var ctx = canvas.getContext("2d");

    // 填充三角形
    ctx.beginPath();
    ctx.moveTo(25, 25);
    ctx.lineTo(105, 25);
    ctx.lineTo(25, 105);
    ctx.fill();

    // 描边三角形
    ctx.beginPath();
    ctx.moveTo(125, 125);
    ctx.lineTo(125, 45);
    ctx.lineTo(45, 125);
    ctx.closePath();
    ctx.stroke();
  }
}

两种三角形绘制步骤有所不同,正是我们前面提到fill填充函数会自动调用closePath进行图形路径闭合,而stroke方式不会自动闭合,若没有调用closePath,则只会绘制两条线。

arc(x,y,radius,startAngle,endAngle,anticlockwise=false) —— 圆弧或圆的绘制 x y 为圆心 radius为半径 开始弧度和结束弧度(x轴为基准),最后一个参数为绘制圆弧方向,默认为顺时针。

arcTo(x1, y1, x2, y2, radius) —— 给定控制点和半径画一段圆弧,再以直线连接两个控制点

arc() 函数中表示角的单位是弧度,不是角度。角度与弧度的 js 表达式: 弧度=(Math.PI/180)*角度。

function draw() {
  var canvas = document.getElementById("canvas");
  if (canvas.getContext) {
    var ctx = canvas.getContext("2d");

    for (var i = 0; i < 4; i++) {
      for (var j = 0; j < 3; j++) {
        ctx.beginPath();
        var x = 25 + j * 50; // x 坐标值
        var y = 25 + i * 50; // y 坐标值
        var radius = 20; // 圆弧半径
        var startAngle = 0; // 开始点
        var endAngle = Math.PI + (Math.PI * j) / 2; // 结束点
        var anticlockwise = i % 2 == 0 ? false : true; // 顺时针或逆时针

        ctx.arc(x, y, radius, startAngle, endAngle, anticlockwise);

        if (i > 1) {
          ctx.fill();
        } else {
          ctx.stroke();
        }
      }
    }
  }
}

二次贝塞尔曲线以及三次贝塞尔曲线(以下都是介绍路径类型)

quadraticCurveTo(cp1x, cp1y, x, y) —— 二次贝塞尔曲线 cp1x,cp1y为控制点, x,y为结束点。 bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y)—— 三次贝塞尔曲线 cp1x,cp1y为控制点一,cp2x,cp2y为控制点二,x,y为结束点。 二次贝塞尔曲线和三次贝塞尔曲线使用有一定难度,因为我们没法直接观察预测图形的变化。通过例子进行实战和熟悉。

这个例子使用多个二次贝塞尔曲线绘制对话气泡。

function draw() {
  var canvas = document.getElementById("canvas");
  if (canvas.getContext) {
    var ctx = canvas.getContext("2d");

    // 二次贝塞尔曲线
    ctx.beginPath();
    ctx.moveTo(75, 25);
    ctx.quadraticCurveTo(25, 25, 25, 62.5);
    ctx.quadraticCurveTo(25, 100, 50, 100);
    ctx.quadraticCurveTo(50, 120, 30, 125);
    ctx.quadraticCurveTo(60, 120, 65, 100);
    ctx.quadraticCurveTo(125, 100, 125, 62.5);
    ctx.quadraticCurveTo(125, 25, 75, 25);
    ctx.stroke();
  }
}

使用三次贝塞尔曲线绘制心形

function draw() {
  var canvas = document.getElementById("canvas");
  if (canvas.getContext) {
    var ctx = canvas.getContext("2d");

    //三次贝塞尔曲线
    ctx.beginPath();
    ctx.moveTo(75, 40);
    ctx.bezierCurveTo(75, 37, 70, 25, 50, 25);
    ctx.bezierCurveTo(20, 25, 20, 62.5, 20, 62.5);
    ctx.bezierCurveTo(20, 80, 40, 102, 75, 120);
    ctx.bezierCurveTo(110, 102, 130, 80, 130, 62.5);
    ctx.bezierCurveTo(130, 62.5, 130, 25, 100, 25);
    ctx.bezierCurveTo(85, 25, 75, 37, 75, 40);
    ctx.fill();
  }
}
矩形路径

rect(x, y, width, height) —— 相对原点坐标为x,y,对应宽高的矩形。

执行该方法时,自动调用moveTo方法将坐标相对参数设置为原点(0,0) 组合使用路径绘制例子

function draw() {
  var canvas = document.getElementById("canvas");
  if (canvas.getContext) {
    var ctx = canvas.getContext("2d");

    roundedRect(ctx, 12, 12, 150, 150, 15);
    roundedRect(ctx, 19, 19, 150, 150, 9);
    roundedRect(ctx, 53, 53, 49, 33, 10);
    roundedRect(ctx, 53, 119, 49, 16, 6);
    roundedRect(ctx, 135, 53, 49, 33, 10);
    roundedRect(ctx, 135, 119, 25, 49, 10);

    ctx.beginPath();
    ctx.arc(37, 37, 13, Math.PI / 7, -Math.PI / 7, false);
    ctx.lineTo(31, 37);
    ctx.fill();

    for (var i = 0; i < 8; i++) {
      ctx.fillRect(51 + i * 16, 35, 4, 4);
    }

    for (i = 0; i < 6; i++) {
      ctx.fillRect(115, 51 + i * 16, 4, 4);
    }

    for (i = 0; i < 8; i++) {
      ctx.fillRect(51 + i * 16, 99, 4, 4);
    }

    ctx.beginPath();
    ctx.moveTo(83, 116);
    ctx.lineTo(83, 102);
    ctx.bezierCurveTo(83, 94, 89, 88, 97, 88);
    ctx.bezierCurveTo(105, 88, 111, 94, 111, 102);
    ctx.lineTo(111, 116);
    ctx.lineTo(106.333, 111.333);
    ctx.lineTo(101.666, 116);
    ctx.lineTo(97, 111.333);
    ctx.lineTo(92.333, 116);
    ctx.lineTo(87.666, 111.333);
    ctx.lineTo(83, 116);
    ctx.fill();

    ctx.fillStyle = "white";
    ctx.beginPath();
    ctx.moveTo(91, 96);
    ctx.bezierCurveTo(88, 96, 87, 99, 87, 101);
    ctx.bezierCurveTo(87, 103, 88, 106, 91, 106);
    ctx.bezierCurveTo(94, 106, 95, 103, 95, 101);
    ctx.bezierCurveTo(95, 99, 94, 96, 91, 96);
    ctx.moveTo(103, 96);
    ctx.bezierCurveTo(100, 96, 99, 99, 99, 101);
    ctx.bezierCurveTo(99, 103, 100, 106, 103, 106);
    ctx.bezierCurveTo(106, 106, 107, 103, 107, 101);
    ctx.bezierCurveTo(107, 99, 106, 96, 103, 96);
    ctx.fill();

    ctx.fillStyle = "black";
    ctx.beginPath();
    ctx.arc(101, 102, 2, 0, Math.PI * 2, true);
    ctx.fill();

    ctx.beginPath();
    ctx.arc(89, 102, 2, 0, Math.PI * 2, true);
    ctx.fill();
  }
}

// 封装的一个用于绘制圆角矩形的函数。

function roundedRect(ctx, x, y, width, height, radius) {
  ctx.beginPath();
  ctx.moveTo(x, y + radius);
  ctx.lineTo(x, y + height - radius);
  ctx.quadraticCurveTo(x, y + height, x + radius, y + height);
  ctx.lineTo(x + width - radius, y + height);
  ctx.quadraticCurveTo(x + width, y + height, x + width, y + height - radius);
  ctx.lineTo(x + width, y + radius);
  ctx.quadraticCurveTo(x + width, y, x + width - radius, y);
  ctx.lineTo(x + radius, y);
  ctx.quadraticCurveTo(x, y, x, y + radius);
  ctx.stroke();
}

fillStyle 属性 修改填充样式颜色

Path2D 对象

Path2D用于缓存或者记录绘画命令,可以快速回顾路径。 Path2D对象初始化。

new Path2D(); // 空的 Path 对象
new Path2D(path); // 克隆 Path 对象
new Path2D(d); // 从 SVG 建立 Path 对象

所有前面提到的路径方法都可以在Path2D对象上使用。moveTo,rect等。 Path2D.addPath(path [, transform]) —— 添加一条路径当前路径中

function draw() {
  var canvas = document.getElementById("canvas");
  if (canvas.getContext) {
    var ctx = canvas.getContext("2d");

    var rectangle = new Path2D();
    rectangle.rect(10, 10, 50, 50);

    var circle = new Path2D();
    circle.moveTo(125, 35);
    circle.arc(100, 35, 25, 0, 2 * Math.PI);

    ctx.stroke(rectangle);
    ctx.fill(circle);
  }
}

随着新的 Path2D API 产生,几种方法也相应地被更新来使用 Path2D 对象而不是当前路径。带路径参数的stroke和fill可以把对象画在画布上。

使用SVG paths

当前Path2D api可以使用 SVG path data来初始化canvas上的路径。

例子挑战:这条路径将先移动到点 (M10 10) 然后再水平移动 80 个单位(h 80),然后下移 80 个单位 (v 80),接着左移 80 个单位 (h -80),再回到起点处 (z)。

new Path2D("M10 10 h 80 v 80 -80 z")

写在最后

未完待续… 下期讲解canvas中样式和色彩应用。 希望大家好好生活,健康快乐。