首页 > 技术文章 > Html5中Canvas绘制、样式详解(不包含动画部分)

jianghaijun4031 2020-06-24 16:34 原文

1.  canvas元素

<canvas>是一个可以使用脚本(通常为JavaScript)来绘制图形的 HTML 元素.不过,<canvas> 元素本身并没有绘制能力(它仅仅是图形的容器) - 必须使用脚本来完成实际的绘图任务

<canvas id="cas" width="500" height="500"></canvas>

<canvas>元素只有两个属性---width和height属性,id是html的通用属性

如果有些浏览器不支持,我们只是在<canvas>标签中提供了替换内容。不支持<canvas>的浏览器将会忽略容器并在其中渲染后备内容。而支持<canvas>的浏览器将会忽略在容器中包含的内容,并且只是正常渲染canvas。

<canvas id="stockGraph" width="150" height="150">
  是时候该换浏览器了。ie不应该是一个正常人该用的
</canvas>

canvas起初是空白的。为了展示,首先脚本需要找到渲染上下文,然后在它的上面绘制。<canvas> 元素有一个叫做 getContext() 的方法,这个方法是用来获得渲染上下文和它的绘画功能。getContext()只有一个参数 可以是‘2d’

<script type="text/javascript">
  var canvas = document.getElementById('cas');
  var ctx = canvas.getContext('2d');
</script>

检查浏览器是否支持的js方法

if (canvas.getContext){
  var ctx = canvas.getContext('2d');
  // drawing code here
} else {
  // canvas-unsupported code here
}

2. 坐标系统

栅格的起点为左上角(坐标为(0,0))。所有元素的位置都相对于原点定位。所以图中蓝色方形左上角的坐标为距离左边(X轴)x像素,距离上边(Y轴)y像素(坐标为(x,y))

3. 绘制矩形

 首先,我们回到矩形的绘制中。canvas提供了三种方法绘制矩形:
     fillRect(x, y, width, height)
    绘制一个填充的矩形
    strokeRect(x, y, width, height)
    绘制一个矩形的边框
    clearRect(x, y, width, height)
    清除指定矩形区域,让清除部分完全透明。
    上面提供的方法之中每一个都包含了相同的参数。x与y指定了在canvas画布上所绘制的矩形的左上角(相对于原点)的坐标。width和height设置矩形的尺寸。

开始画的位置是矩形的左上角。

例子: 下边用整片的文档,以下只用函数

<!DOCTYPE html>
<html>
	<head>
		<meta charset="utf-8">
    <meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no" />
		<title></title>
    <style type="text/css">
      canvas { border: 1px solid black; }
    </style>
	</head>
	<body>
    <canvas id="canvas" width="500" height="500"></canvas>
    <script type="text/javascript">
      function draw() {
        var canvas = document.getElementById('canvas');
        if(canvas.getContext('2d')) {
          var ctx = canvas.getContext('2d');
           ctx.fillRect(25, 25, 100, 100);
           ctx.clearRect(45, 45, 60, 60);
           ctx.strokeRect(50, 50, 50, 50);
        }        
      }
      draw();   
    </script>
	</body>
</html>

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

4. 绘制路径

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

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

以下是所要用到的函数:

beginPath()

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

closePath()

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

stroke()

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

fill()

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

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

注意:当前路径为空,即调用beginPath()之后,或者canvas刚建的时候,第一条路径构造命令通常被视为是moveTo(),无论实际上是什么。出于这个原因,你几乎总是要在设置路径之后专门指定你的起始位置。

第二步就是调用函数指定绘制路径,本文稍后我们就能看到了。

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

注意:当你调用fill()函数时,所有没有闭合的形状都会自动闭合,所以你不需要调用closePath()函数。但是调用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();
  }
}

5. 移动笔触

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

moveTo(xy)

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

当canvas初始化或者beginPath()调用后,你通常会使用moveTo()函数设置起点。我们也能够使用moveTo()绘制一些不连续的路径。

例如画两条直线

function draw() {
  var canvas = document.getElementById('canvas');
  if (canvas.getContext('2d')) {
    var ctx = canvas.getContext('2d');
    ctx.beginPath();
    ctx.moveTo(25, 25);
    ctx.lineTo(105, 25);
    ctx.moveTo(30, 30);
    ctx.lineTo(100, 30);
    ctx.stroke();
  }
}

例如画个笑脸

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

