Canvas API 学习记录

905 阅读12分钟

一、<canvas> 元素

在html中添加canvas元素

<canvas id="canvas" width="150" height="150">
    您的浏览器不支持 Canvas!
</canvas>

获取canvas,并获取其渲染上下文

const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
// 获取宽高
const { width, height } = canvas;

1.1 替换内容

“您的浏览器不支持 Canvas!” 代表的是替换内容, 不支持<canvas>的浏览器,将会显示该内容。

1.2 渲染上下文

const ctx = canvas.getContext('2d'); // 二维上下文
const ctx = canvas.getContext('webgl'); // 三维上下文

1.3 坐标系

左上角为坐标原点,x轴向右,y轴向下。

canvas 坐标系

二、形状绘制-矩形绘制

绘制一个填充的矩形,也就是一个实心的矩形:

ctx.fillRect(25, 25, 100, 100);

绘制一个矩形的边框,也就是一个空心的矩形:

ctx.strokeRect(50, 50, 50, 50);

清除指定矩形区域里内容:

ctx.clearRect(45, 45, 60, 60);

三个方法的参数都是 (x, y, width, height),并且这三个函数调用以后,会马上显示在canvas上,即时生效

三、形状绘制-路径绘制

ctx.beginPath(); // 开始绘制路径
ctx.moveTo(x, y); // 将画笔移动到指定的坐标上
ctx.closePath(); // 闭合路径,从当前点到开始点闭合路径
ctx.stroke(); // 绘制图形轮廓,不会自动闭合。
ctx.fill(); // 填充路径的内容区域,生成实心图形。会自动闭合。

生成路径步骤:

  1. 开始绘制路径 beginPath();
  2. 调用函数,指定绘制路径(直线、弧线等);
  3. 闭合路径closePath()、描边stroke()或填充路径fill()

注意

  • closePath() 不是必须的。
  • fill() 对于没有闭合的路径,会自动闭合;stroke() 不会自动闭合。
  • moveTo() 可以调用多次。

3.1 直线

ctx.lineTo(x, y); // 绘制一条从当前位置到(x,y)的直线

比如绘制一个三角形

ctx.beginPath();
ctx.moveTo(75, 50);
ctx.lineTo(100, 75);
ctx.lineTo(100, 25);
ctx.closePath();
ctx.stroke();

3.2 弧线

绘制圆弧路径。对于圆弧的弧度,x轴方向为 0,顺时针为正,逆时针为负。

/**
 * 绘制圆弧路径
 * @param {number} x 圆弧中心(圆心)的 x 轴坐标。
 * @param {number} y 圆弧中心(圆心)的 y 轴坐标。
 * @param {number} radius 圆弧的半径。
 * @param {number} startAngle 圆弧的起始点, x轴方向开始计算,单位以弧度表示。
 * @param {number} endAngle 圆弧的终点, 单位以弧度表示。
 * @param {boolean} anticlockwise 如果为 true,逆时针绘制圆弧,反之,顺时针绘制。默认为顺时针。
 */
ctx.arc(x, y, radius, startAngle, endAngle, anticlockwise);

根据控制点和半径绘制圆弧路径

/**
 * 根据控制点和半径绘制圆弧路径
 * @param {number} x1 第一个控制点的 x 轴坐标。
 * @param {number} y1 第一个控制点的 y 轴坐标。
 * @param {number} x2 第二个控制点的 x 轴坐标。
 * @param {number} y2 第二个控制点的 y 轴坐标。
 * @param {number} radius 圆弧的半径。
 */
ctx.arcTo(x1, y1, x2, y2, radius);

根据当前描点与给定的控制点1连接的直线,和控制点1与控制点2连接的直线,作为使用指定半径的圆的切线,画出两条切线之间的弧线路径

注意: 弧度=(Math.PI/180)*角度

3.3 贝塞尔曲线

二阶贝塞尔曲线

