canvas从入门到入土

582 阅读6分钟

1.基本介绍

Canvas是一个内置的 HTML5 API,用于构建形状并将其有效地绘制到屏幕上。以下定义来自MDN 网络文档Canvas 允许通过 Javascript 和 HTML元素 绘制图形。 我们可以将其用于动画、游戏图形、数据可视化、照片处理和实时视频处理。

2.基本用法

2.1 canvas画线段

移动画笔(moveTo())

之前我们获得了画笔 context,所以以此为例,给出该方法的使用实例——context.moveTo(100,0)。这句代码的意思是 移动画笔至(100,100)这个点(单位是px) 。记住,这里是以 canvas 画布的左上角为笛卡尔坐标系的原点,且y轴的正方向向下,x轴的正方向向右。

笔画停点(lineTo())

同理,context.lineTo(600,600)。这句的意思是从 上一笔的停止点 绘制到(500,500)这里。不过要清楚,这里的moveTo() lineTo()都只是状态而已,是规划,是我准备要画,还没有开始画,只是一个计划而已!

选择画笔

这里我们暂且只设置一下画笔的颜色和粗细。

context.lineWidth = 5,这句话的意思是设置画笔(线条)的粗细为 5 px。

context.strokeStyle = "red",这句话的意思是设置画笔(线条)的颜色为红色。

确定绘制

确定绘制只有两种方法,fill()stroke(),有点绘画基础的应该知道,前者是指填充,后者是指描边。因为我们只是绘制线条,所以只要描边就可以了。调用代码 context.stroke() 即可。

     //1.构建画布
      const canvas = document.getElementById("canvas");
      canvas.width = 800;
      canvas.height = 600;
      const context = canvas.getContext("2d");
      //2.移动画笔,使画笔移动至绘画的开始处
      context.moveTo(100, 0);
      //3.确定第一笔的停止点
      context.lineTo(500, 500);
      //4.确定好画笔的颜色和线条粗细
      context.lineWidth = 5;
      context.strokeStyle = "red";
      //5.确定绘制
      context.stroke();

线条属性

1、lineCap属性

lineCap 定义上下文中线的端点,可以有以下 3 个值。

  • butt:默认值,端点是垂直于线段边缘的平直边缘。
  • round:端点是在线段边缘处以线宽为直径的半圆。
  • square:端点是在选段边缘处以线宽为长、以一半线宽为宽的矩形。
   const canvas = document.getElementById("canvas");
          canvas.width = 800;
          canvas.height = 600;
          const context = canvas.getContext("2d");
          context.beginPath();
          context.moveTo(100, 100);
          context.lineTo(700, 100);
          context.lineWidth = 50;
           context.strokeStyle = "gold";
          context.lineCap = "butt";
          context.stroke();

          context.beginPath();
          context.moveTo(100, 300);
          context.lineTo(700, 300);
          context.lineWidth = 50;
           context.strokeStyle = "red";
           context.lineCap = "round";
          context.stroke();


          context.beginPath();
          context.moveTo(100, 500);
          context.lineTo(700, 500);
          context.lineWidth = 50;
           context.strokeStyle = "green";
           context.lineCap = "square";
          context.stroke();

image.png

2、lineJoin属性

lineJoin 定义两条线相交产生的拐角,可将其称为连接。在连接处创建一个填充三角形,可以使用 lineJoin 设置它的基本属性。

  • miter:默认值,在连接处边缘延长相接。miterLimit 是角长和线宽所允许的最大比例(默认是 10)。
  • bevel:连接处是一个对角线斜角。
  • round:连接处是一个圆。

