前端技术

Canvas 基本入门手册

使用 Canvas

在页面中使用 Canvas ,只需将 <canvas>标签写入 html 文件中即可。

<canvas width="800" height="600">
  您的浏览器不支持 Canvas,建议使用 Chrome 等其他主流浏览器。
</canvas>
  • Canvas 标签属于闭合标签;
  • 在 Canvas 标签中写入文字,如果浏览器支持此标签就不会显示里面的文字 (ie9 以上版本支持此标签);
  • Canvas 的默认大小为300 × 150px;
  • 指定画布大小,直接在标签中指定 width 和 height 属性,单位是 px;
  • 对于 Canvas 来说,不能直接使用 margin: 0 auto; 居中对齐页面,如果需要居中对齐,则需在外层增加一个父容器,或将容器属性修改为块元素。

基本概念

绘图特点

  • 图形一旦绘制成功,就无法再次获取这个图形以及修改这个图形
  • 如果我们想要让 canvas 的图形做动画,必须按照清屏-更新-渲染的逻辑进行编写,也就是重新绘制一次

栅格 (Canvas grid)

Canvas 元素默认被网格所覆盖。通常来说网格中的一个单元相当于 Canvas 元素中的一像素。栅格的起点为画布的左上角(坐标为(0, 0))。所有元素的位置都相对于原点定位。

图形绘制形式

Canvas 只支持两种形式的图形绘制:矩形和路径(由一系列点连成的线段)。所有其他类型的图形都是通过一条或者多条路径组合而成的。

绘制矩形

绘制矩形只需通过 Canvas 提供的方法,设置矩形的坐标以及宽高进行绘制。

绘制路径

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

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

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

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

Path2D 对象

可以使用一系列的路径和绘画命令来把对象“画”在画布上。为了简化代码和提高性能,Path2D对象已可以在较新版本的浏览器中使用,用来缓存或记录绘画命令,这样你将能快速地回顾路径。

参考链接:https://developer.mozilla.org/zh-CN/docs/Web/API/Path2D/Path2D

new Path2D();     // 空的Path对象
new Path2D(path); // 克隆Path对象
new Path2D(d);    // 从SVG建立Path对象
  • path :当调用另一个 Path2D 对象时,会创建一个 path 变量的拷贝。
  • d :当调用 SVG path 数据构成的字符串时,会根据描述创建一个新的路径。
// 创建和拷贝路径
const CANVAS = document.getElementById("canvas");
let ctx = CANVAS.getContext("2d");

let path1 = new Path2D();
path1.rect(10, 10, 100,100);

let path2 = new Path2D(path1);
path2.moveTo(220, 60);
path2.arc(170, 60, 50, 0, 2 * Math.PI);

ctx.stroke(path2);

使用 SVG paths

var p = new Path2D("M10 10 h 80 v 80 h -80 Z");

绘图步骤

// 第一步,获取到 canvas 标签
const CANVAS = document.querySelector('canvas');

// 第二步,获取 canvas 的上下文
let ctx = CANVAS.getContext('2d');

// 第三步,开始绘制
...

// 1,2步合并
var ctx = document.querySelector('canvas').getContext('2d');
  • canvas 自身无法绘制任何内容,是使用 JavaScript 操作的;
  • context 对象就是 JavaScript 操作 canvas 的接口,是所有绘制操作 api 的入口或者集合;

上下文

使用 getContext() 方法获取 Canvas 的上下文。如果上下文没有定义则返回 null。在获取上下文时,Canvas 还提供了一个属性选项(可选)

  • alpha: 布尔值。如果设置为 false,浏览器将认为 Canvas 背景总是不透明的, 这样可以加速绘制透明的内容和图片。
  • willReadFrequently (Gecko only): 布尔值。是否有重复读取计划。经常使用 getImageData(),这将迫使软件使用 2D Canvas 并节省内存(而不是硬件加速)。这个方案适用于存在属性 gfx.canvas.willReadFrequently的环境。并设置为 true (缺省情况下,只有B2G / Firefox OS).
  • storage (Blink only): string 这样表示使用哪种方式存储(默认为:持久(”persistent”)).

参考链接:https://developer.mozilla.org/zh-CN/docs/Web/API/HTMLCanvasElement/getContext

ctx = CANVAS.getContext('2d', {alpha: false});

开始绘制

