Canvas 绘图秘籍:你必须知道的技巧

614 阅读9分钟

简介

在HTML5技术框架中,Canvas 这一元素被普遍译为“画布”,其核心功能在于网页环境下的图形绘制。Canvas元素相较于传统图像格式,展现出其动态性、互动性及可扩展性的显著优势,因此在游戏制作、数据图形化展示、动画制作等多个领域获得了推广。

动画方面

数据可视化方面

游戏方面

基本语法

鉴于不同浏览器对HTML5标准的支持存在差异,开发者通常在 标签内嵌入辅助性的HTML代码。在兼容Canvas的浏览器中,这些内部代码将被忽略;而在不支持Canvas的浏览器上,这些代码则会被展示出来。在开展Canvas编程之前,必须通过canvas.getContext方法来检验浏览器的兼容性。

兼容性如下图

当没有设置宽度和高度时,canvas 会初始化宽300px150px

<canvas id="test-canvas" width="200" heigth="100">
  <p>你的浏览器不支持Canvas</p>
</canvas>
let canvas = document.getElementById('test-canvas');
if (canvas.getContext) {
  console.log('你的浏览器支持Canvas!');
  // 进行后续操作
} else {
  console.log('你的浏览器不支持Canvas!');
}

基础图形绘制

掌握Canvas技术,其入门步骤是学习绘制基础图形。

坐标系

在绘制基础图形之前,需要先搞清楚 Canvas 使用的坐标系。

该系统与常见的数学直角坐标系在X轴上保持一致,但在Y轴的方向上相反,其中W3C坐标系的Y轴正方向是向下的。

圆点

绘图的原点,即圆点的定位,是图形绘制和变换的基础。默认情况下,该点位于坐标 (0,0)

直线

要在画布上绘制直线,请使用以下方法:

  • moveTo(x,y) - 定义线的起点
  • lineTo(x,y) - 定义线的终点

要实际绘制线条,您必须使用“颜料”方法之一,比如 stroke()

const canvas = document.getElementById("myCanvas");
const ctx = canvas.getContext("2d");

ctx.moveTo(0, 0);
ctx.lineTo(200, 100);
ctx.stroke();

矩形

context.rect(x, y, width, height)

x 矩形左上角的 x 坐标。

y 矩形左上角的 y 坐标。

width 矩形的宽度,以像素为单位。

height 矩形的高度,以像素为单位。

const canvas = document.getElementById("myCanvas");
const ctx = canvas.getContext("2d");

ctx.beginPath();
ctx.rect(20, 20, 150, 100);
ctx.stroke();

三角形/其他形状

三角形

ctx.beginPath();

ctx.moveTo(100,20);
ctx.lineTo(175,100);
ctx.lineTo(20,100);
ctx.lineTo(100,20);

ctx.stroke();

不规则图形

const canvas = document.getElementById("myCanvas");
const ctx = canvas.getContext("2d");

ctx.beginPath();
ctx.moveTo(20,20);
ctx.lineTo(100,20);
ctx.lineTo(175,100);
ctx.lineTo(20,100);
ctx.lineTo(20,20);
ctx.stroke();

圆形/弧线

弧线

const canvas = document.getElementById("myCanvas");
const ctx = canvas.getContext("2d");

ctx.beginPath();
ctx.arc(100, 50, 50, 0, Math.PI);
ctx.stroke();

圆形

圆是一种特殊的弧线: 要创建圆,请将起始角度设置为 0结束角度设置为 2 * Math.PI

要在画布上绘制圆形,请使用以下方法:

  • beginPath() - 开始一条路径
  • arc(x,y,r,startangle,endangle) - 创建圆弧/曲线

const canvas = document.getElementById("myCanvas");
const ctx = canvas.getContext("2d");

ctx.beginPath();
ctx.arc(95, 50, 40, 0, 2 * Math.PI);
ctx.stroke();

渐变

createLinearGradient

创建线性渐变。

const c = document.getElementById("myCanvas");
const ctx = c.getContext("2d");

// 创建渐变
const grd = ctx.createLinearGradient(0, 0, 200, 0);
grd.addColorStop(0, "red");
grd.addColorStop(1, "white");

// 用渐变填充
ctx.fillStyle = grd;
ctx.fillRect(10, 10, 150, 80);

createRadialGradient

创建径向/圆形渐变。

const c = document.getElementById("myCanvas");
const ctx = c.getContext("2d");

// 创建渐变
const grd = ctx.createRadialGradient(75, 50, 5, 90, 60, 100);
grd.addColorStop(0, "red");
grd.addColorStop(1, "white");

// 用渐变填充
ctx.fillStyle = grd;
ctx.fillRect(10, 10, 150, 80);

文本