注意:miterLimit规定了一个自动填充连接点的极限值。如果超过了这个值,会导致lineJoin属性失效,会从 miter 变成 bevel。可以看出来,这个值和线宽以及夹角有关

  const canvas = document.getElementById("canvas1");
          canvas.width = 800;
          canvas.height = 600;
          const context = canvas.getContext("2d");
          context.beginPath();
          context.moveTo(100, 100);
          context.lineTo(300, 300);
          context.lineTo(100, 500);
          context.lineWidth = 20;
          context.strokeStyle = "gold";
          //方角连接
          context.lineJoin = "miter";
          context.stroke();

          context.beginPath();
          context.moveTo(300, 100);
          context.lineTo(500, 300);
          context.lineTo(300, 500);
          context.lineWidth = 20;
          context.strokeStyle = "red";
          //断角连接
          context.lineJoin = "bevel";
          context.stroke();

          context.beginPath();
          context.moveTo(500, 100);
          context.lineTo(700, 300);
          context.lineTo(500, 500);
          //圆角连接
          context.lineJoin = "round";
          context.lineWidth = 20;
          context.strokeStyle = "green";
          context.lineCap = "square";
          context.stroke();

image.png

3、线宽

lineWidth 定义线的宽度(默认值为 1.0)。

2.2 画不同类型的线段

如何描绘?

如果需要画三个不同的线段,很多人想是不是就是将代码复制,其实不然,实际上Canvas是基于状态的绘制,每次使用stroke()时,它都会把之前设置的状态再绘制一遍。因此在绘制之前应该加上context.beginPath()

     const canvas = document.getElementById("canvas");
      canvas.width = 800;
      canvas.height = 600;
      const context = canvas.getContext("2d");
      //1.重新绘制
      context.beginPath();
      //2.移动画笔,使画笔移动至绘画的开始处
      context.moveTo(100, 0);
      //3.确定第一笔的停止点
      context.lineTo(500, 500);
      //4.确定好画笔的颜色和线条粗细
      context.lineWidth = 5;
      context.strokeStyle = "red";
      //5.确定绘制
      context.stroke();

      //1.重新绘制
      context.beginPath();
      //2.移动画笔,使画笔移动至绘画的开始处
      context.moveTo(20, 0);
      //3.确定第一笔的停止点
      context.lineTo(300, 300);
      //4.确定好画笔的颜色和线条粗细
      context.lineWidth = 5;
      context.strokeStyle = "blue";
      //5.确定绘制
      context.stroke();

2.3 画矩形

如果使用上面画线段的方式,只要确定四个点就可以画矩形了,但事实上如果这样画出的矩形会没有闭合,因此在绘制之前应该加上context.closePath()用来补全图形,这里还可以使用油漆桶给我们所画的图形上色,使用 context.fillStyle = "green"给油漆桶设置一个颜色,context.fill(); 进行填充。

      const canvas = document.getElementById("canvas");
      canvas.width = 800;
      canvas.height = 600;
      const context = canvas.getContext("2d");
      //1.开始绘制
      context.beginPath();
      //2.从坐标100,100开始绘制
      context.moveTo(100, 100);
      context.lineTo(300, 100);
      context.lineTo(300, 300);
      context.lineTo(100, 300);
      context.lineTo(100, 100);
      //3.补全图形
      context.closePath()

      //4.选择油漆桶颜色
      context.fillStyle = "green"

      //5.确定图形线条宽度以及颜色
      context.lineWidth=3;
      context.strokeStyle='blue';

      //6.确定填充
      context.fill();                 
      //7.开始绘制
      context.stroke()

2.3使用rect绘制多个矩形