绘制一条直线

在做好画布准备工作后,绘制一条直线的绘制过程,需要经过以下步骤:

  1. 开启一个绘图状态 beginPath();
  2. 确定起始点 moveTo();
  3. 绘制的结束点 lineTo();
  4. 闭合路径 closePath(); (可选)
  5. 设置样式 strokeStyle …
  6. 生成绘制图形 stroke() / fill();
const CANVAS = document.querySelector('canvas');
let ctx = CANVAS.getContext('2d');
ctx.beginPath();
ctx.moveTo(100,300);
ctx.lineTo(700,300);
ctx.strokeStyle = 'blue';
ctx.stroke();

颜色

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

常用 API

基础方法

beginPath()

当绘制完成一个图形后,需使用此方法开启一个新的路径,以保证后面更新的图形与样式不影响之前的效果。

参考链接:https://developer.mozilla.org/zh-CN/docs/Web/API/CanvasRenderingContext2D/beginPath

beginPath();
closePath()

将笔点返回到当前子路径起始点,它尝试从当前点到起始点绘制一条直线。 如果图形已经是封闭的或者只有一个点,那么此方法不会做任何操作。即封闭路径。

moveTo()

将一个新的子路径的起始点移动到(x,y)坐标。

moveTo(x, y);
clearRect()

擦除画布内容,设置擦除的坐标与尺寸,可实现局部擦除或全部擦除

clearRect(x, y, width, height);

// 擦除整个画布内容
ctx.clearRect(0, 0, canvas.clientWidth, canvas.clientHeight);

基础样式

strokeStyle

描述线条颜色或者样式。

参考链接:https://developer.mozilla.org/zh-CN/docs/Web/API/CanvasRenderingContext2D/strokeStyle

strokeStyle = color;
strokeStyle = gradient;
strokeStyle = pattern;
lineWidth

设置线段厚度。

lineWidth = value;
lineCap

指定如何绘制每一条线段末端的属性。有3个可能的值,分别是:’butt’, ’round’ and ‘square’。默认值是 ‘butt’。

lineCap = "butt";
lineJoin

设置2个长度不为0的相连部分(线段,圆弧,曲线)如何连接在一起的,由三个值 ’round’, ‘bevel’ and ‘miter’。默认值是 ‘miter’

lineJoin = "miter";
miterLimit

限制当两条线相交时交接处最大长度;所谓交接处长度(斜接长度)是指线条交接处内角顶点到外角顶点的长度。

miterLimit = value
lineDashOffset

参考链接:https://developer.mozilla.org/zh-CN/docs/Web/API/CanvasRenderingContext2D/lineJoin

设置虚线偏移量

lineDashOffset = value;

// 示例
ctx.setLineDash([4, 16]);
setLineDash()

设置当前虚线样式。在填充线时使用虚线模式。 它使用一组值来指定描述模式的线和间隙的交替长度。

参考链接:https://developer.mozilla.org/zh-CN/docs/Web/API/CanvasRenderingContext2D/setLineDash

setLineDash(segments);
stroke()

给当前路径绘制线条

stroke();
stroke(path);
fillStyle

描述填充的颜色或样式。

参考链接:https://developer.mozilla.org/zh-CN/docs/Web/API/CanvasRenderingContext2D/fillStyle

fillStyle = color;
fillStyle = gradient;
fillStyle = pattern;
fill()

填充当前或已存在的路径

fill();
fill(fillRule);
fill(path, fillRule);
  • fillRule 在内填充还是在外填充 nonzero、evenodd
  • path 需要填充的路径

绘制线条

lineTo()

使用直线连接子路径的终点到x,y坐标的方法(并不会真正地绘制)。

lineTo(x, y);

绘制矩形

rect()

创建矩形路径

rect(x, y, width, height);
strokeRect()

直接在画布上绘制一个边框矩形

strokeRect(x, y, width, height);
fillRect()

直接在画布上绘制一个已填充的矩形

fillRect(x, y, width, height);

绘制圆(弧)

arc()

绘制圆弧路径的方法。 圆弧路径的圆心在 (x, y) 位置,半径为 r ,根据anticlockwise (默认为顺时针)指定的方向从 startAngle 开始绘制,到 endAngle 结束。

参考链接:https://developer.mozilla.org/zh-CN/docs/Web/API/CanvasRenderingContext2D/arc