6. 线

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

lineTo(x, y)
绘制一条从当前位置到指定x以及y位置的直线。
该方法有两个参数: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();
  }
}

 

 

7. 圆弧

绘制圆弧或者圆,我们使用arc()方法。

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

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

这里的圆的坐标系

画个从 0到π/2的一个顺时针圆弧

ctx.beginPath();
ctx.arc(75, 75, 50, 0, Math.PI/2, false); // 设置false为顺时针
ctx.stroke();

换个逆时针

ctx.beginPath();
ctx.arc(75, 75, 50, 0, Math.PI/2, true); // 设置true为逆时针
ctx.stroke();

整圆逆时针顺时针就无所谓了

ctx.beginPath();
ctx.arc(75, 75, 50, 0, Math.PI*2, true); // 绘制
ctx.stroke();

如果把ctx.stroke()改成 ctx.fill();是这个样子

8. 贝塞尔曲线

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

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为坐标中的第二个控制点。

曲线起始点是通过上次绘图的结束点或者moveTo的位置

二次曲线实例

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

再画个太极图嘚瑟一下

function fillTaiji() {
  var c = document.getElementById("canvas");
  var ctx = c.getContext("2d");
  ctx.beginPath();
  
  ctx.arc(250,250,100,Math.PI*3/2,Math.PI/2,true);
  ctx.arc(250,300,50,Math.PI/2,Math.PI*3/2,true);
  ctx.arc(250,200,50,Math.PI/2,Math.PI*3/2,false);
  // ctx.stroke();
  ctx.fillStyle = '#000000';
  ctx.fill();
  ctx.closePath();
  ctx.beginPath();
  ctx.arc(250,300,5,0,Math.PI*2,false);
  ctx.fillStyle = '#ffffff';
  ctx.fill();
  ctx.closePath();
  ctx.beginPath();
  ctx.arc(250,250,100,Math.PI*3/2,Math.PI/2,false);
  ctx.arc(250,300,50,Math.PI/2,Math.PI*3/2,true);
  ctx.arc(250,200,50,Math.PI/2,Math.PI*3/2,false);
  ctx.strokeStyle = '#ffffff';
  ctx.fill();
  ctx.beginPath();
  ctx.arc(250,200,5,0,Math.PI*2,false);
  ctx.fillStyle = '#000000';
  ctx.fill();
}
fillTaiji()

效果图

 

9. 色彩 Colors

上边一些案例也有用到颜色。

如果我们想要给图形上色,有两个重要的属性可以做到:fillStyle 和 strokeStyle。

fillStyle = color

设置图形的填充颜色。

strokeStyle = color

设置图形轮廓的颜色。

color 可以是表示 CSS 颜色值的字符串,渐变对象或者图案对象 只要css支持的都可以写

一旦设置了 strokeStyle 或者 fillStyle 的值,那么这个新值就会成为新绘制的图形的默认值。如果你要给每个图形上不同的颜色,你需要重新设置 fillStyle 或 strokeStyle 的值。

// 这些 fillStyle 的值均为 '橙色'
ctx.fillStyle = "orange";
ctx.fillStyle = "#FFA500";
ctx.fillStyle = "rgb(255,165,0)";
ctx.fillStyle = "rgba(255,165,0,1)";

fillStyle案例

 for 循环来绘制方格阵列,每个方格不同的颜色

function draw() {
  var ctx = document.getElementById('canvas').getContext('2d');
  for (var i=0;i<6;i++){
    for (var j=0;j<6;j++){
      ctx.fillStyle = 'rgb(' + Math.floor(255-42.5*i) + ',' + 
                       Math.floor(255-42.5*j) + ',0)';
      ctx.fillRect(j*25,i*25,25,25);
    }
  }
}

strokeStyle案例

 function draw() {
    var ctx = document.getElementById('canvas').getContext('2d');
    for (var i=0;i<6;i++){
      for (var j=0;j<6;j++){
        ctx.strokeStyle = 'rgb(0,' + Math.floor(255-42.5*i) + ',' + 
                         Math.floor(255-42.5*j) + ')';
        ctx.beginPath();
        ctx.arc(12.5+j*25,12.5+i*25,10,0,Math.PI*2,true);
        ctx.stroke();
      }
    }
  }

10. 线型样式

线宽lineWidth

这个属性设置当前绘线的粗细。属性值必须为正数。默认值是1.0。
​​​​​