由于绘制矩形比较常见,因此在 canvas中已经封装了rect这个api用来绘制矩形context.rect(x,y,width,height)

      const canvas = document.getElementById("canvas");
      canvas.width = 800;
      canvas.height = 600;
      const context = canvas.getContext("2d");

      let rectArr = [
        {
          x: 150,
          y: 50,
          width: 50,
          height: 50,
          fillColor: "blue",
          borderColor: "gray",
          borderWidth: 2,
        },
        {
          x: 250,
          y: 50,
          width: 50,
          height: 50,
          fillColor: "yellow",
          borderColor: "gray",
          borderWidth: 2,
        },
        {
          x: 350,
          y: 50,
          width: 50,
          height: 50,
          fillColor: "red",
          borderColor: "gray",
          borderWidth: 2,
        },
        {
          x: 450,
          y: 50,
          width: 50,
          height: 50,
          fillColor: "pink",
          borderColor: "gray",
          borderWidth: 2,
        },
        {
          x: 150,
          y: 150,
          width: 50,
          height: 50,
          fillColor: "orange",
          borderColor: "gray",
          borderWidth: 2,
        },
        {
          x: 250,
          y: 150,
          width: 50,
          height: 50,
          fillColor: "green",
          borderColor: "gray",
          borderWidth: 2,
        },
      ];

      rectArr.forEach((item) => {
        drawRect(
          context,
          item.x,
          item.y,
          item.width,
          item.height,
          item.fillColor,
          item.borderWidth,
          item.borderColor
        );
      });
      function drawRect(
        context,
        x,
        y,
        width,
        height,
        fillColor,
        borderWidth,
        borderColor
      ) {
        //1.开始绘制
        context.beginPath();
        //2.从坐标100,100开始绘制
        // context.moveTo(x, y);
        // context.lineTo(x + width, y);
        // context.lineTo(x + width, y + height);
        // context.lineTo(x, y + height);
        // context.lineTo(x, y);
        // //3.补全图形
        // context.closePath();
        
        //上面注释2,3与下面的代码同样的效果
        context.rect(x, y, width, height);

        //4.选择油漆桶颜色
        context.fillStyle = fillColor;

        //5.确定图形线条宽度以及颜色
        context.lineWidth = borderWidth;
        context.strokeStyle = borderColor;

        //6.确定填充
        context.fill();
        //7.开始绘制
        context.stroke();
      }

绘制案例

 <div id="canvas-warp">
      <canvas
        id="canvas"
        style="border: 1px solid #aaaaaa; display: block; margin: 50px auto"
      >
        你的浏览器居然不支持Canvas?!赶快换一个吧!!
      </canvas>
    </div>
    <script>
      const canvas = document.getElementById("canvas");
      canvas.width = 800;
      canvas.height = 600;
      const context = canvas.getContext("2d");
      context.beginPath();
      context.rect(0, 0, 800, 600);
      context.fillStyle = "yellow";
      context.fill();
      context.beginPath();
      for (var i = 0; i < 20; i++) {
        drawWhiteRect(
          context,
          200 + 10 * i,
          100 + 10 * i,
          400 - 20 * i,
          400 - 20 * i
        );
        drawBlackRect(
          context,
          205 + 10 * i,
          105 + 10 * i,
          390 - 20 * i,
          390 - 20 * i
        );
      }
      context.beginPath();
      context.rect(395, 295, 5, 5);
      context.fillStyle = "orange";
      context.lineWidth = 5;
      context.fill();
      context.stroke();
      function drawBlackRect(cxt, x, y, width, height) {
        cxt.beginPath();
        cxt.rect(x, y, width, height);
        cxt.lineWidth = 5;
        cxt.strokeStyle = "gold";
        cxt.stroke();
      }
      function drawWhiteRect(cxt, x, y, width, height) {
        cxt.beginPath();
        cxt.rect(x, y, width, height);
        cxt.lineWidth = 5;
        cxt.strokeStyle = "white";
        cxt.stroke();
      }

绘制图形 1675739943813.png

2.4 绘制三角形

const canvas4 = document.getElementById('canvas4');
        const ctx4 = canvas4.getContext('2d');
        ctx4.beginPath();
        ctx4.fillStyle = '#ff6';
        ctx4.fillRect(0, 0, canvas.width, canvas.height);

        ctx4.beginPath()
        //起始绘制点
        ctx4.moveTo(100, 50)
        //第二个点
        ctx4.lineTo(150, 50);
        //第三个点
        ctx4.lineTo(100, 100)
        //填充颜色
        ctx4.fillStyle = 'red';
        //绘图
        ctx4.fill()

3.深入用法

3.1 填充渐变颜色

渐变颜色分为线性渐变和径向渐变,线性渐变是基于两个端点定义的,但是径向渐变是基于两个圆定义的。