arc(x, y, radius, startAngle, endAngle, anticlockwise);
  • startAngle: 起始弧度
  • endAngle: 结束弧度
  • anticlockwise: 顺 / 逆时针旋转,true 为逆时针,false 顺时针,默认false

弧度转换公式: 弧度 = 角度 * Math.PI / 180

a.moveTo(100, 100);
a.arc(100, 100, 100, 0, 30*Math.PI/180, false);
a.closePath();
a.stroke();

// 计算坐标公式
x = x0 + Math.cos( angle * Math.PI / 180 ) * radius;
y = y0 + Math.sin( angle * Math.PI / 180 ) * radius;
arcTo() (再研究)

根据控制点和半径绘制圆弧路径,使用当前的描点 (前一个 moveTo 或 lineTo 等函数的止点)。根据当前描点与给定的控制点1连接的直线,和控制点1与控制点2连接的直线,作为使用指定半径的圆的切线,画出两条切线之间的弧线路径。

arcTo(x1, y1, x2, y2, radius);
ellipse() (实验中)

参考链接:https://developer.mozilla.org/zh-CN/docs/Web/API/CanvasRenderingContext2D/ellipse

ellipse(x, y, radiusX, radiusY, rotation, startAngle, endAngle, anticlockwise);

贝塞尔曲线

二次贝塞尔曲线有一个开始点、一个结束点以及一个控制点,而三次贝塞尔曲线有两个控制点。使用贝塞尔曲线是有一定的难度的,因为我们所绘制的曲线没有给我们提供直接的视觉反馈。这让绘制复杂的图形变得十分困难。

quadraticCurveTo()

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

quadraticCurveTo(cp1x, cp1y, x, y)
bezierCurveTo()

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

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

绘制文本

fillText()

直接在画布上绘制一个填充文本

参考链接:https://developer.mozilla.org/zh-CN/docs/Web/API/CanvasRenderingContext2D/fillText

fillText(text, x, y, [maxWidth]);
strokeText()

直接在画布上绘制一个边框文本

参考链接:https://developer.mozilla.org/zh-CN/docs/Web/API/CanvasRenderingContext2D/strokeText

strokeText(text, x, y [, maxWidth]);
font

描述绘制文字时,当前字体样式。使用和 CSS font 属性相同的语法. 默认的字体是 10px sans-serif。

参考链接:https://developer.mozilla.org/zh-CN/docs/Web/API/CanvasRenderingContext2D/font

font = value;
ctx.font = "48px serif";
ctx.fillText("Hello world", 10, 50);
textAlign

文本的对齐方式。注意,该对齐是基于CanvasRenderingContext2D.fillText方法的x的值。所以如果textAlign=”center”,那么该文本将画在 x-50%*width。

textAlign = "left" || "right" || "center" || "start" || "end";
textBaseline

设置文本基线

参考链接:https://developer.mozilla.org/zh-CN/docs/Web/API/CanvasRenderingContext2D/textBaseline

textBaseline = "top" || "hanging" || "middle" || "alphabetic" || "ideographic" || "bottom";
direction (实验中的功能)

用来在绘制文本时,描述当前文本方向

参考链接:https://developer.mozilla.org/zh-CN/docs/Web/API/CanvasRenderingContext2D/direction

direction = "ltr" || "rtl" || "inherit";
  • ltr : 文本方向从左向右。
  • rtl : 文本方向从右向左。
  • inherit : 根据情况继承
measureText()

返回一个关于被测量文本TextMetrics 对象包含的信息(例如它的宽度)。

参考链接:https://developer.mozilla.org/zh-CN/docs/Web/API/CanvasRenderingContext2D/measureText

measureText(text);

绘制图片

参考链接:https://developer.mozilla.org/zh-CN/docs/Web/API/Canvas_API/Tutorial/Using_images

可以用于动态的图像合成或者作为图形的背景,以及游戏界面(Sprites)等等。浏览器支持的任意格式的外部图片都可以使用,比如PNG、GIF或者JPEG。 你甚至可以将同一个页面中其他canvas元素生成的图片作为图片源。

引入图像到canvas里需要以下两步基本操作:

  • 获得一个指向 HTMLImageElement 的对象或者另一个 Canvas 元素的引用作为源,也可以通过提供一个 URL 的方式来使用图片;
  • 使用 drawImage() 函数将图片绘制到画布上

