你不知道的Canvas(一)

  • 2019 年 10 月 24 日
  • 筆記

Canvas基础

一、Canvas是什么

Canvas是一个可以使用脚本(通常为JavaScript来绘制图形的HTML) 元素.例如,它可以用于绘制图表、制作图片构图或者制作简单的动画,主要用来绘制2D图形。

Canvas的默认高度为300px*150px,可以使用HTML的高度和宽度属性来自定义Canvas 的尺寸。为了在 Canvas 上绘制图形,我们使用一个JavaScript上下文对象,它能动态创建图像。

二、<canvas>元素

<canvas id="myCanvas" width="150" height="150"></canvas>

<canvas>元素跟<img>元素很像,唯一的不同就是它并没有 src 和 alt 属性。实际上,<canvas> 标签只有两个可选的属性—— widthheight

1.替换内容

<canvas><img>的不同之处在于,就像<video><audio><picture>元素一样,很容易定义一些替代内容。由于某些较老的浏览器(尤其是IE9之前的IE浏览器)或者文本浏览器不支持HTML元素"canvas",在这些浏览器上你应该总是能展示替代内容。不支持<canvas>的浏览器将会忽略容器并在其中渲染后备内容。而支持<canvas>的浏览器将会忽略在容器中包含的内容,并且只是正常渲染canvas。

举个例子,我们可以提供对canvas内容的文字描述或者是提供动态生成内容相对应的静态图片,如下所示:

<canvas id="stockGraph" width="150" height="150">    current stock price: $3.15 +0.15  </canvas>    <canvas id="clock" width="150" height="150">    <img src="images/clock.png" width="150" height="150" alt=""/>  </canvas>  

2.渲染上下文

<canvas>元素创造了一个固定大小的画布,它公开了一个或多个渲染上下文,其可以用来绘制和处理要展示的内容。我们将会将注意力放在2D渲染上下文中。其他种类的上下文也许提供了不同种类的渲染方式。

canvas起初是空白的。为了展示,首先脚本需要找到渲染上下文,然后在它的上面绘制。

<canvas>元素有一个叫做getContext()的方法,这个方法是用来获得渲染上下文和它的绘画功能。getContext()只有一个参数,上下文的格式。

var canvas = document.getElementById('myCanvas');  var ctx = canvas.getContext('2d');

3.检查支持性

替换内容适用于在不支持<canvas>标签的浏览器中展示的。通过简单测试getContext()方法的存在,脚本可以检查编程支持性。

4.模板骨架

<html>    <head>      <title>Canvas tutorial</title>      <script type="text/javascript">        function draw(){          var canvas = document.getElementById('myCanvas');          if (canvas.getContext){            var ctx = canvas.getContext('2d');          }        }      </script>      <style type="text/css">        canvas { border: 1px solid black; }      </style>    </head>    <body onload="draw();">      <canvas id="myCanvas" width="150" height="150"></canvas>    </body>  </html>

上面的脚本中包含一个叫做draw()的函数,当页面加载结束的时候就会执行这个函数。通过使用在文档上加载事件来完成。只要页面加载结束,这个函数,或者像是这个的,同样可以使用 window.setTimeout()window.setInterval()或者其他任何事件处理程序来调用。

三、绘制图形

1.绘制长方形

一开始,让我们来看个简单的例子,我们绘制了两个有趣的长方形,其中的一个有着alpha透明度。

<!DOCTYPE html>  <!--[if lt IE 7]>      <html class="no-js lt-ie9 lt-ie8 lt-ie7"> <![endif]-->  <!--[if IE 7]>         <html class="no-js lt-ie9 lt-ie8"> <![endif]-->  <!--[if IE 8]>         <html class="no-js lt-ie9"> <![endif]-->  <!--[if gt IE 8]><!--> <html class="no-js"> <!--<![endif]-->      <head>          <meta charset="utf-8">            <title>绘制长方形</title>            <script type="text/javascript">              function draw() {                  var canvas = document.getElementById('myCanvas');                  if(canvas.getContext) {                      var ctx = canvas.getContext('2d');                        ctx.fillStyle = "rgba(200, 0 ,0 ,1)";                      ctx.fillRect(10, 10, 55, 50);                        ctx.fillStyle = "rgba(0, 0, 200, 0.5)"                      ctx.fillRect(30, 30, 55, 50);                  }              }          </script>      </head>      <body onload="draw()">          <!--[if lt IE 7]>              <p class="browsehappy">You are using an <strong>outdated</strong> browser. Please <a href="#">upgrade your browser</a> to improve your experience.</p>          <![endif]-->          <canvas id="myCanvas" width="150" height="150"></canvas>      </body>  </html>

fillRect() 是Canvas 2D API 绘制填充矩形的方法。矩形的起点在 (x, y) 位置,矩形的尺寸是 widthheightfillStyle 属性决定矩形的样式。