/**
 * 绘制二次贝塞尔曲线
 * @param {number} cp1x 控制点的 x 轴坐标。
 * @param {number} cp1y 控制点的 y 轴坐标。
 * @param {number} x 结束点的 x 轴坐标。
 * @param {number} y 结束点的 y 轴坐标。
 */
ctx.quadraticCurveTo(cp1x, cp1y, x, y);

三阶贝塞尔曲线

/**
 * 绘制三次贝塞尔曲线
 * @param {*} cp1x 控制点1的 x 轴坐标。
 * @param {*} cp1y 控制点1的 y 轴坐标。
 * @param {*} cp2x 控制点2的 x 轴坐标。
 * @param {*} cp2y 控制点2的 y 轴坐标。
 * @param {*} x 结束点的 x 轴坐标。
 * @param {*} y 结束点的 y 轴坐标。
 */
ctx.bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y);

贝塞尔曲线

3.4 矩形

和前面的绘制矩形不同,这个是将矩形的路径添加到当前的路径上。

ctx.rect(x, y, width, height);

注意:当该方法执行的时候,moveTo()方法自动设置坐标参数(0,0)。也就是说,当前笔触自动重置回默认坐标。

四、样式和颜色

4.1 颜色 Color

设置图形的填充颜色

ctx.fillStyle = color;

设置图形轮廓的颜色

ctx.strokeStyle = color;

颜色可以设置为符合 CSS3颜色值标准 的有效字符串,比如 orange#FFA500rgb(255,165,0)rgba(255,165,0,1)

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

4.2 透明度 Transparency

在设置颜色的时候,可以设置透明的颜色,比如

ctx.strokeStyle = "rgba(255,0,0,0.5)";

4.3 线型 Line Style

ctx.lineWidth = value; //设置线条宽度
ctx.lineCap = type; //设置线条末端样式
ctx.lineJoin = type; // 设定线条与线条间接合处的样式
ctx.setLineDash(segments); // 设置当前虚线样式
ctx.getLineDash(); // 返回一个包含当前虚线样式,长度为非负偶数的数组
ctx.lineDashOffset = value; //设置虚线样式的起始偏移量

4.3.1 线条宽度

宽度为1.0的线,边缘较为模糊的原因:

如果你想要绘制一条从 (3,1) 到 (3,5),宽度是 1.0 的线条,你会得到像第二幅图一样的结果。实际填充区域(深蓝色部分)仅仅延伸至路径两旁各一半像素。而这半个像素又会以近似的方式进行渲染,这意味着那些像素只是部分着色,结果就是以实际笔触颜色一半色调的颜色来填充整个区域(浅蓝和深蓝的部分)。这就是为何宽度为 1.0 的线并不准确的原因。

image

4.3.2 lineCap 线端样式

样式有:butt,round 和 square。默认是 butt。

image

4.3.3 lineJoin

样式有round, bevel 和 miter。默认是 miter。

image

4.3.4 虚线

参数 segments,表示一组描述交替绘制线段和间距(坐标空间单位)长度的数字。 如果数组元素的数量是奇数, 数组的元素会被复制并重复。

从上到下segments参数分别是:

ctx.setLineDash([]);
ctx.setLineDash([1, 1]);
ctx.setLineDash([10, 10]);
ctx.setLineDash([20, 5]);
ctx.setLineDash([15, 3, 3, 3]);
ctx.setLineDash([20, 3, 3, 3, 3, 3, 3, 3]);
ctx.setLineDash([12, 3, 3]);  // Equals [12, 3, 3, 12, 3, 3]

image

4.4 阴影 Shadows

ctx.shadowOffsetX = float; // 阴影在 X 轴的延伸距离
ctx.shadowOffsetY = float; // 阴影在 Y 轴的延伸距离
ctx.shadowBlur = float; // 设定阴影的模糊程度
ctx.shadowColor = color; // 设定阴影颜色效果

注意:shadowOffsetX 和 shadowOffsetY,负值表示阴影会往上或左延伸,正值则表示会往下或右延伸

