一文彻底理解canvas和svg

352 阅读11分钟

canvas

什么是canvas

canvas是一种可供绘图的平面,我们用JavaScript对它进行配置和操作。它很灵活,相对容易使用,并且提供了足够多的功能来替代Flash制作某些类型的富内容。

canvas的语法

canvas在使用时,可以按照以下几个方面去介绍:

准备用来绘图的canvas

在DOM里找到canvas元素,这个canvas只有两个属性:width和height,具体的绘图都是通过js来实现的。然后调用HTMLCanvasObject上的getContext方法

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>canvas</title>
</head>
<body>
  <canvas id="canvas" width="500" height="100">
  </canvas>
  <script>
    let ctx = document.getElementById('canvas').getContext('2d');
    ctx.fillRect(10, 10, 50, 50);
  </script>
</body>
</html>

效果:

image.png

绘制矩形

使用fillRect或strokeRect方法。

成员说明返回
fillRect(x, y, w, h)绘制一个实心矩形void
strokeRect(x, y, w, h)绘制一个空心矩形void

这两个方法都需要四个参数:前两个是从canvas元素左上角算起的偏移量。后两个指定了待绘制矩形的宽度和高度。

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>canvas</title>
</head>
<body>
  <canvas id="canvas" width="500" height="500">
  </canvas>
  <script>
    let ctx = document.getElementById('canvas').getContext('2d');
    let offset = 10;
    let size = 50;
    let count = 5;
    for(let i = 0; i < count; i++) {
      ctx.fillRect(i * (offset + size) + offset, offset, size, size);
      ctx.strokeRect(i * (offset + size) + offset, (2 * offset) + size, size, size);
    }
  </script>
</body>
</html>

效果:

1651567345(1).png

清除矩形

使用clearRect方法。 clearRect也是像绘制矩形一样有四个参数,表示清除指定的矩形。

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>canvas</title>
</head>
<body>
  <canvas id="canvas" width="500" height="500">
  </canvas>
  <script>
    let ctx = document.getElementById('canvas').getContext('2d');
    let offset = 10;
    let size = 50;
    let count = 5;
    for(let i = 0; i < count; i++) {
      ctx.fillRect(i * (offset + size) + offset, offset, size, size);
      ctx.strokeRect(i * (offset + size) + offset, (2 * offset) + size, size, size);
      ctx.clearRect(i * (offset + size) + offset, offset + 5, size, size - 10);
    }
  </script>
</body>
</html>

效果:

1651567655(1).png

设置绘图操作的样式

在执行操作前设置绘制状态属性的(如lineWidth和lineJoin)的值。 绘图操作由绘制状态加以配置。基本的绘制状态有以下几种情况:

名称说明默认值
fillStyle获取或者设置实心图形的样式black
strokeStyle获取或者设置用于线条的样式black
lineJoin获取或者设置线条与图形连接时的样式miter
lineWidth获取或者设置线条的宽度1.0

lineWidth:设置线条宽度

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>canvas</title>
</head>
<body>
  <canvas id="canvas" width="500" height="500">
  </canvas>
  <script>
    let ctx = document.getElementById('canvas').getContext('2d');
    ctx.lineWidth = 2;
    ctx.strokeRect(10, 10, 50, 50);
    ctx.lineWidth = 4;
    ctx.strokeRect(70, 10, 50, 50);
    ctx.lineWidth = 6;
    ctx.strokeRect(130, 10, 50, 50);
    ctx.strokeRect(190, 10, 50, 50);
  </script>
</body>
</html>

效果:

1651572261(1).png

lineJoin: 设置线条连接样式 它有三个值: round, bevel, miter。

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>canvas</title>
</head>
<body>
  <canvas id="canvas" width="500" height="500">
  </canvas>
  <script>
    let ctx = document.getElementById('canvas').getContext('2d');
    ctx.lineWidth = 20;
    ctx.lineJoin = "round";
    ctx.strokeRect(20, 20, 100, 100);
    ctx.lineJoin = "bevel";
    ctx.strokeRect(160, 20, 100, 100);
    ctx.lineJoin = "miter";
    ctx.strokeRect(300, 20, 100, 100);
  </script>
</body>
</html>

效果:

1651572585(1).png

在绘图操作中使用纯色