canvas提供了三种方法绘制矩形:

  • fillRect(x, y, width, height)

    绘制一个填充的矩形

  • strokeRect(x, y, width, height)

    绘制一个矩形的边框

  • clearRect(x, y, width, height)

    清除指定矩形区域,让清除部分完全透明。

    上面提供的方法之中每一个都包含了相同的参数。x与y指定了在canvas画布上所绘制的矩形的左上角(相对于原点)的坐标。width和height设置矩形的尺寸。

2.栅格

在我们开始画图之前,我们需要了解一下画布栅格(canvas grid)以及坐标空间。上文的HTML模板中有个宽150px, 高150px的canvas元素。如图所示,canvas元素默认被网格所覆盖。通常来说网格中的一个单元相当于canvas元素中的一像素。栅格的起点为左上角(坐标为(0,0))。所有元素的位置都相对于原点定位。所以图中蓝色方形左上角的坐标为距离左边(X轴)x像素,距离上边(Y轴)y像素(坐标为(x,y))。在课程的最后我们会平移原点到不同的坐标上,旋转网格以及缩放。现在我们还是使用原来的设置。

现在使用三种方法绘制长方形:

<!DOCTYPE html>  <!--[if lt IE 7]>      <html class="no-js lt-ie9 lt-ie8 lt-ie7"> <![endif]-->  <!--[if IE 7]>         <html class="no-js lt-ie9 lt-ie8"> <![endif]-->  <!--[if IE 8]>         <html class="no-js lt-ie9"> <![endif]-->  <!--[if gt IE 8]><!--> <html class="no-js"> <!--<![endif]-->      <head>          <meta charset="utf-8">            <title>绘制长方形</title>            <script type="text/javascript">              function draw() {                  var canvas = document.getElementById('myCanvas');                  if(canvas.getContext) {                      var ctx = canvas.getContext('2d');                        ctx.fillStyle = "rgba(200, 0 ,0 ,1)";                      //绘制一个填充的矩形                      ctx.fillRect(10, 10, 55, 50);                        ctx.fillStyle = "rgba(0, 0, 200, 0.5)"                      ctx.fillRect(30, 30, 55, 50);                        ctx.fillStyle = "rgba(100, 210, 200, 1)";                      //绘制一个矩形的边框                      ctx.strokeRect(40, 40, 55, 50);                        ctx.fillStyle = "rgba(20, 50, 120, 1)";                      //清除指定矩形区域,让清除部分完全透明。                      ctx.clearRect(40, 40, 55, 50);                  }              }          </script>      </head>      <body onload="draw()">          <!--[if lt IE 7]>              <p class="browsehappy">You are using an <strong>outdated</strong> browser. Please <a href="#">upgrade your browser</a> to improve your experience.</p>          <![endif]-->          <canvas id="myCanvas" width="150" height="150"></canvas>      </body>  </html>

fillRect()函数绘制了一个边长为100px的黑色正方形。clearRect()函数擦除了一个5550px的正方形,接着strokeRect()在清除区域内生成一个5550px的正方形边框。

接下来我们能够看到clearRect()的两个可选方法,然后我们会知道如何改变渲染图形的填充颜色及描边颜色。

3.绘制路径

图形的基本元素是路径。路径是通过不同颜色和宽度的线段或曲线相连形成的不同形状的点的集合。一个路径,甚至一个子路径,都是闭合的。使用路径绘制图形需要一些额外的步骤。

  1. 首先,你需要创建路径起始点。
  2. 然后你使用画图命令去画出路径。
  3. 之后你把路径封闭。
  4. 一旦路径生成,你就能通过描边或填充路径区域来渲染图形。

以下是所要用到的函数:

  • beginPath()

    新建一条路径,生成之后,图形绘制命令被指向到路径上生成路径。

  • closePath()

    闭合路径之后图形绘制命令又重新指向到上下文中。

  • stroke()

    通过线条来绘制图形轮廓。

  • fill()

通过填充路径的内容区域生成实心的图形。

生成路径的第一步叫做beginPath()。本质上,路径是由很多子路径构成,这些子路径都是在一个列表中,所有的子路径(线、弧形、等等)构成图形。而每次这个方法调用之后,列表清空重置,然后我们就可以重新绘制新的图形。

第二步就是调用函数指定绘制路径。

第三,就是闭合路径closePath(),不是必需的。这个方法会通过绘制一条从当前点到开始点的直线来闭合图形。如果图形是已经闭合了的,即当前点为开始点,该函数什么也不做。

4.绘制三角形

<!DOCTYPE html>  <!--[if lt IE 7]>      <html class="no-js lt-ie9 lt-ie8 lt-ie7"> <![endif]-->  <!--[if IE 7]>         <html class="no-js lt-ie9 lt-ie8"> <![endif]-->  <!--[if IE 8]>         <html class="no-js lt-ie9"> <![endif]-->  <!--[if gt IE 8]><!--> <html class="no-js"> <!--<![endif]-->      <head>          <meta charset="utf-8">          <meta http-equiv="X-UA-Compatible" content="IE=edge">          <title>三角形</title>          <meta name="description" content="">          <meta name="viewport" content="width=device-width, initial-scale=1">          <link rel="stylesheet" href="">          <script type="text/javascript">              function draw() {                  var canvas = document.getElementById('myCanvas');                  if(canvas.getContext) {                      var ctx = canvas.getContext('2d');                        ctx.beginPath();                      ctx.moveTo(75, 50);                      ctx.lineTo(100, 75);                      ctx.lineTo(100, 25);                      ctx.fill();                  }              }          </script>      </head>      <body onload="draw();">          <!--[if lt IE 7]>              <p class="browsehappy">You are using an <strong>outdated</strong> browser. Please <a href="#">upgrade your browser</a> to improve your experience.</p>          <![endif]-->          <canvas id="myCanvas" width="150" height="150"></canvas>          <script src="" async defer></script>      </body>  </html>

5.移动笔触

一个非常有用的函数,而这个函数实际上并不能画出任何东西,也是上面所描述的路径列表的一部分,这个函数就是moveTo()。或者你可以想象一下在纸上作业,一支钢笔或者铅笔的笔尖从一个点到另一个点的移动过程。

moveTo(x, y)

将笔触移动到指定的坐标x以及y上。

当canvas初始化或者beginPath()调用后,你通常会使用moveTo()函数设置起点。我们也能够使用moveTo()绘制一些不连续的路径。看一下下面的笑脸例子。我将用到moveTo()方法(红线处)的地方标记了。

6.绘制笑脸

<!DOCTYPE html>  <!--[if lt IE 7]>      <html class="no-js lt-ie9 lt-ie8 lt-ie7"> <![endif]-->  <!--[if IE 7]>         <html class="no-js lt-ie9 lt-ie8"> <![endif]-->  <!--[if IE 8]>         <html class="no-js lt-ie9"> <![endif]-->  <!--[if gt IE 8]><!--> <html class="no-js"> <!--<![endif]-->      <head>          <meta charset="utf-8">          <meta http-equiv="X-UA-Compatible" content="IE=edge">          <title></title>          <meta name="description" content="">          <meta name="viewport" content="width=device-width, initial-scale=1">          <link rel="stylesheet" href="">          <script type="text/javascript">              function draw() {                  var canvas = document.getElementById('myCanvas');                  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();                  }              }          </script>      </head>      <body onload="draw();">          <!--[if lt IE 7]>              <p class="browsehappy">You are using an <strong>outdated</strong> browser. Please <a href="#">upgrade your browser</a> to improve your experience.</p>          <![endif]-->          <canvas id="myCanvas" width="150" height="150"></canvas>          <script src="" async defer></script>      </body>  </html>

效果如下:

7.绘制直线

绘制直线,需要用到的方法时lineTo()方法。

lineTo(x,y)表示绘制一条从当前位置到指定x以及y位置的直线。该方法有两个参数:x以及y ,代表坐标系中直线结束的点。开始点和之前的绘制路径有关,之前路径的结束点就是接下来的开始点,开始点也可以通过moveTo()函数改变。

下面的例子绘制两个三角形,一个是填充的,另一个是描边的。

<!DOCTYPE html>  <!--[if lt IE 7]>      <html class="no-js lt-ie9 lt-ie8 lt-ie7"> <![endif]-->  <!--[if IE 7]>         <html class="no-js lt-ie9 lt-ie8"> <![endif]-->  <!--[if IE 8]>         <html class="no-js lt-ie9"> <![endif]-->  <!--[if gt IE 8]><!--> <html class="no-js"> <!--<![endif]-->      <head>          <meta charset="utf-8">          <meta http-equiv="X-UA-Compatible" content="IE=edge">          <title></title>          <meta name="description" content="">          <meta name="viewport" content="width=device-width, initial-scale=1">          <link rel="stylesheet" href="">          <script type="text/javascript">              function draw() {                  var canvas = document.getElementById('myCanvas');                  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();                  }              }          </script>      </head>      <body onload="draw();">          <!--[if lt IE 7]>              <p class="browsehappy">You are using an <strong>outdated</strong> browser. Please <a href="#">upgrade your browser</a> to improve your experience.</p>          <![endif]-->          <canvas id="myCanvas" width="150" height="150"></canvas>          <script src="" async defer></script>      </body>  </html>