ctx.lineWidth = 15

线段端点lineCap 属性

属性 lineCap 的值决定了线段端点显示的样子。它可以为下面的三种的其中之一:buttround 和 square。默认是 butt。

绘制了三条直线,分别赋予不同的 lineCap 值。还有两条辅助线,为了可以看得更清楚它们之间的区别,三条线的起点终点都落在辅助线上。

最左边的线用了默认的 butt 。可以注意到它是与辅助线齐平的。中间的是 round 的效果,端点处加上了半径为一半线宽的半圆。右边的是 square 的效果,端点处加上了等宽且高度为一半线宽的方块。

lineJoin 属性两线段连接处所显示的样子

lineJoin 的属性值决定了图形中两线段连接处所显示的样子。它可以是这三种之一:roundbevel 和 miter。默认是 miter

同样用三条折线来做例子,分别设置不同的 lineJoin 值。最上面一条是 round 的效果,边角处被磨圆了,圆的半径等于线宽。中间和最下面一条分别是 bevel 和 miter 的效果。当值是 miter 的时候,线段会在连接处外侧延伸直至交于一点

使用虚线

用 setLineDash 方法和 lineDashOffset 属性来制定虚线样式

做一个蚂蚁线,会动的虚线

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

function draw() {
  ctx.clearRect(0,0, canvas.width, canvas.height);
  ctx.setLineDash([4, 2]);
  ctx.lineDashOffset = -offset;
  ctx.strokeRect(10,10, 100, 100);
}

function march() {
  offset++;
  if (offset > 16) {
    offset = 0;
  }
  draw();
  setTimeout(march, 20);
}

march();

11. 渐变色

就好像一般的绘图软件一样,我们可以用线性或者径向的渐变来填充或描边

新建一个 canvasGradient 对象,并且赋给图形的 fillStyle 或 strokeStyle 属性

createLinearGradient(x1, y1, x2, y2)
      createLinearGradient 方法接受 4 个参数,表示渐变的起点 (x1,y1) 与终点 (x2,y2)。
createRadialGradient(x1, y1, r1, x2, y2, r2)
      createRadialGradient 方法接受 6 个参数,前三个定义一个以 (x1,y1) 为原点,半径为 r1 的圆,后三个参数则定义另一个以 (x2,y2) 为原点,半径为 r2 的圆。

var lineargradient = ctx.createLinearGradient(0,0,150,150);
var radialgradient = ctx.createRadialGradient(75,75,0,75,75,100);

 创建出 canvasGradient 对象后,我们就可以用 addColorStop 方法给它上色了。
      
gradient.addColorStop(position, color)
      addColorStop 方法接受 2 个参数,position 参数必须是一个 0.0 与 1.0 之间的数值,表示渐变中颜色所在的相对位置。例如,0.5 表示颜色会出现在正中间。color 参数必须是一个有效的 CSS 颜色值(如 #FFF, rgba(0,0,0,1),等等)。
      你可以根据需要添加任意多个色标(color stops)。下面是最简单的线性黑白渐变的例子。   

var lineargradient = ctx.createLinearGradient(0,0,150,150);
    lineargradient.addColorStop(0,'white');
    lineargradient.addColorStop(1,'black');

线性渐变实例

function draw() {
  const ctx = document.getElementById('canvas').getContext('2d');
  ctx.beginPath();
  ctx.rect(100,100,200,200);
  var lingrad = ctx.createLinearGradient(100,100,100,300);
    lingrad.addColorStop(0, '#00ABEB');
    // lingrad.addColorStop(0.5, '#fff');
    lingrad.addColorStop(0.5, '#26C000');
    lingrad.addColorStop(1, '#fff');
  ctx.fillStyle= lingrad
  ctx.fill()
}
draw()

createRadialGradient 的例子(径向渐变是简单地由中心点向外围的圆形扩张)

function draw() {
  const ctx = document.getElementById('canvas').getContext('2d');
  ctx.beginPath();
  ctx.rect(100,100,200,200);
  var lingrad = ctx.createRadialGradient(200,200,0,200,200,200);
    lingrad.addColorStop(0, '#00ABEB');
    // lingrad.addColorStop(0.5, '#fff');
    lingrad.addColorStop(0.5, '#26C000');
    lingrad.addColorStop(1, '#fff');
  ctx.fillStyle= lingrad
  ctx.fill()
}
draw()

12. 使用图片

var img = new Image();
img.src = 'someimage.png';
var ptrn = ctx.createPattern(img,'repeat');

案例

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

  // 创建新 image 对象,用作图案
  var img = new Image();
  img.src = 'https://mdn.mozillademos.org/files/222/Canvas_createpattern.png';
  img.onload = function() {

    // 创建图案
    var ptrn = ctx.createPattern(img, 'repeat');
    ctx.fillStyle = ptrn;
    ctx.fillRect(0, 0, 150, 150);

  }
}