给fillStyle或strokeStyle属性设置一个颜色值或者名称。 设置填充主要是fillStyle, 设置笔触样式主要是strokeStyle。

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>canvas</title>
</head>
<body>
  <canvas id="canvas" width="500" height="500">
  </canvas>
  <script>
    let ctx = document.getElementById('canvas').getContext('2d');
    let offset = 10;
    let size = 50;
    let count = 5;
    ctx.lineWidth = 3;
    let fillColors = ['black', 'grey', 'lightgrey', 'red', 'blue'];
    let strokeColors = ['rgb(0, 0, 0)', 'rgb(100, 100, 100)', 'rgb(200, 200, 200)', 'rgb(255, 0, 0)', 'rgb(0, 0, 255)'];
    for(let i = 0; i < count; i++) {
      ctx.fillStyle = fillColors[i];
      ctx.strokeStyle = strokeColors[i];
      ctx.fillRect(i * (offset + size) + offset, offset, size, size);
      ctx.strokeRect(i * (offset + size) + offset, (2 * offset) + size, size, size);
    }
  </script>
</body>
</html>

效果:

1651573591(1).png

创建线性渐变

调用createLinearGradient方法,并通过addColorStop方法给渐变添加颜色

用法:

createLinearGradient(x0, y0, x1, y1);  // x0,y0是起始点的坐标,x1,y1是终点的坐标
addColorStop(0, "red");
addColorStop(0.5, "white");
addColorStop(1, "black");

样例:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>canvas</title>
</head>
<body>
  <canvas id="canvas" width="500" height="500">
  </canvas>
  <script>
    let ctx = document.getElementById('canvas').getContext('2d');
    let grad = ctx.createLinearGradient(0, 0, 500, 140);
    grad.addColorStop(0, 'red');
    grad.addColorStop(0.5, 'white');
    grad.addColorStop(1, 'black');
    ctx.fillStyle = grad;
    ctx.fillRect(10, 10, 50, 50);
  </script>
</body>
</html>

效果:

1652512538(1).png 从效果图中我们可以看出渐变的梯度线是相对于画布的意思,因此在画出的矩形中只占据了画布的一部分,因此我们主要看到了红色的颜色过渡。 让梯度线匹配图形:


<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>canvas</title>
</head>
<body>
  <canvas id="canvas" width="500" height="500">
  </canvas>
  <script>
    let ctx = document.getElementById('canvas').getContext('2d');
    let grad = ctx.createLinearGradient(10, 10, 50, 50);
    grad.addColorStop(0, 'red');
    grad.addColorStop(0.5, 'white');
    grad.addColorStop(1, 'black');
    ctx.fillStyle = grad;
    ctx.fillRect(0, 0, 500, 140);
  </script>
</body>
</html>

效果:

1652512895(1).png 很显然这个也不是我们想要的效果,我们想要的是图形匹配渐变

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>canvas</title>
</head>
<body>
  <canvas id="canvas" width="500" height="500">
  </canvas>
  <script>
    let ctx = document.getElementById('canvas').getContext('2d');
    let grad = ctx.createLinearGradient(10, 10, 60, 60);
    grad.addColorStop(0, 'red');
    grad.addColorStop(0.5, 'white');
    grad.addColorStop(1, 'black');
    ctx.fillStyle = grad;
    ctx.fillRect(10, 10, 50, 50);
  </script>
</body>
</html>

效果:

1652513031(1).png

创建径向渐变

调用createRadialGradient方法,并通过addColorStop方法给渐变添加颜色

createRadialGradient(x1, y1, r1, x2, y2, r2);  // x1, y1表示起点圆的圆心坐标,r1表示起点圆的半径。x2, y2表示终点圆的圆心坐标,r2表示终点圆的半径。
grad.addColorStop(0, 'red');
grad.addColorStop(0.5, 'white');
grad.addColorStop(1, 'black');

例子:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>canvas</title>
</head>
<body>
  <canvas id="canvas" width="500" height="500">
  </canvas>
  <script>
    let ctx = document.getElementById('canvas').getContext('2d');
    let grad = ctx.createRadialGradient(250, 70, 20, 200, 60, 100);
    grad.addColorStop(0, 'red');
    grad.addColorStop(0.5, 'white');
    grad.addColorStop(1, 'black');
    ctx.fillStyle = grad;
    ctx.fillRect(0, 0, 500, 140);
  </script>
</body>
</html>

效果:

1652513532(1).png