获得需要绘制的图片

canvas的API可以使用下面这些类型中的一种作为图片的源:

  • HTMLImageElement :这些图片是由 Image() 函数构造出来的,或者任何的 <img> 元素
  • HTMLVideoElement :用一个HTML的 <video> 元素作为图片源,可以从视频中抓取当前帧作为一个图像
  • HTMLCanvasElement :可以使用另一个 <canvas> 元素作为图片源。
  • ImageBitmap :这是一个高性能的位图,可以低延迟地绘制,它可以从上述的所有源以及其它几种源中生成。

使用相同页面内的图片

使用其它域名下的图片

使用其它 Canvas 元素

由零开始创建图像

通过 data: url 方式嵌入图像

使用视频帧

drawImage()

提供了多种方式在Canvas上绘制图像。

参考链接:https://developer.mozilla.org/zh-CN/docs/Web/API/CanvasRenderingContext2D/drawImage

drawImage(image, dx, dy);
drawImage(image, dx, dy, dWidth, dHeight);
drawImage(image, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight);
createImageData()

参考链接:https://developer.mozilla.org/zh-CN/docs/Web/API/CanvasRenderingContext2D/createImageData

绘制背景图

creatPattern
creatPattern(img,repeat)

裁切

clip()

裁切路径和普通的 canvas 图形差不多,不同的是它的作用是遮罩,用来隐藏不需要的部分。

function draw() {
  var ctx = document.querySelector('canvas').getContext('2d');
  ctx.fillRect(0,0,150,150);
  ctx.translate(75,75);
  
  // Create a circular clipping path
  ctx.beginPath();
  ctx.arc(0,0,30,0,Math.PI*2,true);
  ctx.clip();
  
  // draw background
  var lingrad = ctx.createLinearGradient(0,-75,0,75);
  lingrad.addColorStop(0, '#232256');
  lingrad.addColorStop(1, '#143778');
  
  ctx.fillStyle = lingrad;
  ctx.fillRect(-75,-75,150,150);
  
  // draw stars
  for (var j=1;j<50;j++){
    ctx.save();
    ctx.fillStyle = '#fff';
    ctx.translate(75-Math.floor(Math.random()*150),75-Math.floor(Math.random()*150));
    drawStar(ctx,Math.floor(Math.random()*4)+2);
    ctx.restore();
  }
}
function drawStar(ctx,r){
  ctx.save();
  ctx.beginPath();
  ctx.moveTo(r,0);
  for (var i=0;i<9;i++){
    ctx.rotate(Math.PI/5);
    if(i%2 == 0) {
      ctx.lineTo((r/0.525731)*0.200811,0);
    } else {
      ctx.lineTo(r,0);
    }
  }
  ctx.closePath();
  ctx.fill();
  ctx.restore();
}
draw();

渐变

creatLinearGradient()

线性渐变,渐变的起点 (x1,y1) 与终点 (x2,y2)。

createLinearGradient(x1, y1, x2, y2)
var lineargradient = ctx.createLinearGradient(0,0,150,150);
lineargradient.addColorStop(0,'white');
lineargradient.addColorStop(1,'black');
creatRadialGradient()

径向渐变,前三个定义一个以 (x1,y1) 为原点,半径为 r1 的圆,后三个参数则定义另一个以 (x2,y2) 为原点,半径为 r2 的圆。

createRadialGradient(x1, y1, r1, x2, y2, r2)
var radgrad = ctx.createRadialGradient(45,45,10,52,50,30);
radgrad.addColorStop(0, '#A7D30C');
radgrad.addColorStop(0.9, '#019F62');
radgrad.addColorStop(1, 'rgba(1,159,98,0)');
addColorStop()

接受 2 个参数,position 参数必须是一个 0.0 与 1.0 之间的数值,表示渐变中颜色所在的相对位置。例如,0.5 表示颜色会出现在正中间。color 参数必须是一个有效的 CSS 颜色值(如 #FFF, rgba(0,0,0,1),等等)。

gradient.addColorStop(position, color)

图案样式

createPattern()

该方法接受两个参数。Image 可以是一个 Image 对象的引用,或者另一个 canvas 对象。Type 必须是下面的字符串值之一:repeat,repeat-x,repeat-y 和 no-repeat。

参考链接:https://developer.mozilla.org/zh-CN/docs/Web/API/CanvasRenderingContext2D/createPattern

createPattern(image, type)
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);

  }
}