13. 阴影 Shadows

shadowOffsetX = float

shadowOffsetX 和 shadowOffsetY 用来设定阴影在 X 和 Y 轴的延伸距离,它们是不受变换矩阵所影响的。负值表示阴影会往上或左延伸,正值则表示会往下或右延伸,它们默认都为 0

shadowOffsetY = float

shadowOffsetX 和 shadowOffsetY 用来设定阴影在 X 和 Y 轴的延伸距离,它们是不受变换矩阵所影响的。负值表示阴影会往上或左延伸,正值则表示会往下或右延伸,它们默认都为 0

shadowBlur = float

shadowBlur 用于设定阴影的模糊程度,其数值并不跟像素数量挂钩,也不受变换矩阵的影响,默认为 0

shadowColor = color

shadowColor 是标准的 CSS 颜色值,用于设定阴影颜色效果,默认是全透明的黑色。

文字阴影的例子

这个例子绘制了带阴影效果的文字。

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

  ctx.shadowOffsetX = 2;
  ctx.shadowOffsetY = 2;
  ctx.shadowBlur = 2;
  ctx.shadowColor = "rgba(0, 0, 0, 0.5)";
 
  ctx.font = "20px Times New Roman";
  ctx.fillStyle = "Black";
  ctx.fillText("Sample String", 5, 30);
}

14 绘制文本

canvas 提供了两种方法来渲染文本:

fillText(text, x, y [, maxWidth])
在指定的(x,y)位置填充指定的文本,绘制的最大宽度是可选的.
strokeText(text, x, y [, maxWidth])
在指定的(x,y)位置绘制文本边框,绘制的最大宽度是可选的.

function draw() {
  var ctx = document.getElementById('canvas').getContext('2d');
  ctx.font = "48px serif";
  ctx.fillText("Hello world", 10, 50);
}

文本框

function draw() {
  var ctx = document.getElementById('canvas').getContext('2d');
  ctx.font = "48px serif";
  ctx.strokeText("Hello world", 10, 50);
}

我们已经使用了 font 来使文本比默认尺寸大一些. 还有更多的属性可以让你改变canvas显示文本的方式:

font = value
当前我们用来绘制文本的样式. 这个字符串使用和 CSS font 属性相同的语法. 默认的字体是 10px sans-serif。
textAlign = value
文本对齐选项. 可选的值包括:start, end, left, right or center. 默认值是 start。
textBaseline = value
基线对齐选项. 可选的值包括:top, hanging, middle, alphabetic, ideographic, bottom。默认值是 alphabetic。
direction = value
文本方向。可能的值包括:ltr, rtl, inherit。默认值是 inherit。(测试好像没有用,可能我没整明白)
如果你之前使用过CSS,那么这些选项你会很熟悉。

演示

function draw() {
  var ctx = document.getElementById('canvas').getContext('2d');
  ctx.font = "60px serif";
  ctx.textAlign = "center";
  // ctx.direction = "rtl"
  ctx.strokeStyle = "red"
  ctx.strokeText("Hello world", 300, 100);
}

15. 绘制图片

一旦获得了源图对象,我们就可以使用 drawImage 方法将它渲染到 canvas 里。drawImage 方法有三种形态,下面是最基础的一种。

drawImage(image, x, y)

其中 image 是 image 或者 canvas 对象,x 和 y 是其在目标 canvas 里的起始坐标。

 function draw() {
    var ctx = document.getElementById('canvas').getContext('2d');
    var img = new Image();
    img.onload = function(){
      ctx.drawImage(img,0,0);
      ctx.beginPath();
      ctx.moveTo(30,96);
      ctx.lineTo(70,66);
      ctx.lineTo(103,76);
      ctx.lineTo(170,15);
      ctx.stroke();
    }
    img.src = 'images/backdrop.png';
  }

 

 

 

 

 

 

 

 

 

 

推荐阅读