使用较小的图形和径向渐变

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>canvas</title>
</head>
<body>
  <canvas id="canvas" width="500" height="500">
  </canvas>
  <script>
    let ctx = document.getElementById('canvas').getContext('2d');
    let grad = ctx.createRadialGradient(250, 70, 20, 200, 60, 100);
    grad.addColorStop(0, 'red');
    grad.addColorStop(0.5, 'white');
    grad.addColorStop(1, 'black');
    ctx.fillStyle = grad;
    ctx.fillRect(150, 20, 75, 50);
    ctx.lineWidth = 8;
    ctx.strokeStyle = grad;
    ctx.strokeRect(250, 20, 75, 50);
  </script>
</body>
</html>

效果:

1652513796(1).png

创建图案

调用createPattern方法,指定图案文件的来源和重复方式。 如果将图像作为图案,渲染到canvas上,必须将一个HTMLImageElement对象作为第一个参数传到createPattern方法。第二个参数是重复样式,必须是以下值其中的一个:repeat, repeat-x, repeat-y, no-repeat

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>canvas</title>
</head>
<body>
  <canvas id="canvas" width="500" height="500">
  </canvas>
  <img id="img" src="./flower.JPG" alt="">
  <script>
    let ctx = document.getElementById('canvas').getContext('2d');
    let imgEle = document.getElementById('img');
    let pattern = ctx.createPattern(imgEle, "repeat");
    ctx.fillStyle = pattern;
    ctx.fillRect(150, 20, 75, 50);
    ctx.lineWidth = 8;
    ctx.strokeStyle = pattern;
    ctx.strokeRect(250, 20, 75, 50);
  </script>
</body>
</html>

保存和恢复绘制状态

使用save和restore方法。绘制状态保存时会被存放到一个后进先出的栈里,意思就是我们用save方法最后保存的状态会被restore方法首先进行恢复。

在画布上绘制图像

使用drawImage方法,指定一个img、canvas或video元素作为来源

canvas的应用

canvas除了上面的基础用法之外,它还具有以下用法:

用线条绘制图形

在绘制图形的时候其实都是用到了用路径(path)绘图的方式。 绘制一条路径的基本顺序:

  1. 调用beginPath方法
  2. 用moveTo移动到起始点
  3. 用arc和lineTo等方法绘制子路径
  4. 调用closePath方法(可选)
  5. 调用fill或stroke方法

样例:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>canvas</title>
</head>
<body>
  <canvas id="canvas" width="500" height="500">
  </canvas>
  <script>
    let ctx = document.getElementById('canvas').getContext('2d');
    // 定义绘制的样式
    ctx.fillStyle = 'yellow';
    ctx.strokeStyle = 'black';
    // 第一个图形
    ctx.beginPath();
    ctx.moveTo(10, 10);
    ctx.lineTo(110, 10);
    ctx.lineTo(110, 110);
    ctx.closePath();
    ctx.fill();
    // 第二个图形
    ctx.beginPath();
    ctx.moveTo(200, 10);
    ctx.lineTo(250, 10);
    ctx.lineTo(250, 110);
    ctx.lineTo(230, 110);
    ctx.fill();
    ctx.stroke();
    // 第三个图形
    ctx.beginPath();
    ctx.moveTo(300, 10);
    ctx.lineTo(300, 110);
    ctx.stroke();
  </script>
</body>
</html>

效果:

1652517277(1).png

设置用于绘制线条末端的样式

可以用lineCap属性来设置线条末端的样式,主要有这几种属性值:butt(默认值), round, square

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>canvas</title>
</head>
<body>
  <canvas id="canvas" width="500" height="500">
  </canvas>
  <script>
    let ctx = document.getElementById('canvas').getContext('2d');
    // 绘制基线
    ctx.strokeStyle = 'red';
    ctx.lineWidth = '2';
    ctx.beginPath();
    ctx.moveTo(0, 50);
    ctx.lineTo(200, 50);
    ctx.stroke();
    // 绘制不同线条末端的样式
    ctx.strokeStyle = 'black';
    ctx.lineWidth = 40;
    let xpos = 50;
    let styles = ['butt', 'round', 'square'];
    for(let i = 0; i < styles.length; i++) {
      ctx.beginPath();
      ctx.lineCap = styles[i];
      ctx.moveTo(xpos, 50);
      ctx.lineTo(xpos, 150);
      ctx.stroke();
      xpos += 50;
    }
  </script>
</body>
</html>

效果:

1652517726(1).png

绘制作为路径一部分的矩形

上面已经讲过fillRect()和strokeReact()可以绘制矩形,对于复杂的矩形可以使用react()方法来画,其中第一个参数和第二个参数表示矩形起始点的坐标,第三个参数表示矩形的长,第四个参数表示矩形的宽。 样例:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>canvas</title>
</head>
<body>
  <canvas id="canvas" width="500" height="500">
  </canvas>
  <script>
    let ctx = document.getElementById('canvas').getContext('2d');
    // 绘制样式
    ctx.fillStyle = 'yellow';
    ctx.strokeStyle = 'black';
    ctx.lineWidth = '4';
    // 绘制矩形
    ctx.beginPath();
    ctx.moveTo(110, 10);
    ctx.lineTo(110, 100);
    ctx.lineTo(10, 10);
    ctx.closePath();
    ctx.rect(110, 10, 100, 90);
    ctx.rect(110, 100, 130, 30);
    ctx.fill();
    ctx.stroke();
  </script>
</body>
</html>

效果:

1652518491(1).png

绘制圆弧

绘制圆弧有两个方法:arc()和actTo()

名称说明
arc(x, y, rad, startAngle, endAngle, direction)绘制一段圆弧到(x, y),半径为rad,起始角度为startAngle,结束角度为endAngle,可选参数direction指定了圆弧的方向
arcTo(x1, y1, X2, y2, rad)绘制一段半径为rad,经过(x1, y1),直到(x2, y2)的圆弧

样例:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>canvas</title>
</head>
<body>
  <canvas id="canvas" width="500" height="500">
  </canvas>
  <script>
    let ctx = document.getElementById('canvas').getContext('2d');
    let point1 = [100, 10];
    let point2 = [200, 10];
    let point3 = [200, 110];
    ctx.fillStyle = 'yellow';
    ctx.strokeStyle = 'black';
    ctx.lineWidth = 4;
    // 绘制弧形
    ctx.beginPath();
    ctx.moveTo(point1[0], point1[1]);
    ctx.arcTo(point2[0], point2[1], point3[0], point3[1], 100);
    ctx.stroke()
    drawPoint(point1[0], point1[1]);
    drawPoint(point2[0], point2[1]);
    drawPoint(point3[0], point3[1]);
    // 绘制线
    ctx.beginPath();
    ctx.moveTo(point1[0], point1[1]);
    ctx.lineTo(point2[0], point2[1]);
    ctx.lineTo(point3[0], point3[1]);
    ctx.stroke();
    // 绘制点
    function drawPoint(x, y) {
      ctx.lineWidth = 1;
      ctx.strokeStyle = 'red';
      ctx.strokeRect(x - 2, y - 2, 4, 4);
    }
  </script>
</body>
</html>

效果:

1652535293(1).png

响应鼠标移动绘制圆弧

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>canvas</title>
</head>
<body>
  <canvas id="canvas" width="500" height="500">
  </canvas>
  <script>
    let canvasEle = document.getElementById('canvas');
    let ctx = canvasEle.getContext('2d');
    let point1 = [100, 10];
    let point2 = [200, 10];
    let point3 = [200, 110];
    draw();
    // 监听鼠标事件
    canvasEle.onmouseover = function(e) {
      if(e.ctrlKey) {
        point1 = [e.clientX, e.clientY];
      } else if(e.shiftKey) {
        point2 = [e.clientX, e.clientY];
      } else {
        point3 = [e.clientX, e.clientY];
      }
      ctx.clearRect(0, 0, 540, 140);
      draw();
    }
    // 绘制弧形
    function draw() {
      ctx.fillStyle = 'yellow';
      ctx.strokeStyle = 'black';
      ctx.lineWidth = 4;
      ctx.beginPath();
      ctx.moveTo(point1[0], point1[1]);
      ctx.arcTo(point2[0], point2[1], point3[0], point3[1], 100);
      ctx.stroke()
      drawPoint(point1[0], point1[1]);
      drawPoint(point2[0], point2[1]);
      drawPoint(point3[0], point3[1]);
      // 绘制线
      ctx.beginPath();
      ctx.moveTo(point1[0], point1[1]);
      ctx.lineTo(point2[0], point2[1]);
      ctx.lineTo(point3[0], point3[1]);
      ctx.stroke();
    }
    // 绘制点
    function drawPoint(x, y) {
      ctx.lineWidth = 1;
      ctx.strokeStyle = 'red';
      ctx.strokeRect(x - 2, y - 2, 4, 4);
    }
  </script>