线性渐变

  1. 添加渐变线:

    const grd = context.createLinearGradient(xstart,ystart,xend,yend);
    
  2. 为渐变线添加关键色(类似于颜色断点):

    grd.addColorStop(stop,color);
    
  3. 应用渐变

  const canvas = document.getElementById("canvasBox");
      canvas.width = 800;
      canvas.height = 600;
      const context = canvas.getContext("2d");
      context.rect(200,100,400,400);
        //添加渐变线
        //context.createLinearGradient(xstart,ystart,xend,yend);
        var grd = context.createLinearGradient(200,300,600,300);
        //添加颜色断点
        grd.addColorStop(0,"pink");
        grd.addColorStop(0.5,"red");
        grd.addColorStop(1,"pink");
        //应用渐变
        context.fillStyle = grd;
        context.fill();

image.png

3.2 填充图案

纹理其实就是图案的重复,填充图案通过createPattern()函数进行初始化。它需要传进两个参数createPattern(img,repeat-style),第一个是Image对象实例,第二个参数是String类型,表示在形状中如何显示repeat图案。可以使用这个函数加载图像或者整个画布作为形状的填充图案。

有以下4种图像填充类型:

  • 平面上重复:repeat;
  • x轴上重复:repeat-x;
  • y轴上重复:repeat-y;
  • 不使用重复:no-repeat;
 const canvas = document.getElementById("canvas");
      canvas.width = 500;
      canvas.height = 500;
      const context = canvas.getContext("2d");
      const img = new Image(10, 10);
      img.src = "./img/flower.png";
      img.onload = function () {
        //重置一个画布用来改变图片大小
        let tempCanvas = document.createElement("canvas");
        tempCanvas.width = 10;
        tempCanvas.height = 10;
        let tempCtx = tempCanvas.getContext("2d");
        tempCtx.drawImage(img, 0, 0, this.width, this.height);
        let pattern = context.createPattern(tempCanvas, "repeat");
        // 创建一个绘制背景图的模式
        context.fillStyle = pattern;
        context.fillRect(0, 0, 500, 500);
      };

image.png

3.3圆弧

canvas画圆弧主要有以下几种方式:

  • 标准圆弧:arc()
  • 复杂圆弧:arcTo()
  • 二次贝塞尔曲线:quadraticCurveTo()
  • 三次贝塞尔曲线:bezierCurveTo()

标准圆

      var canvas = document.getElementById("canvas");
      canvas.width = "500";
      canvas.height = "600";
      var context = canvas.getContext("2d");
      //圆
      context.beginPath();
      context.fillStyle = "gold";
      context.strokeStyle = "gold";
      context.arc(100, 100, 50, 0, 2 * Math.PI);
      context.stroke();
      context.fill();

      //半圆
      context.beginPath();
      context.fillStyle = "pink";
      context.strokeStyle = "pink";
      context.arc(100, 250, 50, Math.PI, 2 * Math.PI);
      context.stroke();
      context.fill();

圆角矩形

画圆角矩形主要由圆弧和线段组成

    var canvas = document.getElementById("canvas");
      canvas.width = 800;
      canvas.height = 600;
      var context = canvas.getContext("2d");
      context.fillRect(0, 0, 800, 600);
      drawRoundRect(context, 100, 100, 400, 400, 50);
      context.strokeStyle = "green";
      context.fillStyle = "pink";
      context.stroke();
      context.fill();
      //画圆角矩形
      function drawRoundRect(cxt, x, y, width, height, radius){
        cxt.beginPath();
        //画弧线
        cxt.arc(x + radius, y + radius, radius, Math.PI, Math.PI * 3 / 2);
        //画线段
        cxt.lineTo(width - radius + x, y);
        cxt.arc(width - radius + x, radius + y, radius, Math.PI * 3 / 2, Math.PI * 2);
        cxt.lineTo(width + x, height + y - radius);
        cxt.arc(width - radius + x, height - radius + y, radius, 0, Math.PI * 1 / 2);
        cxt.lineTo(radius + x, height +y);
        cxt.arc(radius + x, height - radius + y, radius, Math.PI * 1 / 2, Math.PI);
        cxt.closePath();
      }

使用切点画弧