效果如下:

8.圆弧

绘制圆弧或者圆,我们使用arc()方法。当然可以使用arcTo(),不过这个的实现并不是那么的可靠。

  • arc(x, y, radius, startAngle, endAngle, anticlockwise)

    画一个以(x,y)为圆心的以radius为半径的圆弧(圆),从startAngle开始到endAngle结束,按照anticlockwise给定的方向(默认为顺时针)来生成。

  • arcTo(x1, y1, x2, y2, radius)

    根据给定的控制点和半径画一段圆弧,再以直线连接两个控制点。

这里详细介绍一下arc方法,该方法有六个参数:x,y为绘制圆弧所在圆上的圆心坐标。radius为半径。startAngle以及endAngle参数用弧度定义了开始以及结束的弧度。这些都是以x轴为基准。参数anticlockwise为一个布尔值。为true时,是逆时针方向,否则顺时针方向。

注意:arc()函数中表示角的单位是弧度,不是角度。角度与弧度的js表达式:

弧度=(Math.PI/180)*角度。

下面两个for循环,生成圆弧的行列(x,y)坐标。每一段圆弧的开始都调用beginPath()。代码中,每个圆弧的参数都是可变的,实际编程中,我们并不需要这样做。

x,y坐标是可变的。半径(radius)和开始角度(startAngle)都是固定的。结束角度(endAngle)在第一列开始时是180度(半圆)然后每列增加90度。最后一列形成一个完整的圆。