设置阴影(性能较差)

shadowOffsetX

设置阴影的水平距离,不受变换矩阵所影响的。负值表示阴影会往上或左延伸,正值则表示会往下或右延伸,它们默认都为 0。

shadowOffsetX = float;
shadowOffsetY

设置阴影的垂直距离,不受变换矩阵所影响的。负值表示阴影会往上或左延伸,正值则表示会往下或右延伸,它们默认都为 0。

shadowOffsetY = float;
shadowColor

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

shadowColor = color;
shadowBlur

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

shadowBlur = float;
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);
}

透明度

globalAlpha

可以使用 rgba 来替换此属性

参考链接:https://developer.mozilla.org/zh-CN/docs/Web/API/CanvasRenderingContext2D/globalAlpha

globalAlpha = transparencyValue

变换

参考链接:https://developer.mozilla.org/zh-CN/docs/Web/API/Canvas_API/Tutorial/Transformations

translate()

移动画布和它的原点到一个不同的位置。x 是左右偏移量,y 是上下偏移量。

translate(x, y);

function draw() {
  var ctx = document.getElementById('canvas').getContext('2d');
  for (var i = 0; i < 3; i++) {
    for (var j = 0; j < 3; j++) {
      ctx.save();
      ctx.fillStyle = 'rgb(' + (51 * i) + ', ' + (255 - 51 * i) + ', 255)';
      ctx.translate(10 + j * 50, 10 + i * 50);
      ctx.fillRect(0, 0, 25, 25);
      ctx.restore();
    }
  }
}
rotate()

用于以原点为中心旋转画布。它是顺时针方向的,以弧度为单位的值。旋转的中心点始终是 canvas 的原点,如果要改变它,我们需要用到 translate() 方法。

rotate(angle);

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

  for (var i=1;i<6;i++){ // Loop through rings (from inside to out)
    ctx.save();
    ctx.fillStyle = 'rgb('+(51*i)+','+(255-51*i)+',255)';

    for (var j=0;j<i*6;j++){ // draw individual dots
      ctx.rotate(Math.PI*2/(i*6));
      ctx.beginPath();
      ctx.arc(0,i*12.5,5,0,Math.PI*2,true);
      ctx.fill();
    }

    ctx.restore();
  }
}
scale()

缩放画布的水平和垂直的单位。两个参数都是实数,可以为负数,x 为水平缩放因子,y 为垂直缩放因子,如果比1小,会缩小图形, 如果比1大会放大图形。默认值为1, 为实际大小。

scale(x, y);

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

  // draw a simple rectangle, but scale it.
  ctx.save();
  ctx.scale(10, 3);
  ctx.fillRect(1, 10, 10, 10);
  ctx.restore();

  // mirror horizontally
  ctx.scale(-1, 1);
  ctx.font = '48px serif';
  ctx.fillText('MDN', -135, 120);
}
transform()

允许对变形矩阵直接修改

transform(a, b, c, d, e, f)
  • a (m11) :水平方向的缩放
  • b(m12) :竖直方向的倾斜偏移
  • c(m21) :水平方向的倾斜偏移
  • d(m22) :竖直方向的缩放
  • e(dx) :水平方向的移动
  • f(dy) :竖直方向的移动
function draw() {
  var ctx = document.getElementById('canvas').getContext('2d');

  var sin = Math.sin(Math.PI/6);
  var cos = Math.cos(Math.PI/6);
  ctx.translate(100, 100);
  var c = 0;
  for (var i=0; i <= 12; i++) {
    c = Math.floor(255 / 12 * i);
    ctx.fillStyle = "rgb(" + c + "," + c + "," + c + ")";
    ctx.fillRect(0, 0, 100, 10);
    ctx.transform(cos, sin, -sin, cos, 0, 0);
  }

  ctx.setTransform(-1, 0, 0, 1, 100, 100);
  ctx.fillStyle = "rgba(255, 128, 255, 0.5)";
  ctx.fillRect(0, 50, 100, 100);
}
setTransform()

将当前的变形矩阵重置为单位矩阵,然后用相同的参数调用 transform 方法。如果任意一个参数是无限大,那么变形矩阵也必须被标记为无限大,否则会抛出异常。从根本上来说,该方法是取消了当前变形,然后设置为指定的变形,一步完成。

setTransform(a, b, c, d, e, f)
resetTransform()

重置当前变形为单位矩阵,它和调用以下语句是一样的:ctx.setTransform(1, 0, 0, 1, 0, 0);

resetTransform(a, b, c, d, e, f)
function draw() {
  var ctx = document.getElementById('canvas').getContext('2d');

  var sin = Math.sin(Math.PI/6);
  var cos = Math.cos(Math.PI/6);
  ctx.translate(100, 100);
  var c = 0;
  for (var i=0; i <= 12; i++) {
    c = Math.floor(255 / 12 * i);
    ctx.fillStyle = "rgb(" + c + "," + c + "," + c + ")";
    ctx.fillRect(0, 0, 100, 10);
    ctx.transform(cos, sin, -sin, cos, 0, 0);
  }

  ctx.setTransform(-1, 0, 0, 1, 100, 100);
  ctx.fillStyle = "rgba(255, 128, 255, 0.5)";
  ctx.fillRect(0, 50, 100, 100);
}

画布状态

你可以调用任意多次 save方法。每一次调用 restore 方法,上一个保存的状态就从栈中弹出,所有设定都恢复。

画布状态包括:

  • 当前应用的变形(即移动,旋转和缩放)
  • 以及下面这些属性:strokeStyle, fillStyle, globalAlpha, lineWidth, lineCap, lineJoin, miterLimit, lineDashOffset, shadowOffsetX, shadowOffsetY, shadowBlur, shadowColor, globalCompositeOperation, font, textAlign, textBaseline, direction, imageSmoothingEnabled
  • 当前的裁切路径(clipping path)
save()

保存画布的所有状态

restore()

恢复画布状态到保存的状态

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

  ctx.fillRect(0,0,150,150);   // 使用默认设置绘制一个矩形
  ctx.save();                  // 保存默认状态

  ctx.fillStyle = '#09F'       // 在原有配置基础上对颜色做改变
  ctx.fillRect(15,15,120,120); // 使用新的设置绘制一个矩形

  ctx.save();                  // 保存当前状态
  ctx.fillStyle = '#FFF'       // 再次改变颜色配置
  ctx.globalAlpha = 0.5;
  ctx.fillRect(30,30,90,90);   // 使用新的配置绘制一个矩形

  ctx.restore();               // 重新加载之前的颜色状态
  ctx.fillRect(45,45,60,60);   // 使用上一次的配置绘制一个矩形

  ctx.restore();               // 加载默认颜色配置
  ctx.fillRect(60,60,30,30);   // 使用加载的配置绘制一个矩形
}

合成

参考链接:https://developer.mozilla.org/zh-CN/docs/Web/API/Canvas_API/Tutorial/Compositing

globalCompositeOperation

这个属性设定了在画新图形时采用的遮盖策略,其值是一个标识12种遮盖方式的字符串。

参考链接:https://developer.mozilla.org/zh-CN/docs/Web/API/CanvasRenderingContext2D/globalCompositeOperation

globalCompositeOperation = type;

输出画布

toDataURL(type, encoderOptions);

其他

currentTransform
filter
imageSmoothingEnabled
imageSmoothingQuality
createConicGradient()
drawWidgetAsOnScreen()
getImageData()

drawFocusIfNeeded()
getContextAttributes()
getLineDash()
返回一个包含当前虚线样式,长度为非负偶数的数组。
getTransform()
isPointInPath()
isPointInStroke()
putImageData()
quadraticCurveTo()
scrollPathIntoView()

填充规则

当我们用到 fill(或者 clip和isPointinPath )你可以选择一个填充规则,该填充规则根据某处在路径的外面或者里面来决定该处是否被填充,这对于自己与自己路径相交或者路径被嵌套的时候是有用的。

  • nonzero : non-zero winding rule, 默认值.
  • evenodd : even-odd winding rule.

绘制动画

动画的生成实际就是每一个静态画面连续播放,相当于逐帧动画。

绘制单帧图像

