# canvas基础

<canvas>HTML5 新增的,一个可以使用脚本(通常为 JavaScript) 在其中绘制图像的 HTML 元素。它可以用来制作照片集或者制作简单(也不是那么简单)的动画,甚至可以进行实时视频处理和渲染。

# 1、<canvas>标签

<canvas> 看起来和 <img> 标签一样,只是 <canvas> 只有两个可选的属性 width、heigth 属性,而没有 src、alt 属性。

如果不给 <canvas> 设置 widht、height 属性时,则默认 width为300、height 为 150,单位都是 px

也可以使用 css 属性来设置宽高,但是如宽高属性和初始比例不一致,他会出现扭曲。

TIP

如果使用js重新设定了width和height属性, 那么之前绘制的内容会被清空。

canvas的width、height属性表示的是画布的实际尺寸,绘制图形的参数都是基于这个尺寸。而css设置的width、height,表示的是这个canvas元素在页面中渲染的尺寸大小。

比如下面这个例子,这个canvas的实际尺寸是300*300, 但是显示在页面上只有150*150, 说明被缩小了一倍

<canvas width="300" height="300" style="width: 150px; height: 150px;"></canvas>

用canvas绘制可能会存在高倍屏下的模糊问题 解决方案是在高倍屏下, 让canvas的实际尺寸变大, 可以像下面那样子设置,通过window.devicePixelRatio 获取倍率

const ratio = window.devicePixelRatio || 1
canvas.setAttribute('width', cssWidth * ratio);
canvas.setAttribute('height', cssHeight * ratio);

# 2、render context 渲染上下文

<canvas> 会创建一个固定大小的画布,提供一个或多个渲染上下文(画笔),使用渲染上下文来绘制和处理要展示的内容。

目前研究 2D 渲染上下文。 其他的上下文比如, WebGL 使用了基于 OpenGL ES的3D 上下文 ("experimental-webgl") 。

var canvas = document.getElementById('canvas');
var context = c.getContext('2d');

# 3、绘制形状

# 3.1 栅格和坐标

canvas的内容可以看做是做栅格坐标系统, 左上角是(0,0) 。所有元素的位置都是相对原点来定位。后面可能还需要涉及到原点的平移,网格的旋转缩放等。类似于下图

Canvas_default_grid

# 3.2 绘制矩形

canvas只支持一种原生的图形绘制:矩形。所有其他的图形都需要生成一种路径(path)。不过我们拥有众多路径生成的方法让复杂图形的绘制成为了可能。

canvas提供三种方法绘制矩形

  1. context.fillRect(x, y, width, height); 绘制一个填充的矩形。
  2. context.strokeRect(x, y, width, height) 绘制一个矩形的边框。
  3. clearRect(x, y, widh, height) 清除指定的矩形区域,这块区域会变得透明。
var c = document.getElementById('canvas');
var ctx = c.getContext('2d');
ctx.fillStyle = '#FF0000';
ctx.fillRect(0, 0, 150, 75);

绘制一个颜色填充为红色的矩形

# 4、绘制路径

图形的基本元素是路径。

路径是通过不同颜色和宽度的线段或曲线相连形成的不同形状的点的集合。

一个路径,甚至一个子路径,都是闭合的。

使用路径绘制图形需要一些额外的步骤:

  1. 创建路径起始点
  2. 调用绘制方法去绘制出路径
  3. 把路径封闭
  4. 一旦路径生成,通过描边或填充路径区域来渲染图形。

下面是需要用到的方法:

  1. beginPath()

    新建一条路径,路径一旦创建成功,图形绘制命令被指向到路径上生成路径

  2. moveTo(x, y)

    把画笔移动到指定的坐标(x, y)。相当于设置路径的起始点坐标。

  3. closePath()

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

  4. lineTo()

    绘制一条从当前位置到指定坐标的直线

  5. stroke()

    通过线条来绘制图形轮廓

  6. fill()

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

# 4.1 绘制线段
var canvas = document.getElementById('canvas');
var context = canvas.getContext('2d');
context.beginPath(); // 新建一条path
context.moveTo(50, 50); // 移动画笔到坐标(50, 50)
context.lineTo(200, 50); // 绘制一条从当前位置到指定坐标的(200, 50)的直线
context.closePath(); // //闭合路径。会拉一条从当前点到path起始点的直线。如果当前点与起始点重合,则什么都不做
context.stroke(); // 绘制路径
# 4.2 绘制三角形
var canvas = document.getElementById('canvas');
var context = canvas.getContext('2d');
context.beginPath(); // 新建一条path
context.moveTo(50, 50); // 移动画笔到坐标(50, 50)
context.lineTo(200, 50); // 绘制一条从当前位置到指定坐标的(200, 50)的直线
context.lineTo(200, 200);
context.closePath(); // 虽然只画了两条线, 但是closePath会闭合路径
context.stroke(); // 描边;stroke 不会自动闭合路径
# 4.3 填充三角形
var canvas = document.getElementById('canvas');
var context = canvas.getContext('2d');
context.beginPath(); // 新建一条path
context.moveTo(50, 50); // 移动画笔到坐标(50, 50)
context.lineTo(200, 50); // 绘制一条从当前位置到指定坐标的(200, 50)的直线
context.lineTo(200, 200);
context.fill(); //填充闭合区域。如果path没有闭合,则fill()会自动闭合路径。
# 4.4 绘制圆弧

有两个方法可以绘制圆弧:

1、arc(x, y, r, startAngle, endAngle, anticlockwise): 以(x, y) 为圆心,以r 为半径,从 startAngle 弧度开始到endAngle弧度结束。anticlosewise 是布尔值,true 表示逆时针,false 表示顺时针(默认是顺时针)。

注意:

  1. 这里的度数都是弧度。

  2. 0 弧度是指的 x 轴正方向。

    radians=(Math.PI/180)*degrees   //角度转换成弧度
    
var canvas = document.getElementById('canvas');
var ctx = canvas.getContext("2d");
    ctx.beginPath();
    ctx.arc(50, 50, 40, 0, Math.PI / 2, false);
    ctx.stroke();
 
    ctx.beginPath();
    ctx.arc(150, 50, 40, 0, -Math.PI / 2, true);
    ctx.closePath();
    ctx.stroke();
 
    ctx.beginPath();
    ctx.arc(50, 150, 40, -Math.PI / 2, Math.PI / 2, false);
    ctx.fill();
 
    ctx.beginPath();
    ctx.arc(150, 150, 40, 0, Math.PI, false);
    ctx.fill();

可以绘制

02arc

2、arcTo(x1, y1, x2, y2, radius): 根据给定的控制点和半径画一段圆弧,最后再以直线连接两个控制点。

var canvas = document.getElementById('canvas');
var context = canvas.getContext('2d');
context.beginPath();
context.moveTo(50, 50);
context.arcTo(200, 50, 200, 200, 100);
context.stroke();
context.beginPath();
context.arc(50, 50, 5, 0, Math.PI * 2, false);
context.fill();
context.beginPath();
context.arc(200, 50, 5, 0, Math.PI * 2, false);
context.fill();
context.beginPath();
context.arc(200, 200, 5, 0, Math.PI * 2, false);
context.fill();

arcTo 方法的说明:

这个方法可以这样理解。绘制的弧形是由两条切线所决定。

第 1 条切线:起始点和控制点1决定的直线。

第 2 条切线:控制点1 和控制点2决定的直线。

其实绘制的圆弧就是与这两条直线相切的圆弧。

# 4.5 绘制贝塞尔曲线

贝塞尔曲线(Bézier curve),又称贝兹曲线或贝济埃曲线,是应用于二维图形应用程序的数学曲线。

quadraticCurveTo(cp1x, cp1y, x, y) // 二次
bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y) // 三次

# 5、设置样式和颜色

在前面的绘制矩形章节中,只用到了默认的线条和颜色。

如果想要给图形上色,有两个重要的属性可以做到。

  1. fillStyle = color 设置图形的填充颜色

  2. strokeStyle = color 设置图形轮廓的颜色

  3. lineWidth

  4. lineCap butt、round、square

  5. lineJoin同一个 path 内,设定线条与线条间接合处的样式。共有 3 个值 round, bevelmiter

  6. setLineDash 方法和 lineDashOffset 属性来制定虚线样式。 setLineDash 方法接受一个数组,来指定线段与间隙的交替;lineDashOffset属性设置起始偏移量。

    getLineDash() 返回一个包含当前虚线样式,长度为非负偶数的数组。

# 6、绘制文本

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

    1. fillText(text, x, y [, maxWidth]) 在指定的 (x,y) 位置填充指定的文本,绘制的最大宽度是可选的。
    2. strokeText(text, x, y [, maxWidth]) 在指定的 (x,y) 位置绘制文本边框,绘制的最大宽度是可选的
      context.font = 'italic normal bold 20px PingFangSC-Regular sans-serif';
	    //   context.textAlign = 'left';
      context.direction = 'ltr';
      context.fillText('Hello, world', 50, 50);
      context.strokeText('Hello, world', 50, 100);

# 7、绘制图片

ctx.drawImage(img,0,0);

const img = new Image();
img.onload = function () {
  context.drawImage(this, 0, 0);
};
img.src = './imgs/Canvas_default_grid.png';

也可以绘制从HTML获取的img图片

const img = document.querySelector('#img');
img.addEventListener("click", () => {
  context.drawImage(img, 0, 0);// 点击事件是为了确保图片已经加载
})
# 7.1 缩放图片

drawImage() 也可以再添加两个参数:drawImage(image, x, y, width, height),方法多了 2 个参数:widthheight,这两个参数用来控制 当向 canvas 画入时应该缩放的大小

const img = document.querySelector('#img');
img.addEventListener('click', () => {
  console.log(img.width);
  context.drawImage(img, 0, 0, 100, 100);
});
# 7.2 切片(绘制部分图片)

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

第一个参数和其它的是相同的,都是一个图像或者另一个 canvas 的引用。 其他 8 个参数: 前 4 个是定义图像源的切片位置和大小,后 4 个则是定义切片的目标显示位置和大小。

const img = document.querySelector('#img');
img.addEventListener('click', () => {
  console.log(img.width);
  context.drawImage(img, 0, 0, 100, 100, 50, 50, 100, 100);
});

# 8、状态的保存和恢复

Saving and restoring state 是绘制复杂图形时必不可少的操作。 save()restore() saverestore 方法是用来保存和恢复 canvas 状态的,都没有参数。 Canvas 的状态就是当前画面应用的所有样式和变形的一个快照。 1、关于 save() :Canvas状态存储在栈中,每当save()方法被调用后,当前的状态就被推送到栈中保存。 一个绘画状态包括:

  • 当前应用的变形(即移动,旋转和缩放)
  • strokeStyle, fillStyle, globalAlpha, lineWidth, lineCap, lineJoin, miterLimit, shadowOffsetX, shadowOffsetY, shadowBlur, shadowColor, globalCompositeOperation 的值
  • 当前的裁切路径(clipping path) 可以调用任意多次 save方法(类似数组的 push())。

# 9、变形

# 9.1 translate

translate(x, y)用来平移canvas的原点到指定的位置。在做变形之前保存状态是一个良好的习惯

context.save();
context.translate(100, 100);
context.fillRect(0, 0, 100, 100);
context.restore();
context.fillStyle = 'red';
context.fillRect(0, 0, 100, 100);
# 9.2 rotate

rotate(angle) 旋转坐标轴, angle是旋转的角度, 弧度单位, 顺时针方向。

context.save();
context.rotate(Math.PI / 6);
context.strokeRect(100, 0, 100, 100);
context.restore();
# 9.3 scale

scale(x, y)我们用它来增减图形在 canvas 中的像素数目,对形状,位图进行缩小或者放大。

context.save();
context.scale(0.5, 0.5);
context.strokeRect(100, 0, 100, 100);
context.restore();
# 9.4 transform(变形矩阵)

transform(a, b, c, d, e, f)

  • a (m11): Horizontal scaling.
  • b (m12): Horizontal skewing.
  • c (m21): Vertical skewing.
  • d (m22): Vertical scaling.
  • e (dx): Horizontal moving.
  • f (dy): Vertical moving.

# 10、合成

在前面的所有例子中、,我们总是将一个图形画在另一个之上,对于其他更多的情况,仅仅这样是远远不够的。比如,对合成的图形来说,绘制顺序会有限制。不过,我们可以利用 globalCompositeOperation 属性来改变这种状况。

context.globalCompositeOperation = type type是下面几种之一

  1. source-over 这是默认设置,新图像会覆盖在原有图像。
  2. source-in仅仅会出现新图像与原来图像重叠的部分,其他区域都变成透明的。(包括其他的老图像区域也会透明)保证重叠部分最量的像素。(每个颜色位进行比较,得到最大的)
  3. source-out 仅仅显示新图像与老图像没有重叠的部分,其余部分全部透明。(老图像也不显示)
  4. source-atop新图像仅仅显示与老图像重叠区域。老图像仍然可以显示。
  5. destination-over 新图像会在老图像的下面。
  6. destination-in 仅仅新老图像重叠部分的老图像被显示,其他区域全部透明。
  7. destination-out仅仅老图像与新图像没有重叠的部分。 注意显示的是老图像的部分区域。
  8. destination-atop 老图像仅仅仅仅显示重叠部分,新图像会显示在老图像的下面。
  9. lighter新老图像都显示,但是重叠区域的颜色做加处理。
  10. darken 保留重叠部分最黑的像素。(每个颜色位进行比较,得到最小的)
  11. lighten保证重叠部分最量的像素。(每个颜色位进行比较,得到最大的)
  12. xor 重叠部分会变成透明。
  13. copy 只有新图像会被保留,其余的全部被清除(边透明)。

# 11、剪裁路径

var ctx = canvas.getContext('2d');
ctx.beginPath();
// 这个圆以外的内容不会被显示
ctx.arc(100, 100, 100, 0, Math.PI * 2);
ctx.clip();
ctx.fillStyle = 'pink';
ctx.fillRect(0, 0, 200, 200);	

# 12、动画

# 12.1 动画的基本步骤

1、清空 canvas 再绘制每一帧动画之前,需要清空所有。清空所有最简单的做法就是 clearRect() 方法。 2、保存 canvas 状态 如果在绘制的过程中会更改 canvas 的状态(颜色、移动了坐标原点等),又在绘制每一帧时都是原始状态的话,则最好保存下 canvas 的状态 3、绘制动画图形这一步才是真正的绘制动画帧 4、恢复 canvas 状态如果你前面保存了 canvas 状态,则应该在绘制完成一帧之后恢复 canvas 状态。

# 12.2 控制动画

我们可用通过 canvas 的方法或者自定义的方法把图像绘制到 canvas 上。正常情况,我们能看到绘制的结果是在脚本执行结束之后。例如,我们不可能在一个 for 循环内部完成动画。 也就是,为了执行动画,我们需要一些可以定时执行重绘的方法。 一般用到下面三个方法:

  1. setInterval()
  2. setTimeout()
  3. requestAnimationFrame()
上次更新: 1/22/2025, 9:39:13 AM