clockwise语句作用于第一、三行是顺时针的圆弧,anticlockwise作用于二、四行为逆时针圆弧。if语句让一、二行描边圆弧,下面两行填充路径。

<!DOCTYPE html>  <!--[if lt IE 7]>      <html class="no-js lt-ie9 lt-ie8 lt-ie7"> <![endif]-->  <!--[if IE 7]>         <html class="no-js lt-ie9 lt-ie8"> <![endif]-->  <!--[if IE 8]>         <html class="no-js lt-ie9"> <![endif]-->  <!--[if gt IE 8]><!-->  <html class="no-js">  <!--<![endif]-->    <head>      <meta charset="utf-8">      <meta http-equiv="X-UA-Compatible" content="IE=edge">      <title></title>      <meta name="description" content="">      <meta name="viewport" content="width=device-width, initial-scale=1">      <link rel="stylesheet" href="">      <script type="text/javascript">          function draw() {              var canvas = document.getElementById('myCanvas');              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();                          }                      }                  }              }          }      </script>  </head>    <body onload="draw();">      <!--[if lt IE 7]>              <p class="browsehappy">You are using an <strong>outdated</strong> browser. Please <a href="#">upgrade your browser</a> to improve your experience.</p>          <![endif]-->      <canvas id="myCanvas" width="150" height="200"></canvas>      <script src="" async defer></script>  </body>    </html>

效果如下:

9.二次贝塞尔曲线及三次贝塞尔曲线

二次及三次贝塞尔曲线都十分有用,一般用来绘制复杂有规律的图形。

  • quadraticCurveTo(cp1x, cp1y, x, y)

    绘制二次贝塞尔曲线,cp1x,cp1y为一个控制点,x,y为结束点。

  • bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y)

    绘制三次贝塞尔曲线,cp1x,cp1y为控制点一,cp2x,cp2y为控制点二,x,y为结束点。

下面的图能够很好的描述两者的关系,二次贝塞尔曲线有一个开始点(蓝色)、一个结束点(蓝色)以及一个控制点(红色),而三次贝塞尔曲线有两个控制点。

参数x、y在这两个方法中都是结束点坐标。cp1x,cp1y为坐标中的第一个控制点,cp2x,cp2y为坐标中的第二个控制点。

使用二次以及三次贝塞尔曲线是有一定的难度的,因为不同于像Adobe Illustrators这样的矢量软件,我们所绘制的曲线没有给我们提供直接的视觉反馈。这让绘制复杂的图形变得十分困难。在下面的例子中,我们会绘制一些简单有规律的图形,如果你有时间以及更多的耐心,很多复杂的图形你也可以绘制出来。

9.1二次贝塞尔曲线

这个例子使用多个贝塞尔曲线来渲染对话气泡。

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();    }  }

效果如下:

9.2三次贝塞尔曲线

这个例子使用贝塞尔曲线绘制心形。

//三次贝塞尔曲线                  ctx1.fillStyle="pink";                  ctx1.beginPath();                  ctx1.moveTo(75, 40);                  ctx1.bezierCurveTo(75, 37, 70, 25, 50, 25);                  ctx1.bezierCurveTo(20, 25, 20, 62.5, 20, 62.5);                  ctx1.bezierCurveTo(20, 80, 40, 102, 75, 120);                  ctx1.bezierCurveTo(110, 102, 130, 80, 130, 62.5);                  ctx1.bezierCurveTo(130, 62.5, 130, 25, 100, 25);                  ctx1.bezierCurveTo(85, 25, 75, 37, 75, 40);                  ctx1.fill();

效果如下: