Canvas 何尝不是亮点呢?(一)
一、Canvas 是什么?
Canvas 是高性能渲染引擎。
<canvas>是一个位图渲染容器- 所有内容都通过 JS API 绘制
- 绘制完成后,浏览器只认识「像素」,不再关心你画了什么
- canvans 支持绘制的图形,总结起来有四种:
- 直线(矩形)
- 曲线(圆形)
- 文字
- 图片
这与 DOM(结构化、可访问)完全不同。
Canvas 和 DOM / SVG 的区别:
使用场景:
- 动画动效
- 编辑器(时间轴 / 画布)
- 数据 / 大屏可视化
- 大数据量下的表格(性能优化)
- 游戏
- 音视频波形、字幕
二、Canvas 基础
Canvas 是 命令式 + 无状态渲染
- 每一帧都要重新画完整画面
- Canvas 不会保存任何结构
-
简单示例
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<canvas id="canvas"></canvas>
<script>
// 获取 canvas 元素
const canvas = document.querySelector("#canvas");
// 获取 canvas 上下文
const ctx = canvas.getContext("2d");
// 初始化
function init() {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
}
init();
// ==设置初始值==
// 设置路径
ctx.beginPath();
// 起始路径点:从画布左上角开始移动到某个位置
ctx.moveTo(100, 200);
// 画线中途连接点
ctx.lineTo(200, 300);
// 画线下一个点
// ctx.lineTo(300, 350);
// 最后闭合,终点连接起点(可选)
// ctx.closePath();
// ==绘制行为==
// 示例一:画线
ctx.strokeStyle = "red";
ctx.stroke();
// 示例二:画圆(小的填充圆就是点)
ctx.beginPath();
ctx.arc(200, 300, 15, 0, 2 * Math.PI, true); // 圆心坐标x,圆心坐标y,半径,起始角度,结束角度(默认顺指针),是否逆时针
ctx.fillStyle = "green";
ctx.fill();
// 填充
// ctx.fillStyle = "blue";
// ctx.fill();
// 实例三:文字
// ctx.font = "32px sans-serif";
// ctx.fillText("123", 300, 400);
</script>
</body>
</html>
-
坐标系统
- 左上角
(0,0) - 向右:x 正方向
- 向下:y 正方向
-
常见 API
这里如果想要练手 canvas,我推荐两个项目:第一个是 vue3-particles(现在可能不叫这个名字了,总之就是粒子库/星空图的类似效果,第二个是代码雨/文字雨(canvas 版本),完全可以模仿着使用 canvas 复刻一下,能复刻出来,canvas 就算真正入门了。
彩蛋(😜):如果后续有时间,我还会分享一些我之前做过的“花里胡哨”的动效。
进阶同学可以研究下 gsap 这个动画库,配合 canvas 会非常好用,就可以 GSAP 管时间,Canvas 管空间。
/********************** 获取 Canvas & 上下文 **********************/
const canvas = document.getElementById('canvas'); // 获取 canvas DOM
const ctx = canvas.getContext('2d'); // 获取 2D 渲染上下文(核心对象)
/********************** 状态管理 **********************/
ctx.save(); // 保存当前绘图状态(样式、变换、裁剪区)
ctx.restore(); // 恢复到最近一次 save 的状态
ctx.resetTransform(); // 重置 transform(现代浏览器)
ctx.globalAlpha = 1; // 全局透明度(0~1)
ctx.globalCompositeOperation = 'source-over'; // 图层混合模式
/********************** 路径相关 **********************/
ctx.beginPath(); // 开启新路径
ctx.closePath(); // 闭合路径
ctx.moveTo(x, y); // 移动到指定点(不画线)
ctx.lineTo(x, y); // 从当前点画线到指定点
ctx.arc(x, y, r, start, end, ccw); // 画圆/圆弧
ctx.arcTo(x1, y1, x2, y2, r); // 圆角连接线
ctx.rect(x, y, w, h); // 画矩形路径
ctx.quadraticCurveTo(cpx, cpy, x, y); // 二次贝塞尔
ctx.bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y); // 三次贝塞尔
ctx.stroke(); // 描边
ctx.fill(); // 填充
ctx.clip(); // 将当前路径设为裁剪区域(遮罩核心)
/********************** 样式设置 **********************/
ctx.strokeStyle = '#fff'; // 描边颜色
ctx.fillStyle = '#000'; // 填充颜色
ctx.lineWidth = 2; // 线宽
ctx.lineCap = 'round'; // 线帽(butt | round | square)
ctx.lineJoin = 'round'; // 连接处样式
ctx.miterLimit = 10; // 尖角限制
ctx.shadowColor = 'rgba(0,0,0,0.3)'; // 阴影颜色
ctx.shadowBlur = 10; // 阴影模糊
ctx.shadowOffsetX = 5; // 阴影偏移
ctx.shadowOffsetY = 5;
/********************** 变换 **********************/
ctx.translate(x, y); // 平移
ctx.rotate(rad); // 旋转(弧度)
ctx.scale(sx, sy); // 缩放
ctx.transform(a, b, c, d, e, f); // 矩阵变换
ctx.setTransform(a, b, c, d, e, f); // 直接设置变换矩阵
/********************** 清屏 & 擦除 **********************/
ctx.clearRect(0, 0, canvas.width, canvas.height); // 每次重新 draw 的时候通常会清空指定区域
/********************** 文本 **********************/
ctx.font = 'bold 48px sans-serif'; // 字体
ctx.textAlign = 'center'; // 对齐方式
ctx.textBaseline = 'middle'; // 基线
ctx.fillText('Hello', x, y); // 填充文字
ctx.strokeText('Hello', x, y); // 描边文字
ctx.measureText('Hello'); // 测量文字宽度(字母动画必用)
/********************** 图片 **********************/
const img = new Image();
img.src = 'image.png';
ctx.drawImage(img, x, y); // 原尺寸绘制
ctx.drawImage(img, x, y, w, h); // 指定尺寸
ctx.drawImage(img, sx, sy, sw, sh, dx, dy, dw, dh); // 裁剪绘制(精灵图)
/********************** 渐变 & 图案 **********************/
const linearGradient = ctx.createLinearGradient(x0, y0, x1, y1);
linearGradient.addColorStop(0, '#ff0');
linearGradient.addColorStop(1, '#f00');
ctx.fillStyle = linearGradient;
const radialGradient = ctx.createRadialGradient(x0, y0, r0, x1, y1, r1);
radialGradient.addColorStop(0, '#fff');
radialGradient.addColorStop(1, '#000');
const pattern = ctx.createPattern(img, 'repeat'); // 平铺图案
/********************** 像素操作(性能敏感) **********************/
const imageData = ctx.getImageData(0, 0, w, h); // 读取像素
const data = imageData.data; // RGBA 数组
ctx.putImageData(imageData, x, y); // 写回像素
// data[i] -> R
// data[i + 1] -> G
// data[i + 2] -> B
// data[i + 3] -> A
/********************** 动画循环 **********************/
function render() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
// draw...
requestAnimationFrame(render);
// 不能使用 setInterval,因为 1. 不跟随屏幕刷新 2. 掉帧严重 3. 能耗高
}
render();
三、其他注意事项
-
分层思想
- 时间层(GSAP / Ticker)
- 逻辑层(业务逻辑、状态计算)
- 渲染层(Canvas draw)
-
canvas / 图片清晰度问题
样式尺寸:el.style.width
原始尺寸:el.naturalWidth(canvas.width)
页面缩放倍率:devicePixelRatio
☝️ 保证图片(canvas)清晰可以总结为一个等式:原始尺寸 = 样式尺寸 * 缩放倍率
所以在项目中,我们一般为初始化的 canvas 设置宽高为:
const dpr = window.devicePixelRatio || 1;
canvas.width = project.width * dpr;
canvas.height = project.height * dpr;
-
必须使用 requestAnimationFrame
function render() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
// draw...
requestAnimationFrame(render);
//
}
render();
不能使用 setInterval,因为:
- 不跟随屏幕刷新
- 掉帧严重
- 能耗高

浙公网安备 33010602011771号