</body>
</html>

效果:

1652536115(1).png

绘制三次或二次贝塞尔曲线

绘制曲线有以下两种方法

名称说明
bezierCurveTo(cx1, cy1, cx2, cy2, x, y)绘制一段贝塞尔曲线到点(x, y),控制点为(cx1, cy1)和(cx2, cy2)
quadraticCurveTo(cx, cy, x, y)绘制一段二次贝塞尔曲线到点(x, y),控制点为(cx, cy)

样例:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>canvas</title>
</head>
<body>
  <canvas id="canvas" width="500" height="500">
  </canvas>
  <script>
    let canvasEle = document.getElementById('canvas');
    let ctx = canvasEle.getContext('2d');
    let startPoint = [50, 100];
    let endPoint = [400, 100];
    let cp1 = [250, 50];
    let cp2 = [350, 50];
    // 监听鼠标事件
    canvasEle.onmouseover = function(e) {
      if(e.ctrlKey) {
        cp1 = [e.clientX, e.clientY];
      } else if(e.shiftKey) {
        cp2 = [e.clientX, e.clientY];
      }
      ctx.clearRect(0, 0, 540, 140);
      draw();
    }
    draw();
    // 绘制弧形
    function draw() {
      ctx.strokeStyle = 'black';
      ctx.lineWidth = 4;
      ctx.beginPath();
      ctx.moveTo(startPoint[0], startPoint[1]);
      ctx.bezierCurveTo(cp1[0], cp1[1], cp2[0], cp2[1], endPoint[0], endPoint[1]);
      // 绘制二次贝塞尔曲线
      // ctx.quadraticCurveTo(cp1[0], cp1[1], endPoint[0], endPoint[1]);
      ctx.stroke();
      ctx.lineWidth = 1;
      ctx.strokeStyle = 'red';
      let points = [startPoint, endPoint, cp1, cp2];
      for(let i = 0; i < points.length; i++) {
        drawPoint(points[i]);
      }
      drawLine(startPoint, cp1);
      drawLine(endPoint, cp2);
    }
    // 绘制点
    function drawPoint(point) {
      ctx.beginPath();
      ctx.strokeRect(point[0] - 2, point[1] - 2, 4, 4);
    }
    // 绘制线
    function drawLine(from, to) {
      ctx.beginPath();
      ctx.moveTo(from[0], from[1]);
      ctx.lineTo(to[0], to[1]);
      ctx.stroke();
    }
  </script>
</body>
</html>

效果:

1652579529(1).png

把绘图操作的效果限制在画布特定的区域内

前面已经介绍过,fill和stroke方法可以用来绘制或填充一条路径。使用裁剪的方法clip也可以将裁剪区域的路径才会显示出来。 样例:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>canvas</title>
</head>
<body>
  <canvas id="canvas" width="500" height="500">
  </canvas>
  <script>
    let canvasEle = document.getElementById('canvas');
    let ctx = canvasEle.getContext('2d');
    // 绘制第一个矩形
    ctx.fillStyle = 'yellow';
    ctx.beginPath();
    ctx.rect(0, 0, 500, 140);
    ctx.fill();
    // 裁剪区域
    ctx.beginPath();
    ctx.rect(100, 20, 300, 100);
    ctx.clip();
    // 绘制第二个矩形
    ctx.fillStyle = 'red';
    ctx.beginPath();
    ctx.rect(0, 0, 500, 140);
    ctx.fill();
  </script>
</body>
</html>

效果: 第二个矩形只会显示在裁剪区域内,这样也能实现把绘图操作的效果限制在画布特定的区域内。

1652580276(1).png

在画布上绘制文本

在画布上绘制文本有两个方法:fillText和strokeText.

名称说明
fillText(text, x, y, width)在位置(x, y)上绘制并填充指定文本。宽度参数是可选的,它设置了文本宽度的上限
strokeText(text, x, y, width)在位置(x, y)上绘制并描边指定文本。宽度参数是可选的,它设置了文本宽度的上限

可以用三种绘制状态属性来控制文本绘制的方式。

名称说明
font设置绘制文本使用的字体
textAlign设置绘制文本的对齐方式:start, end, left, right, center
textBaseline设置绘制文本的基线:top, hanging, middle, alphabetic, ideographic, bottom