可以通过以下的步骤来画出一帧:

  1. 清空画布:除非接下来要画的内容会完全充满 canvas (例如背景图),否则你需要清空所有。最简单的做法就是用 clearRect 方法。
  2. 保存画布状态:如果你要改变一些会改变 canvas 状态的设置(样式,变形之类的),又要在每画一帧之时都是原始状态的话,你需要先保存一下。
  3. 绘制动画图形 (animated shapes):这一步才是重绘动画帧。
  4. 恢复画布状态:如果已经保存了 canvas 的状态,可以先恢复它,然后重绘下一帧。

操控动画

在 canvas 上绘制内容是用 canvas 提供的或者自定义的方法,而通常,我们仅仅在脚本执行结束后才能看见结果,比如说,在 for 循环里面做完成动画是不太可能的。

因此, 为了实现动画,我们需要一些可以定时执行重绘的方法。有两种方法可以实现这样的动画操控。首先可以通过 setInterval 和 setTimeout 方法来控制在设定的时间点上执行重绘。

有安排的更新画布

首先,可以用window.setInterval(), window.setTimeout(),和window.requestAnimationFrame()来设定定期执行一个指定函数。

简单动画示例

const canvas = document.querySelector('canvas');
const ctx = canvas.getContext('2d');
ctx.fillStyle = 'blue';
let left = 100;
this.setInterval(() => {
  ctx.clearRect(0,0,800,600);
  left++
  ctx.fillRect(left,100,100,100)
}, 10)

因为 Canvas 不能获取已经绘制到画布上的对象,我们要维持对象的状态,所以用 Canvas 实现动画基本上使用面向对象的思维来实现

// 利用构造函数
const canvas = document.querySelector('canvas');
const ctx = canvas.getContext('2d');
function Rect(x, y, w, h, color) {
  this.x = x;
  this.y = y;
  this.w = w;
  this.h = h;
  this.color = color;
}

Rect.prototype.update = function () {
  this.x++;
}

Rect.prototype.render = function () {
  ctx.fillStyle = this.color;
  ctx.fillRect(this.x, this.y, this.w, this.h);
}

let r1 = new Rect(100,100,50,50,'blue');
setInterval(() => {
  ctx.clearRect(0,0,canvas.width, canvas.height);
  r1.update();
  r1.render();
},5)

太阳系的动画

var sun = new Image();
var moon = new Image();
var earth = new Image();
function init(){
  sun.src = 'https://mdn.mozillademos.org/files/1456/Canvas_sun.png';
  moon.src = 'https://mdn.mozillademos.org/files/1443/Canvas_moon.png';
  earth.src = 'https://mdn.mozillademos.org/files/1429/Canvas_earth.png';
  window.requestAnimationFrame(draw);
}

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

  ctx.globalCompositeOperation = 'destination-over';
  ctx.clearRect(0,0,300,300); // clear canvas

  ctx.fillStyle = 'rgba(0,0,0,0.4)';
  ctx.strokeStyle = 'rgba(0,153,255,0.4)';
  ctx.save();
  ctx.translate(150,150);

  // Earth
  var time = new Date();
  ctx.rotate( ((2*Math.PI)/60)*time.getSeconds() + ((2*Math.PI)/60000)*time.getMilliseconds() );
  ctx.translate(105,0);
  ctx.fillRect(0,-12,50,24); // Shadow
  ctx.drawImage(earth,-12,-12);

  // Moon
  ctx.save();
  ctx.rotate( ((2*Math.PI)/6)*time.getSeconds() + ((2*Math.PI)/6000)*time.getMilliseconds() );
  ctx.translate(0,28.5);
  ctx.drawImage(moon,-3.5,-3.5);
  ctx.restore();

  ctx.restore();

  ctx.beginPath();
  ctx.arc(150,150,105,0,Math.PI*2,false); // Earth orbit
  ctx.stroke();

  ctx.drawImage(sun,0,0,300,300);

  window.requestAnimationFrame(draw);
}

init();

参考链接

bilibili 教程
https://www.bilibili.com/video/BV1ss411V7s1?spm_id_from=333.999.0.0

MDN 开发文档
https://developer.mozilla.org/zh-CN/docs/Web/API/Canvas_API
https://developer.mozilla.org/zh-CN/docs/Web/API/CanvasRenderingContext2D

https://malun666.github.io/aicoder_vip_doc/#/pages/canvas?id=_21-canvas-%e6%a0%87%e7%ad%be

内容目录

PUJI Design 朴及设计 (c) 2024. 沪ICP备17052229号