要在画布上绘制文本,最重要的属性和方法是:

  • font - 定义文本的字体属性
  • fillText(text,x,y) - 在画布上绘制“填充的”文本
  • strokeText(text,x,y) - 在画布上绘制文本(无填充)

fillText

将字体设置为 "30px Arial",并在画布上写入填充的文本:

const canvas = document.getElementById("myCanvas");
const ctx = canvas.getContext("2d");

ctx.font = "30px Arial";
ctx.fillText("Hello World", 10, 50);

strokeText

将字体设置为 "30px Arial",并在画布上写入文本(不填充):

const canvas = document.getElementById("myCanvas");
const ctx = canvas.getContext("2d");

ctx.font = "30px Arial";
ctx.strokeText("Hello World", 10, 50);

图片

要在画布上绘制图像,请使用以下方法:

  • drawImage(image,x,y)

window.onload = function() {
  const canvas = document.getElementById("myCanvas");
  const ctx = canvas.getContext("2d");
  const img = document.getElementById("tulip");
  ctx.drawImage(img, 10, 10);
};

样式

lineWidth

lineWidth 属性定义在画布中绘制时要使用的线条宽度。

必须在调用 stroke() 方法之前设置它。

const canvas = document.getElementById("myCanvas");
const ctx = canvas.getContext("2d");

ctx.beginPath();
ctx.moveTo(0, 0);
ctx.lineTo(200, 100);
ctx.lineWidth = 10; // 这行代码
ctx.stroke();

strokeStyle

strokeStyle 属性定义在画布中绘制时要使用的样式。

必须在调用 stroke() 方法之前设置它。

const canvas = document.getElementById("myCanvas");
const ctx = canvas.getContext("2d");

ctx.beginPath();
ctx.moveTo(0, 0);
ctx.lineTo(200, 100);
ctx.lineWidth = 10;
ctx.strokeStyle = "red"; // 这行代码
ctx.stroke();

ctx.beginPath();

// 定义矩形
ctx.moveTo(20,20);
ctx.lineTo(175,20);
ctx.lineTo(175,100);
ctx.lineTo(20,100);
ctx.lineTo(20,20);

// 定义三角形
ctx.moveTo(100,20);
ctx.lineTo(175,100);
ctx.lineTo(20,100);
ctx.lineTo(100,20);

ctx.strokeStyle = "red"; // 这行代码
ctx.stroke();

lineCap

lineCap 属性定义线的端部样式(buttroundsquare)。

默认为 square(方形)。

必须在调用 stroke() 方法之前设置它。

const canvas = document.getElementById("myCanvas");
const ctx = canvas.getContext("2d");

ctx.beginPath();
ctx.moveTo(0,0);
ctx.lineTo(175,75);
ctx.lineWidth = 10;
ctx.lineCap = "round"; // 这行代码
ctx.stroke();

textAlign

居中文本

const canvas = document.getElementById("myCanvas");
const ctx = canvas.getContext("2d");

ctx.font = "30px Comic Sans MS";
ctx.fillStyle = "red";
ctx.textAlign = "center"; // 这行代码
ctx.fillText("Hello World", canvas.width/2, canvas.height/2);

形变

操作canvas进行形变之前,最好使用save和restore保存canvas状态。

save

保存画布 (canvas) 的所有状态。

画布状态:strokeStyle, fillStyle, globalAlpha, lineWidth, lineCap, lineJoin, miterLimit, lineDashOffset, shadowOffsetX, shadowOffsetY, shadowBlur, shadowColor, globalCompositeOperation, font, textAlign, textBaseline, direction, imageSmoothingEnabled 等

restore

save 和 restore 方法是用来保存和恢复 canvas 状态的,都没有参数。Canvas 的状态就是当前画面应用的所有样式和变形的一个快照。

save、restore一般成对出现!

translate

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

function draw() {
  const ctx = document.getElementById("canvas").getContext("2d");

  // left rectangles, rotate from canvas origin
  ctx.save();
  // blue rect
  ctx.fillStyle = "#0095DD";
  ctx.fillRect(30, 30, 100, 100);
  ctx.rotate((Math.PI / 180) * 25);
  // grey rect
  ctx.fillStyle = "#4D4E53";
  ctx.fillRect(30, 30, 100, 100);
  ctx.restore();

  // right rectangles, rotate from rectangle center
  // draw blue rect
  ctx.fillStyle = "#0095DD";
  ctx.fillRect(150, 30, 100, 100);

  ctx.translate(200, 80); // translate to rectangle center
  // x = x + 0.5 * width
  // y = y + 0.5 * height
  ctx.rotate((Math.PI / 180) * 25); // rotate
  ctx.translate(-200, -80); // translate back

  // draw grey rect
  ctx.fillStyle = "#4D4E53";
  ctx.fillRect(150, 30, 100, 100);
}

scale

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

总结

api