4.5 渐变 Gradients

渐变可以用来填充和描边。创建 CanvasGradient 对象,再赋值给fillStyle 或 strokeStyle 属性

  1. 创建 CanvasGradient 对象。
/**
 * 线性渐变
 * @param {*} x1 渐变起点 x
 * @param {*} y1 渐变起点 y
 * @param {*} x2 渐变终点 x
 * @param {*} y2 渐变终点 y
 */
var lineargradient = ctx.createLinearGradient(x1, y1, x2, y2);
/**
 * 放射性渐变
 * @param {*} x0 开始圆形的 x 轴坐标。
 * @param {*} y0 开始圆形的 y 轴坐标。
 * @param {*} r0 开始圆形的半径。
 * @param {*} x1 结束圆形的 x 轴坐标。
 * @param {*} y1 结束圆形的 y 轴坐标。
 * @param {*} r1 结束圆形的半径。
 */
var radialgradient = ctx.createRadialGradient(x0, y0, r0, x1, y1, r1);
  1. 上色
/**
 * 添加渐变颜色
 * @param {*} position 渐变中颜色所在的相对位置, 0.0 ~ 1.0 之间的值。
 * @param {*} color 有效的 CSS 颜色值
 */
gradient.addColorStop(position, color);
  1. 赋值给 fillStyle 或 strokeStyle
ctx.fillStyle = lineargradient;
ctx.strokeStyle = radialgradient;

4.6 图案样式 Patterns

使用指定的图像创建模式

/**
 * 
 * @param {*} image 作为重复图像源的 CanvasImageSource 对象
 * @param {*} type 指定如何重复图像: repeat,repeat-x,repeat-y, no-repeat
 */
ctx.createPattern(image, type)

使用方式:

// 创建新 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);
}

注意:与 drawImage 有点不同,createPattern 需要确认 image 对象已经 onload,否则图案可能效果不对的。

4.7 填充规则

  • "nonzero",默认值
  • "evenodd"
ctx.fill("evenodd");

五、文本绘制

/**
 * 填充指定文本
 * @param {number} text 文本
 * @param {number} x 
 * @param {number} y 
 * @param {number} maxWidth 
 */
ctx.fillText(text, x, y, maxWidth);
/**
 * 绘制指定文本边框
 * @param {number} text 文本
 * @param {number} x 
 * @param {number} y 
 * @param {number} maxWidth 
 */
ctx.strokeText(text, x, y, maxWidth);

5.1 文本样式

ctx.font = value; // 字体样式,和 CSS font 属性相同的语语法
ctx.textAlign = value; // 文本对齐选项, 默认值是 start
ctx.textBaseline = value; // 基线对齐选项, 默认值是 alphabetic
ctx.direction = value; // 文本方向, ltr, rtl, inherit, 默认值是 inherit

5.1.1 textAlign

可选的值包括:start, end, left, right or center

image

5.1.2 textBaseline

可选的值包括:top, hanging, middle, alphabetic, ideographic, bottom。

image

5.2 测量文本宽度

测量出来的数据和文本样式有关

var text = ctx.measureText("foo"); // TextMetrics object
console.log(text.width);

六、图片绘制

6.1 图片来源

  • HTMLImageElement。由 Image() 构造出来的,或 <img> 元素。需要 onload 以后才能绘制。
  • HTMLVideoElement。<video> 元素,从视频中抓取当前帧。即使视频是不可见的。
  • HTMLCanvasElement。<canvas> 元素。
  • ImageBitmap。高性能位图,可由上述元素或其他来源生成。

6.2 绘制方法

方式一

/**
 * 绘制图片
 * @param {*} image 图片源
 * @param {*} x 在目标 canvas 里的起始坐标 x
 * @param {*} y 在目标 canvas 里的起始坐标 y
 */
ctx.drawImage(image, x, y);

方式二,可以缩放图片的大小

/**
 * 绘制图片
 * @param {*} image 图片源
 * @param {*} x 在目标 canvas 里的起始坐标 x
 * @param {*} y 在目标 canvas 里的起始坐标 y
 * @param {*} width 目标 canvas 画入时应该缩放的大小 width
 * @param {*} height 目标 canvas 画入时应该缩放的大小 height
 */
ctx.drawImage(image, x, y, width, height);

方式三,可做切片显示

 /**
  * 绘制图片
  * @param {*} image 图片源
  * @param {*} sx image的矩形(裁剪)选择框的左上角 X 轴坐标。
  * @param {*} sy image的矩形(裁剪)选择框的左上角 Y 轴坐标
  * @param {*} sWidth image的矩形(裁剪)选择框的宽度
  * @param {*} sHeight image的矩形(裁剪)选择框的高度
  * @param {*} dx image的左上角在目标canvas上 X 轴坐标
  * @param {*} dy image的左上角在目标canvas上 Y 轴坐标。
  * @param {*} dWidth image在目标canvas上绘制的宽度
  * @param {*} dHeight image在目标canvas上绘制的高度
  */
ctx.drawImage(image, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight);

可以参考下图:

image

注意:图片跨域问题,需要使用 crossOrigin 属性,并且图片服务器需要允许跨域。

七、Transformations(变形)

7.1 状态保存和恢复

ctx.save(); // 保存画布(canvas)的所有状态
ctx.restore(); // 恢复 canvas 状态

canvas 的状态就是当前应用的所有样式和形变的一个快照。每当 save 以后,当前状态就被推到栈中保存,调用restore,上一个保存的状态就栈中弹出。

可保存的状态有:

  • 形变(移动、旋转、缩放)
  • 裁剪路径
  • 样式、颜色、字体等属性

7.2 移动 Translating

用来移动canvas的坐标原点。

ctx.translate(x, y);

7.3 旋转 Rotating

以原点为中心旋转 canvas

ctx.rotate(angle); // angle 顺时针方向的,以弧度为单位

7.4 缩放 Scaling

增减图形在 canvas 中的像素数目,对形状,位图进行缩小或者放大

ctx.scale(x, y);

将坐标移动到 canvas 中心位置,并将y轴指向改为向上。

const { width, height } = canvas;
ctx.translate(0.5 * width, 0.5 * height);
ctx.scale(1, -1);

7.5 变形 Transforms

/**
 * 缩放、旋转、移动和倾斜上下文
 * @param {*} a (m11) 水平缩放。
 * @param {*} b (m12) 垂直倾斜。
 * @param {*} c (m21) 水平倾斜。
 * @param {*} d (m22) 垂直缩放。
 * @param {*} e (dx)  水平移动。
 * @param {*} f (dy)  垂直移动。
 */
ctx.transform(a, b, c, d, e, f);

参数构成了该矩阵:

\left[ \begin{array}{ccc} a & c & e \\ b & d & f \\ 0 & 0 & 1 \end{array} \right]
ctx.setTransform(a, b, c, d, e, f); //更改当前形变。

这个方法会将当前的变形矩阵重置为单位矩阵,然后用相同的参数调用 transform 方法。从根本上来说,该方法是取消了当前变形,然后设置为指定的变形,一步完成。

ctx.resetTransform(); // 重置当前形变。

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

八、合成与裁剪

8.1 合成

在已有图形上,再画新图形,使用不同的策略,会有不一样的效果。

ctx.globalCompositeOperation = type; // 修改遮盖策略,默认 source-over

共有12个参数,各个参数下的效果示例

8.2 裁剪路径

ctx.clip(); // 将当前的路径转换为裁剪路径

在路径以外的部分都不会在 canvas 上绘制出来

九、基本动画

9.1 动画的基本步骤:

  1. 清空 canvas。最简单的做法就是用 clearRect 方法。
  2. 保存 canvas 状态
  3. 绘制动画图形(animated shapes)
  4. 恢复 canvas 状态

9.2 操控动画

定时执行重绘的方法

setInterval(function, delay);
setTimeout(function, delay);
requestAnimationFrame(callback); // 推荐

十、像素操作

10.1 ImageData 对象

ImageData 对象中存储着 canvas 对象真实的像素数据。

// 只读属性:
width // 图片宽度,单位是像素
height // 图片高度,单位是像素
data // 数组,存储像素相信

data 包含的数据个数为: 高度 × 宽度 × 4

// 根据行列(从0开始),获取指定像素的R/G/B/A的值
imageData.data[((row * (imageData.width * 4)) + (col * 4)) + 0/1/2/3];

10.2 创建 ImageData 对象

创建空白的ImageData对象,所有像素被预设为透明黑,即每个像素的RGBA为(0,0,0,0)。

var myImageData = ctx.createImageData(width, height);

创建一个和anotherImageData对象相同像素的ImageData对象,所有像素被预设为透明黑。这个并非复制对象。

var myImageData = ctx.createImageData(anotherImageData);

10.3 获取场景像素数据

var myImageData = ctx.getImageData(left, top, width, height);

画布之外的元素会被设置成透明的黑。

10.4 更改场景中的像素数据

/**
 * 更改场景中的像素数据
 * @param { ImageData } imageData 包含像素值的数组对象
 * @param {*} dx x 轴方向的偏移量
 * @param {*} dy y 轴方向的偏移量
 */
ctx.putImageData(imageData, dx, dy);

10.4.1 图片灰度

  var grayscale = function() {
    for (var i = 0; i < imageData.length; i += 4) {
      var avg = (imageData[i] + imageData[i +1] + imageData[i +2]) / 3;
      imageData[i]     = avg; // red
      imageData[i + 1] = avg; // green
      imageData[i + 2] = avg; // blue
    }
    ctx.putImageData(imageData, 0, 0);
  };

10.4.2 图片反色

  var invert = function() {
    for (var i = 0; i < imageData.length; i += 4) {
      imageData[i]     = 255 - imageData[i];     // red
      imageData[i + 1] = 255 - imageData[i + 1]; // green
      imageData[i + 2] = 255 - imageData[i + 2]; // blue
    }
    ctx.putImageData(imageData, 0, 0);
  };

10.5 反锯齿

zoomctx.imageSmoothingEnabled = value; // 图片是否平滑

true表示图片平滑(默认值),false表示图片不平滑

10.6 保存图片

生成 PNG 图片

canvas.toDataURL('image/png');

生成 JPG 图片

canvas.toDataURL('image/jpeg', quality); // quality, 0~1, 1表示最好品质

创建一个Blob对像

/**
 * 创建一个Blob对像
 * @param {function} callback 回调函数,可获得一个单独的Blob对象参数。
 * @param {string} type 图片格式,默认格式为image/png。
 * @param {number} encoderOptions 值在0与1之间。图片格式为image/jpeg或者image/webp时用来指定图片展示质量。
 */
canvas.toBlob(callback, type, encoderOptions)

十一、优化

  • 在离屏canvas上预渲染相似的图形或重复的对象
  • 避免浮点数的坐标点,用整数取而代之
  • 不要在用drawImage时缩放图像
  • 使用多层canvas去画一个复杂的场景
  • 用CSS设置大的背景图
  • 用CSS transforms特性缩放画布
  • 关闭透明度
  • 将画布的函数调用集合到一起(例如,画一条折线,而不要画多条分开的直线)
  • 避免不必要的画布状态改变
  • 渲染画布中的不同点,而非整个新状态
  • 尽可能避免 shadowBlur特性
  • 尽可能避免text rendering
  • 尝试不同的方法来清除画布(clearRect() vs. fillRect() vs. 调整canvas大小)
  • 有动画,请使用window.requestAnimationFrame() 而非window.setInterval()
  • 请谨慎使用大型物理库

十二、参考