使用arcTo(x1,y1,x2,y2,radius) image.png

   var canvas = document.getElementById("canvasBox");
      canvas.width = 600;
      canvas.height = 600;
      var context = canvas.getContext("2d");
      context.fillStyle = "#FFF";
      context.fillRect(0, 0, 600, 600);
      drawArcTo(context, 500, 200, 600, 200, 600, 400, 100);
      function drawArcTo(context, x0, y0, x1, y1, x2, y2, r) {
        context.beginPath();
        context.moveTo(x0, y0);
        context.arcTo(x1, y1, x2, y2, r);
        context.lineWidth = 6;
        context.strokeStyle = "red";
        context.stroke();
        
        context.beginPath();
        context.moveTo(x0, y0);
        context.lineTo(x1, y1);
        context.lineTo(x2, y2);
        context.lineWidth = 1;
        context.strokeStyle = "#0088AA";
        context.stroke();

      }

3.4 微信聊天框

聊天框可由一个三角加上一个圆角矩形组成

聊天框

//绘制聊天框
   function drawChatRect(cxt, type, x, y, width, height, radius, strokeStyle, fillStyle, num) {
       cxt.beginPath();
       cxt.strokeStyle = strokeStyle;
       cxt.fillStyle = fillStyle;
       //画弧线
       cxt.arc(x + radius, y + radius, radius, Math.PI, (Math.PI * 3) / 2);
       //画线段
       cxt.lineTo(width - radius + x, y);
       cxt.arc(
           width - radius + x,
           radius + y,
           radius,
           (Math.PI * 3) / 2,
           Math.PI * 2
       );
       if (type === "forward") {
           cxt.lineTo(width + x, (height + y - radius) / 2 + y / 2);
           cxt.lineTo(width + x + num, ((height + y - radius) / 2 + y / 2) + num);
           cxt.lineTo(width + x, (height + y - radius) / 2 + y / 2 + num * 2);
           cxt.lineTo(width + x, height + y - radius);
           cxt.arc(
               width - radius + x,
               height - radius + y,
               radius,
               0,
               (Math.PI * 1) / 2
           );
           cxt.lineTo(radius + x, height + y);
           cxt.arc(
               radius + x,
               height - radius + y,
               radius,
               (Math.PI * 1) / 2,
               Math.PI
           );
       } else {
           cxt.lineTo(width + x, height + y - radius);
           cxt.arc(
               width - radius + x,
               height - radius + y,
               radius,
               0,
               (Math.PI * 1) / 2
           );
           cxt.lineTo(radius + x, height + y);
           cxt.arc(
               radius + x,
               height - radius + y,
               radius,
               (Math.PI * 1) / 2,
               Math.PI
           );
           cxt.lineTo(radius + x - num, height - radius + y - num * 2);
           cxt.lineTo(radius + x - num * 2, height - radius + y - num * 3);
           cxt.lineTo(radius + x - num, height - radius + y - num * 4);
       }
       cxt.closePath();
       context.fill();
       context.stroke();
   }

文字显示与图片绘制

   //绘制图片
   function drawImg(img, x, y, width, height, url) {
       context.beginPath();
       img.onload = function () {
           context.drawImage(img, x, y, width, height); //绘制图片
       };
       img.src = url; // 设置图片源地址
       context.closePath();
   }

   //绘制文字
   function drawText(fontSize, color, content, x, y) {
       context.font = fontSize;
       context.fillStyle = color;
       context.fillText(content, x, y);
       context.stroke();
   }

右上角头像显示

这里其实是一个圆形加上一个闭合的半圆

function drawCircle(){
 //绘制头部
      context.beginPath();
      //2.从坐标100,100开始绘制
      context.moveTo(0, 0);
      context.lineTo(300, 0);
      context.lineTo(300, 40);
      context.lineTo(0, 40);
      //3.补全图形
      context.closePath();
      //4.选择油漆桶颜色
      context.fillStyle = "rgb(44,44,46)";
      //5.确定图形线条宽度以及颜色
      context.lineWidth = 1;
      context.strokeStyle = "rgb(44,44,46)";
      //6.确定填充
      context.fill();
      //7.开始绘制
      context.stroke();
}