样例:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>canvas</title>
</head>
<body>
  <canvas id="canvas" width="500" height="500">
  </canvas>
  <script>
    let canvasEle = document.getElementById('canvas');
    let ctx = canvasEle.getContext('2d');
    ctx.fillStyle = 'lightgrey';
    ctx.strokeStyle = 'black';
    ctx.lineWidth = 3;
    ctx.font = '100px sans-serif';
    ctx.fillText('Hello', 50, 100);
    ctx.strokeText('Hello', 50, 100);
  </script>
</body>
</html>

效果:

1652581330(1).png

给文本或图形添加阴影

可以用四种绘制状态属性来给我们在画布上绘制的图形和文本添加阴影。

名称说明
shadowBlur设置阴影的模糊程度
shadowColor设置阴影的颜色
shadowOffsetX设置阴影的水平偏移量
shadowOffsetY设置阴影的垂直偏移量

样例:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>canvas</title>
</head>
<body>
  <canvas id="canvas" width="500" height="500">
  </canvas>
  <script>
    let canvasEle = document.getElementById('canvas');
    let ctx = canvasEle.getContext('2d');
    ctx.fillStyle = 'lightgrey';
    ctx.strokeStyle = 'black';
    ctx.lineWidth = 3;
    ctx.shadowOffsetX = 5;
    ctx.shadowOffsetY = 5;
    ctx.shadowBlur = 5;
    ctx.shadowColor = 'grey';
    ctx.strokeRect(270, 20, 100, 100);
    ctx.beginPath();
    ctx.arc(440, 70, 50, 0, Math.PI, true);
    ctx.stroke();
    ctx.beginPath();
    ctx.arc(440, 80, 40, 0, Math.PI, false);
    ctx.fill();
    ctx.font = '100px sans-serif';
    ctx.fillText('Hello', 10, 100);
    ctx.strokeText('Hello', 10, 100);
  </script>
</body>
</html>

效果:

1652583565(1).png

设置合成样式

可以将透明度和globalCompositeOperation属性结合使用,来控制图形和文本在画布上绘制的方式,globalCompositeOperation属性有以下几种属性值。

b1fb85f0128a9670cebc1a743e1a774.jpg 样例:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>canvas</title>
</head>
<body>
  <canvas id="canvas" width="500" height="500">
  </canvas>
  <select id="list">
    <option>destination-atop</option>
    <option>destination-in</option>
    <option>destination-over</option>
    <option>destination-out</option>
    <option>lighter</option>
    <option>source-atop</option>
    <option>source-in</option>
    <option>source-out</option>
    <option>source-over</option>
    <option>xor</option>
  </select>
  <script>
    let canvasEle = document.getElementById('canvas');
    let ctx = canvasEle.getContext('2d');
    ctx.fillStyle = 'lightgrey';
    ctx.strokeStyle = 'black';
    ctx.lineWidth = 3;
    let compVal = 'copy';
    document.getElementById('list').onchange = function(e) {
      compVal = e.target.value;
      draw();
    }
    draw();
    function draw() {
      ctx.clearRect(0, 0, 300, 120);
      ctx.globalAlpha = 1.0;
      ctx.font = '100px sans-serif';
      ctx.fillText('Hello', 10, 100);
      ctx.strokeText('Hello', 10, 100);
      ctx.globalCompositeOperation = compVal;
      ctx.fillStyle = 'red';
      ctx.globalAlpha = 0.5;
      ctx.fillRect(100, 10, 150, 100);
    }
  </script>
</body>
</html>

效果:

1652584588(1).png

变换画布

我们可以给画布应用变换,它会应用到后续所有的绘图操作上。

bd00ac91c442636fd85db17e47a7cca.jpg 样例:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>canvas</title>
</head>
<body>
  <canvas id="canvas" width="500" height="500">
  </canvas>
  <script>
    let canvasEle = document.getElementById('canvas');
    let ctx = canvasEle.getContext('2d');
    ctx.fillStyle = 'lightgrey';
    ctx.strokeStyle = 'black';
    ctx.lineWidth = 3;
    ctx.clearRect(0, 0, 300, 120);
    ctx.globalAlpha = 1.0;
    ctx.font = '100px sans-serif';
    ctx.fillText('Hello', 10, 100);
    ctx.strokeText('Hello', 10, 100);
    ctx.scale(1.3, 1.3);
    ctx.translate(100, -50);
    ctx.rotate(0.5);
    ctx.fillStyle = 'red';
    ctx.globalAlpha = 0.5;
    ctx.fillRect(100, 10, 150, 100);
    ctx.strokeRect(0, 0, 300, 200);
  </script>
</body>
</html>

效果:

1652585350(1).png

svg

什么是svg

SVG是一种图像文件格式,它的英文全称为Scalable Vector Graphics,意思为可缩放的矢量图形。它是基于XML(Extensible Markup Language),由World Wide Web Consortium(W3C)联盟进行开发的。严格来说应该是一种开放标准的矢量图形语言,本质上是文本文件,体积较小,放大多少都不会失真。用户可以直接用代码来描绘图像,可以用任何文字处理工具打开SVG图像,通过改变部分代码来使图像具有交互功能,并可以随时插入到HTML中通过浏览器来观看。

svg的语法

笔画特性

属性
stroke笔画颜色,默认值为 none
stroke-width笔画宽度,可用用户坐标或者指定单位的方式指定。笔画的宽度会相对坐标网格 线居中。默认值为 1
stroke-opacity数字,从 0.0 到 1.0。0.0 是完全透明,1.0 是完全不透明(默认值)
stroke-dasharray用一系列的数字来指定虚线和间隙的长度。这些数字只能使用用户坐标,默认值为 none
stroke-linecap线头尾的形状,值为 butt(默认值)、round 或 square
stroke-linejoin图形的棱角或者一系列连线的形状,取值为 miter(尖的,默认值)、round 或者 bevel(平的)
stroke-mite相交处显示宽度与线宽的最大比例,默认值为 4

填充属性

属性
fill填充颜色,默认值为 black
fill-opacity数字,从 0.0 到 1.0。0.0 是完全透明,1.0 是完全不透明(默认值)
fill-rule属性值为 nonzero(默认值)或 evenodd。该属性决定判断某个点是否在图形内部的方法。只有当边线交叉时或者内部有“洞”时才有效

常用标签

图形标签用法描述
矩形< rect ><rect x="60" y="10" rx="10" ry="10" width="30" height="30" />x:起点横坐标,y:起点纵坐标,rx:倒角x 轴方向半径,ry:倒角x轴方向半径,width:宽度,height:高度
圆形< circle ><circle cx="100" cy="100" r="50" />cx:圆心横坐标,cy:圆心纵坐标,r:半径
椭圆< ellipse ><ellipse cx="75" cy="75" rx="20" ry="5" />cx:椭圆心横坐标,cy:椭圆心纵坐标,rx:椭圆x轴方向半径,ry:椭圆y轴方向半径
直线< line ><line x1="10" x2="50" y1="110" y2="150" />x1,y1:起点,x2,y2:终点
折线< polyline >&ltpolyline points="60 110, 65 120, 70 115, 75 130, 80 125, 85 140, 90 135, 95 150, 100 145" />每两个点以空格配对为一个坐标点,逗号隔开形成坐标集合。连成折线
多边形< polygon ><polygon points="50 160, 55 180, 70 180, 60 190, 65 205, 50 195, 35 205, 40 190, 30 180, 45 180"/>类似折线,不同的是,最后一个点会自动闭合第一个点,形成闭环。
路径< path ><path d="M 100 100 L 300 100 L 200 300 z" />通过指定点以及点和点之间的线来创建任意形状。
图片< image ><image xlink:href="./logo.png" x="0" y="0" height="100" width="100" />将图片嵌入到 SVG 中。
文本< text ><text x="250" y="150" font-family="Times New Roman" font-size="55"> Hello SVG </text>一个由文字组成的图形。
  • path
指令参数说明
Mx y将画笔移动到点(x,y)
Lx y画笔从当前的点绘制线段到点(x,y)
Hx画笔从当前的点绘制水平线段到点(x,y0)
Vy画笔从当前的点绘制竖直线段到点(x0,y)
Arx ry x-axis-rotation large-arc-flag sweep-flag x y画笔从当前的点绘制一段圆弧到点(x,y)
Cx1 y1 x2 y2 x y画笔从当前的点绘制一段三次贝塞尔曲线到点(x,y)
Sx2 y2 x y特殊版本的三次贝塞尔曲线(省略第一个控制点)
Qx1 y1 x y绘制二次贝塞尔曲线到点(x,y)
Tx y特殊版本的二次贝塞尔曲线(省略控制点)
Z无参数绘制闭合图形,如果d属性不指定Z命令,则绘制线段,而不是封闭图形

svg的应用

将svg作为图像

具体可以采用两种方法:

  • 将图像包含在 HTML 标记的 元素内(当图像是页面的基本 组成部分时,推荐这种方式);
  • 或者将图像作为另一个元素的 CSS 样式属性插入(当图像主要用来装饰时,推荐这种方式)。

将svg作为应用程序

<object data="cat.svg" type="image/svg+xml" title="Cat Object" alt="Cat"> 
    <p>No SVG support! Here's a substitute:</p>
    <img src="cat.png" title="Cat Fallback" alt="Cat"/>
</object>

将svg作为混合图文

绘制圆弧指令:

A rx ry x-axis-rotation large-arc-flag sweep-flag x y

rx, ry是弧的半长轴、半短轴长度 x-axis-rotation是圆弧旋转角度,是此段弧所在的半长轴与水平方向上的夹角。正数代表顺时针转动的角度。 large-arc-flag为1表示大角度弧线,0代表小角度弧线 sweep-flag为1代表从起点到终点弧线绕中心顺时针方向,0代表逆时针方向。 x,y是弧终端坐标

  • defs 定义一个绘图模板,以便我们在别处直接使用
  • linearGradient 一个渐变上的颜色坡度,是用stop元素定义的
<stop offset="5%" stop-color="#F60" />

引用自己定义的渐变进行填充颜色

stroke="url(#linear)"
  • stroke-dasharray 用于创建虚线 一个参数时表示一段虚线长度和每段虚线之间的间距。 两个参数或者多个参数时:一个表示长度,一个表示间距
stroke-dasharray = '10';
stroke-dasharray = '10, 10';
stroke-dasharray = '10, 10, 5, 5';
  • stroke-dashoffset stroke-dashoffset属性指定了dash模式到路径开始的距离,如果使用了一个百分比值,那么这个值就代表了当前viewport的一个百分比。值可以是负值。

样例:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>svg的动画</title>
  <style>
    path {
      animation: dash 5s linear forwards;
    }
    path:nth-child(1) {
      stroke-dasharray: 1162;
      stroke-dashoffset: -1162;
    }
    path:nth-child(2){
      stroke-dasharray: 236;
      stroke-dashoffset: -236;
    }
    @keyframes dash {
      to {
        stroke-dashoffset: 0;
      }
    }
  </style>
</head>
<body>
  <div>
    <svg id="svgelem" width="500" height="600" xmlns="http://www.w3.org/2000/svg">
      <!-- 苹果形状 -->
      <path
      stroke-linecap="round"
      fill="none"
      stroke-width="10"
      stroke="url(#linear)"
      d="
    M 197,148
    A 87,87,0,0,0,79,214
    A 282,282,0,0,0,148,438
    A 54,54,0,0,0,212,448
    A 87,87,0,0,1,288,448
    A 54,54,0,0,0,352,438
    A 282,282,0,0,0,407,350
    A 87,87,0,0,1,413,189
    A 87,87,0,0,0,303,148
    A 141,141,0,0,1,197,148
    Z
    "
    />
    <!-- 叶子 -->
    <path
      stroke-linecap="round"
      fill="none"
      stroke="url(#linear)"
      stroke-width="10"
      d="
    M 237,141
    A 87,87,0,0,0,314,64
    A 87,87,0,0,0,237,141
    Z
    "
    />
    <defs>
      <linearGradient id="linear" x1="0%" y1="0%" x2="100%" y2="100%">
        <stop offset="0%" stop-color="rgb(98, 180, 76)" />
        <stop offset="20%" stop-color="rgb(242, 179, 61)" />
        <stop offset="40%" stop-color="rgb(238, 125, 55)" />
        <stop offset="60%" stop-color="rgb(205, 58, 71)" />
        <stop offset="80%" stop-color="rgb(142, 61, 140)" />
        <stop offset="100%" stop-color="rgb(39, 155, 213)" />
      </linearGradient>
    </defs>
  </svg>
  </div>
  <script type="text/javascript">
    let pathLength = document.querySelectorAll('path');
    pathLength.forEach((item, index) => {
      let itemLength = Math.ceil(item.getTotalLength());
      console.log(itemLength);
    });
  </script>
</body>
</html>

效果:

1652588288(1).png