方法描述绘制
beginPath()开始一条路径
closePath()关闭当前路径
moveTo()移动到一个点
lineTo()画线到另一个点
arc()定义曲线****
stroke()做图
fillRect()填充矩形
strokeRect()绘制矩形轮廓
clearRect()清除矩形区域内的绘制
save()保存当前的绘图状态
restore()恢复之前保存的绘图状态
fillText()填充文本
strokeText()绘制文本轮廓
skew()对当前绘图进行缩放
rotate()对当前绘图进行旋转
translate()对当前绘图进行平移

属性

方法描述常用选项默认值
strokeStyle设置填充样式********
lineStyle设置线条样式********
globalAlpha设置全局透明度********
lineWidth设置线条宽度********
lineCap设置线条结束端点样式butt、round、squarebutt
lineJoin设置线条连接处样式round、bevel、mittmiter
font设置或返回文本内容的字体属性********
textAlign设置或返回文本内容的对齐方式********
textBaseLine设置或返回文本内容的基线********

其他常用属性可以打印查看:

const ctx = canvas.getContext("2d")

console.log("ctx属性", ctx);

小技巧

  1. 形变前,执行状态保存操作。

  2. console.log(' %O', canvas); 可以打印Canvas元素中的属性。

  3. 绘制动画步骤

    1. 用 clearRect(x,y, w,h)方法,清空 canvas
    2. 保存 canvas 状态
    3. 修改 canvas 状态 (样式、移动坐标、旋转等)
    4. 绘制秒针图形(即绘制动画中的一帧)
    5. 恢复 canvas 状态

性能优化

使用requestAnimation

requestAnimationFrame最适用于需要连续高频执行的动画,如游戏开发,数据可视化动画等。它与浏览器刷新周期保持一致,不会因为间隔时间不均匀而导致动画卡顿。相较于setTimeout或setInterval,以下是其优势

  1. 不会失帧,动画不会卡顿。 setTimeout和setInterval由于执行异步任务,可能造成动画卡顿。而requestAnimationFrame则与显示器的刷新率同步,确保函数的执行频率与显示器刷新频率相匹配(例如,60Hz的显示器大约每16.7毫秒执行一次,而75Hz的显示器则大约每13.5毫秒执行一次)。
  2. CPU节能。 当页面处于非激活状态时,requestAnimationFrame不会执行,从而降低CPU的能耗。
  3. 自带节流。 该函数只在浏览器刷新周期内执行一次,保证了效率。
const animation = () => {
  // 执行动画
  requestAnimationFrame(animation); 
}

requestAnimationFrame(animation); 

离屏绘制

利用 drawImage 可以接收 Canvas 对象的特点,将对象绘制在一个未插入页面的 Canvas 中,然后每一帧更新时利用这个 Canvas 来绘制。

// 在离屏 canvas 上绘制
var canvasOffscreen = document.createElement('canvas');
canvasOffscreen.width = dw;
canvasOffscreen.height = dh;
canvasOffscreen.getContext('2d').drawImage(image, sx, sy, sw, sh, dx, dy, dw, dh);
 
// 在绘制每一帧的时候,绘制这个图形
context.drawImage(canvasOffscreen, x, y);

使用 Web Worker

动画出现卡顿的实质在于处理任务所需时间超过了浏览器的渲染时间,进而造成进程阻塞,用户在视觉上感受到延迟。根据用户的容忍度,阻塞现象可分为轻微阻塞(小卡顿)和严重阻塞(大卡顿)。轻微的卡顿,如页面加载时的短暂延迟,用户尚可接受;而严重的卡顿,如按钮点击后长时间无响应,则不可忍受。

对于轻微卡顿,可以通过代码优化来改善;而对于严重卡顿,常用的解决策略是采用webWorker技术。我们可以将浏览器比喻为快递车辆,其中阻塞渲染的任务相当于一件体积庞大的快递。通过webWorker技术,我们可以将这件大快递分解为多个小件,以减轻浏览器的负担。

常用库推荐

Canvas技术的诞生可谓是让绘图技术如虎添翼,本文将推荐一系列的库,希望助你在Canvas绘图时寻得一把趁手的利器。

Fabric.js

Fabric.js — 一个能够让你轻而易举操作canvas的神奇的库。

Konva.js

Konva.js 是一个强大的HTML5 Canvas库,用于创建交互式的图形和动画。它提供了一个简单易用的API,可以用于绘制图形、处理用户交互、添加动画效果等。

Pixi.js

Pixi.js 一个适合写游戏的前端渲染框架。

思维导图

最后,给出本文内的思维导图,以帮助读者更好地理解概念。

最后

贴出开头的几个动画代码,若有需要可自取~

贪吃蛇

循环全景

鼠标追踪动画

本文到此就结束了,希望对你理解Canvas有所帮助,感谢你的阅读!!!