//绘制封闭半圆
function drawSemicircle(){
   context.beginPath();
      context.moveTo(20, 10);
      context.lineTo(10, 20);
      context.lineTo(20, 30);
      context.lineJoin = "miter";
      context.miterLimit = 10;
      context.lineWidth = 2;
      context.strokeStyle = "#fff";
      context.stroke();
}

image.png

3.5二次贝塞尔曲线

Bézier curve(贝塞尔曲线)是应用于二维图形应用程序的数学曲线。 曲线定义:起始点、终止点、控制点。通过调整控制点,贝塞尔曲线的形状会发生变化。 1962年,法国数学家Pierre Bézier第一个研究了这种矢量绘制曲线的方法,并给出了详细的计算公式,因此按照这样的公式绘制出来的曲线就用他的姓氏来命名,称为贝塞尔曲线。

贝塞尔曲线是一条由起始点、终止点和控制点所确定的曲线就行了。而n阶贝塞尔曲线就有n-1个控制点。用过Photoshop等绘图软件的同学应该比较熟悉,因为其中的钢笔工具设置锚点绘制路径的时候,用到的就是贝塞尔曲线。下面给一个绘制网址:blogs.sitepointstatic.com/examples/te… 使用canvas进行绘制时使用它的一个api:context.quadraticCurveTo(cpx,cpy,x,y);

      const canvas = document.getElementById("canvasBox");
      ctx = canvas.getContext("2d");
      canvas.width = 500;
      canvas.height = 500;
      ctx.lineWidth = 2;
      ctx.strokeStyle = "gold";
      ctx.beginPath();
      ctx.moveTo(100, 250);
      ctx.quadraticCurveTo(53, 13, 400, 250);
      ctx.closePath();
      ctx.fillStyle = "gold";
      ctx.fill()
      ctx.stroke();

image.png

3.6 三次贝塞尔曲线

如果我们想画波浪线那么三次贝塞尔曲线就可以充分的满足需求,他使用的是canvas中的api: context.bezierCurveTo(cp1x,cp1y,cp2x,cp2y,x,y); 传入的6个参数分别为控制点cp1 (cp1x, cp1y),控制点cp2 (cp2x, cp2y),与终止点 (x, y)。 下面一个绘制网页:blogs.sitepointstatic.com/examples/te…

     const canvas = document.getElementById("canvasBox");
      ctx = canvas.getContext("2d");
      canvas.width = 500;
      canvas.height = 500;
      ctx.lineWidth = 2;
      ctx.strokeStyle = "green";
      ctx.beginPath();
      ctx.moveTo(100, 250);
      ctx.bezierCurveTo(230, 51, 275, 438, 400, 250);
      ctx.closePath();
      ctx.fillStyle = "green";
      ctx.fill()
      ctx.stroke();

image.png

3.8 动画

平移

平移是从一个点到一个点,比如将位于(100,100)的矩形平移至(200,200)点。那么我只要在绘制矩形之前加上context.translate(100,100)就可以实现了。canvas的平移是以左上角的点为初始点进行位移。

    var canvas = document.getElementById("canvasBox");
      canvas.width = 600;
      canvas.height = 600;
      var ctx = canvas.getContext("2d");
      ctx.beginPath();
      ctx.fillStyle = "red";
      ctx.fillRect(0, 100, 100, 100);
      var i = 0;
      let r=Math.floor(Math.random()*10)
      let g=Math.floor(Math.random()*100)
      let b=Math.floor(Math.random()*100)
      var myInterVal = setInterval(() => {
        i++;
        if (i >= 11) {
          clearInterval(myInterVal);
          myInterVal = null;
        }
        // ctx.restore(); // 将canvas恢复到未旋转前的状态
        ctx.clearRect(0, 100, 100, 100);
        ctx.translate(10 * i, 0);
        ctx.fillStyle = `rgb(${r*2+g+100*i},${3*g+10*i},${b*2+3*i+10*i})`;
        ctx.fillRect(0, 100, 100, 100);
      }, 300);
      ctx.closePath();

image.png

image.png

未完待定

4.学习意义