1. HTML Canvas 标签基础与工作原理
1.1 Canvas 标签的结构与属性
在网页中,Canvas标签充当一个像素绘图区域,为绘图提供一个独立的画布空间。它本身不包含图形,真正的绘制工作由 JavaScript 的上下文对象完成。
通过设置 width 与 height 属性可以确定画布的逻辑像素尺寸;通过 CSS 可以改变渲染尺寸,但这会影响清晰度,推荐保持原生分辨率并等比缩放。
Canvas 的绘制上下文通常是 2D 上下文,可通过 canvas.getContext('2d') 获得,提供直线、矩形、圆弧、渐变等绘制能力。
<canvas id="myCanvas" width="600" height="400"></canvas>
const canvas = document.getElementById('myCanvas');
const ctx = canvas.getContext('2d');1.2 初始化与绘图环境
在脚本中,获取上下文是第一步,随后可以开始绘制简单形状。没有上下文,就无法调用绘制 API。
为了避免像素拉伸,请确保画布的 CSS 尺寸与实际用途一致;同时要关注 设备像素比,以保证在高清屏上的绘制清晰度。
开始绘制前,常见的操作包括清屏、设置填充样式以及定义绘制的坐标系原点。
const w = canvas.width;
const h = canvas.height;
ctx.fillStyle = '#0095DD';
ctx.fillRect(50, 50, 150, 100);2. 动态绘图的核心:动画循环与帧更新
2.1 使用 requestAnimationFrame 进行帧循环
要实现动画,核心是建立一个持续更新画布的循环,requestAnimationFrame 可以在浏览器合适的时机执行绘制,从而获得平滑的帧率。
通过记录时间戳实现帧间隔控制,确保动画在不同设备上的表现趋于一致,这也是实现流畅动态绘图的关键。
let lastTime = 0;
function animate(time) {const dt = time - lastTime;lastTime = time;// 清空画布ctx.clearRect(0, 0, canvas.width, canvas.height);// 更新与绘制逻辑(示例)// ...requestAnimationFrame(animate);
}
requestAnimationFrame(animate);
通过这样的循环,可以实现平滑的动态绘图与复杂的动画效果,时间戳与 deltaTime 的概念在后续章节会频繁使用。
2.2 离屏绘制与双缓冲的概念
为了减少闪烁和重绘成本,可以使用 离屏 canvas,在一个隐藏画布上完成复杂绘制,再一次性渲染到前台。
离屏技术对于粒子系统、路径动画等场景尤为有用,能显著提升 渲染性能。
const offscreen = document.createElement('canvas');
offscreen.width = canvas.width;
offscreen.height = canvas.height;
const offCtx = offscreen.getContext('2d');// 绘制到离屏画布
offCtx.fillStyle = '#FF0';
offCtx.arc(100, 100, 50, 0, Math.PI * 2);
offCtx.fill();// 一次性绘制到前台
ctx.drawImage(offscreen, 0, 0);3. 进阶绘图技巧:路径、渐变、变换、纹理
3.1 路径与绘制基本图形
路径 API 提供了组合线段、曲线与弧线的能力,>通过 beginPath、moveTo、lineTo 等方法形成形状,最后用 stroke 或 fill 完成绘制。
在绘制复杂图形时,设置 线宽、描边颜色与 填充颜色 可以快速实现多样效果。
需要注意像素对齐和路径闭合,确保边界清晰且渲染正确。
ctx.beginPath();
ctx.moveTo(50, 50);
ctx.lineTo(200, 50);
ctx.lineTo(200, 150);
ctx.closePath();
ctx.lineWidth = 3;
ctx.strokeStyle = '#333';
ctx.stroke();3.2 渐变与阴影
通过 createLinearGradient、addColorStop 可以实现线性渐变,渐变对象可以赋给 fillStyle 或 strokeStyle,从而绘制渐变填充或描边。
将阴影属性结合起来,可以为图形增加层次感,例如设置 shadowColor、shadowBlur、shadowOffsetX、shadowOffsetY 等。
const g = ctx.createLinearGradient(0, 0, canvas.width, 0);
g.addColorStop(0, '#ff5f6d');
g.addColorStop(1, '#ffc371');
ctx.fillStyle = g;
ctx.fillRect(20, 20, 260, 120);3.3 坐标变换与旋转
坐标系变换让绘图更加灵活,常用方法包括 translate、rotate、scale,并通过 save / restore 保存与恢复状态。
通过变换可以实现旋转的图形、错切的效果、以及在局部坐标系中构建复杂的对象。

ctx.save();
ctx.translate(150, 150);
ctx.rotate(Math.PI / 6);
ctx.fillStyle = '#6a8';
ctx.fillRect(-50, -50, 100, 100);
ctx.restore();4. 动画实战案例:从简单到复杂
4.1 简单圆形移动
通过修改圆心坐标实现水平移动,结合边界检测实现循环动画,直观展示动态绘图的基本思路。
将绘制更新与帧循环结合,可以实现高效的圆形移动效果,且易于扩展到更复杂的形状。
let x = 0;
function animate(ts) {ctx.clearRect(0, 0, canvas.width, canvas.height);ctx.beginPath();ctx.arc(x, canvas.height/2, 20, 0, Math.PI*2);ctx.fill();x += 2;if (x > canvas.width) x = 0;requestAnimationFrame(animate);
}
requestAnimationFrame(animate);4.2 粒子系统基础
粒子系统是动态绘图的经典应用,借助大量小粒子在屏幕上随机移动与淡出,能够营造丰富的视觉效果。
通过更新粒子的速度、方向与寿命,可以实现爆炸、烟雾、火花等多种场景。
const particles = new Array(200).fill(0).map(() => ({x: Math.random() * canvas.width,y: Math.random() * canvas.height,vx: (Math.random() - 0.5) * 2,vy: (Math.random() - 0.5) * 2,life: Math.random() * 60 + 60
}));function update() {ctx.clearRect(0, 0, canvas.width, canvas.height);for (const p of particles) {p.x += p.vx; p.y += p.vy;p.life -= 1;ctx.fillStyle = 'rgba(255,255,255,'+ (p.life/60) +')';ctx.beginPath(); ctx.arc(p.x, p.y, 2, 0, Math.PI*2); ctx.fill();}requestAnimationFrame(update);
}
update();5. 性能优化与跨浏览器兼容性
5.1 最小化绘制区域与离屏渲染策略
在动态绘制中,尽量缩小重绘区域,仅对改变的区域进行 redraw,有助于提升复杂动画的帧率。
离屏渲染除了提升性能,也能减小前台渲染的工作量,需注意资源加载与上下文状态的一致性问题。
// 仅绘制需要刷新区域的示例
function paintDirtyRect(x, y, w, h) {ctx.clearRect(x, y, w, h);ctx.fillRect(x, y, w, h);
}
paintDirtyRect(10,10,100,100);
function resizeCanvasForDPR(canvas) {const dpr = window.devicePixelRatio || 1;const rect = canvas.getBoundingClientRect();canvas.width = rect.width * dpr;canvas.height = rect.height * dpr;const ctx = canvas.getContext('2d');ctx.scale(dpr, dpr);
}
resizeCanvasForDPR(canvas); 

