Canvas 使用完全指南
目录
基础概念
绘制功能
高级特性
交互与事件
性能优化
调试与测试
工程实践
基础概念
Canvas 基础
Canvas 是 HTML5 提供的一个用于绘制图形的元素,它提供了一个可以使用 JavaScript 来绘制图形的位图表面。
// 创建 Canvas 元素
const canvas = document.createElement('canvas');
canvas.width = 800; // 设置画布宽度
canvas.height = 600; // 设置画布高度
document.body.appendChild(canvas);
// 获取绘图上下文
const ctx = canvas.getContext('2d');
渲染上下文
Canvas 的渲染上下文是实际执行绘图操作的接口,它提供了所有绘图方法和属性。
// 基本绘图属性
ctx.fillStyle = 'red'; // 设置填充颜色
ctx.strokeStyle = 'blue'; // 设置描边颜色
ctx.lineWidth = 2; // 设置线条宽度
// 常用绘图方法
ctx.fillRect(x, y, width, height); // 填充矩形
ctx.strokeRect(x, y, width, height); // 描边矩形
ctx.clearRect(x, y, width, height); // 清除区域
坐标系统
Canvas 使用笛卡尔坐标系,原点(0,0)位于画布左上角。
// 坐标系变换
ctx.translate(x, y); // 移动原点
ctx.rotate(angle); // 旋转坐标系
ctx.scale(x, y); // 缩放坐标系
// 保存和恢复坐标系状态
ctx.save(); // 保存当前状态
ctx.restore(); // 恢复之前的状态
绘制状态
Canvas 维护了一个状态栈,包含了当前的样式、变换和其他属性。
// 状态属性
ctx.globalAlpha = 0.5; // 全局透明度
ctx.globalCompositeOperation = 'source-over'; // 合成操作
// 状态管理
ctx.save(); // 保存当前状态到栈
// 进行一些绘制操作...
ctx.restore(); // 恢复之前保存的状态
动画循环
Canvas 动画通常使用 requestAnimationFrame 来实现平滑的动画效果。
function animate() {
// 清除画布
ctx.clearRect(0, 0, canvas.width, canvas.height);
// 更新动画状态
update();
// 重新绘制
draw();
// 请求下一帧
requestAnimationFrame(animate);
}
// 启动动画循环
animate();
绘制功能
基本形状
Canvas 提供了多种基本形状的绘制方法。
// 矩形
ctx.fillRect(x, y, width, height); // 填充矩形
ctx.strokeRect(x, y, width, height); // 描边矩形
// 圆形和圆弧
ctx.beginPath();
ctx.arc(x, y, radius, startAngle, endAngle, anticlockwise);
ctx.fill(); // 填充
ctx.stroke(); // 描边
// 直线
ctx.beginPath();
ctx.moveTo(x1, y1);
ctx.lineTo(x2, y2);
ctx.stroke();
路径绘制
路径是 Canvas 中最灵活的绘图方式,可以创建复杂的形状。
// 创建自定义路径
ctx.beginPath();
ctx.moveTo(x1, y1); // 起点
ctx.lineTo(x2, y2); // 直线
ctx.quadraticCurveTo(cpx, cpy, x, y); // 二次贝塞尔曲线
ctx.bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y); // 三次贝塞尔曲线
ctx.closePath(); // 闭合路径
// 路径绘制
ctx.fill(); // 填充路径
ctx.stroke(); // 描边路径
颜色和样式
Canvas 支持多种颜色格式和样式设置。
// 颜色格式
ctx.fillStyle = 'red'; // 命名颜色
ctx.fillStyle = '#ff0000'; // 十六进制
ctx.fillStyle = 'rgb(255, 0, 0)'; // RGB
ctx.fillStyle = 'rgba(255, 0, 0, 0.5)'; // RGBA
// 线条样式
ctx.lineWidth = 2; // 线宽
ctx.lineCap = 'round'; // 线条端点样式
ctx.lineJoin = 'miter'; // 线条连接样式
ctx.miterLimit = 10; // 斜接限制
渐变和图案
Canvas 支持线性渐变、径向渐变和图案填充。
// 线性渐变
const gradient = ctx.createLinearGradient(x1, y1, x2, y2);
gradient.addColorStop(0, 'white');
gradient.addColorStop(1, 'black');
ctx.fillStyle = gradient;
// 径向渐变
const radialGradient = ctx.createRadialGradient(x1, y1, r1, x2, y2, r2);
radialGradient.addColorStop(0, 'white');
radialGradient.addColorStop(1, 'black');
ctx.fillStyle = radialGradient;
// 图案
const img = new Image();
img.src = 'pattern.png';
img.onload = () => {
const pattern = ctx.createPattern(img, 'repeat');
ctx.fillStyle = pattern;
};
文本渲染
Canvas 提供了文本绘制和测量功能。
// 文本样式
ctx.font = '16px Arial';
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
// 文本绘制
ctx.fillText('Hello Canvas', x, y); // 填充文本
ctx.strokeText('Hello Canvas', x, y); // 描边文本
// 文本测量
const metrics = ctx.measureText('Hello Canvas');
console.log(metrics.width); // 文本宽度
图像处理
Canvas 可以绘制和处理图像。
// 加载图像
const img = new Image();
img.src = 'image.png';
img.onload = () => {
// 绘制图像
ctx.drawImage(img, x, y);
// 缩放绘制
ctx.drawImage(img, x, y, width, height);
// 裁剪绘制
ctx.drawImage(img, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight);
};
// 图像数据操作
const imageData = ctx.getImageData(x, y, width, height);
const data = imageData.data;
// 处理像素数据...
ctx.putImageData(imageData, x, y);
高级特性
变换操作
Canvas 支持多种变换操作,可以改变绘制的位置、旋转和缩放。
// 基本变换
ctx.translate(x, y); // 平移
ctx.rotate(angle); // 旋转
ctx.scale(x, y); // 缩放
// 矩阵变换
ctx.transform(a, b, c, d, e, f); // 矩阵变换
ctx.setTransform(a, b, c, d, e, f); // 重置并设置变换
ctx.resetTransform(); // 重置变换
// 变换示例
ctx.save();
ctx.translate(100, 100);
ctx.rotate(Math.PI / 4);
ctx.fillRect(-25, -25, 50, 50);
ctx.restore();
合成操作
Canvas 提供了多种图形合成模式。
// 设置合成模式
ctx.globalCompositeOperation = 'source-over'; // 默认模式
ctx.globalCompositeOperation = 'destination-over';
ctx.globalCompositeOperation = 'multiply';
ctx.globalCompositeOperation = 'screen';
// 等等...
// 合成示例
ctx.fillStyle = 'red';
ctx.fillRect(10, 10, 50, 50);
ctx.globalCompositeOperation = 'multiply';
ctx.fillStyle = 'blue';
ctx.fillRect(30, 30, 50, 50);
阴影效果
Canvas 可以为绘制的图形添加阴影效果。
// 阴影设置
ctx.shadowColor = 'rgba(0, 0, 0, 0.5)';
ctx.shadowBlur = 5;
ctx.shadowOffsetX = 5;
ctx.shadowOffsetY = 5;
// 绘制带阴影的图形
ctx.fillStyle = 'blue';
ctx.fillRect(20, 20, 100, 100);
像素操作
Canvas 允许直接操作像素数据。
// 获取像素数据
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
const data = imageData.data;
// 修改像素数据
for (let i = 0; i < data.length; i += 4) {
data[i] = 255 - data[i]; // red
data[i + 1] = 255 - data[i + 1]; // green
data[i + 2] = 255 - data[i + 2]; // blue
// data[i + 3] 是 alpha 通道
}
// 将修改后的数据放回画布
ctx.putImageData(imageData, 0, 0);
离屏渲染
使用离屏 Canvas 可以提高性能和实现复杂效果。
// 创建离屏 Canvas
const offscreenCanvas = document.createElement('canvas');
offscreenCanvas.width = width;
offscreenCanvas.height = height;
const offscreenCtx = offscreenCanvas.getContext('2d');
// 在离屏 Canvas 上绘制
offscreenCtx.fillStyle = 'red';
offscreenCtx.fillRect(0, 0, width, height);
// 将离屏 Canvas 绘制到主 Canvas
ctx.drawImage(offscreenCanvas, x, y);
高级动画效果
1. 动画循环优化
使用 requestAnimationFrame 代替 setInterval/setTimeout 来创建平滑的动画:
function animate() {
// 更新动画状态
requestAnimationFrame(animate);
}
requestAnimationFrame(animate);
2. 动画性能优化
- 使用离屏 Canvas 进行预渲染
- 避免不必要的状态改变
- 只更新发生变化的区域
- 使用分层 Canvas 处理复杂场景
交互与事件
事件处理
Canvas 元素可以响应各种鼠标和触摸事件。
// 鼠标事件
canvas.addEventListener('mousedown', (e) => {
const rect = canvas.getBoundingClientRect();
const x = e.clientX - rect.left;
const y = e.clientY - rect.top;
// 处理鼠标按下事件
});
// 触摸事件
canvas.addEventListener('touchstart', (e) => {
e.preventDefault();
const touch = e.touches[0];
const rect = canvas.getBoundingClientRect();
const x = touch.clientX - rect.left;
const y = touch.clientY - rect.top;
// 处理触摸事件
});
碰撞检测
实现图形之间的碰撞检测。
// 点与矩形碰撞检测
function isPointInRect(x, y, rect) {
return x >= rect.x && x <= rect.x + rect.width && y >= rect.y && y <= rect.y + rect.height;
}
// 点与圆形碰撞检测
function isPointInCircle(x, y, circle) {
const dx = x - circle.x;
const dy = y - circle.y;
return dx * dx + dy * dy <= circle.radius * circle.radius;
}
// 路径碰撞检测
function isPointInPath(x, y, path) {
ctx.beginPath();
// 定义路径...
return ctx.isPointInPath(x, y);
}
拖拽操作
实现 Canvas 中的拖拽功能。
let isDragging = false;
let selectedObject = null;
let dragStartX = 0;
let dragStartY = 0;
canvas.addEventListener('mousedown', (e) => {
const rect = canvas.getBoundingClientRect();
const x = e.clientX - rect.left;
const y = e.clientY - rect.top;
// 检查是否点击了对象
selectedObject = findObjectAtPoint(x, y);
if (selectedObject) {
isDragging = true;
dragStartX = x - selectedObject.x;
dragStartY = y - selectedObject.y;
}
});
canvas.addEventListener('mousemove', (e) => {
if (isDragging && selectedObject) {
const rect = canvas.getBoundingClientRect();
const x = e.clientX - rect.left;
const y = e.clientY - rect.top;
selectedObject.x = x - dragStartX;
selectedObject.y = y - dragStartY;
// 重绘画布
redraw();
}
});
canvas.addEventListener('mouseup', () => {
isDragging = false;
selectedObject = null;
});
手势识别
实现基本的手势识别功能。
let startX = 0;
let startY = 0;
let lastX = 0;
let lastY = 0;
// 处理手势开始
canvas.addEventListener('touchstart', (e) => {
e.preventDefault();
const touch = e.touches[0];
startX = lastX = touch.clientX;
startY = lastY = touch.clientY;
});
// 处理手势移动
canvas.addEventListener('touchmove', (e) => {
e.preventDefault();
const touch = e.touches[0];
const deltaX = touch.clientX - lastX;
const deltaY = touch.clientY - lastY;
// 计算手势
const distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
const angle = Math.atan2(deltaY, deltaX);
// 更新位置
lastX = touch.clientX;
lastY = touch.clientY;
// 处理手势...
});
触摸事件优化
针对移动设备的特殊优化。
// 1. 设备像素比适配
function setupHiDPI() {
const dpr = window.devicePixelRatio || 1;
canvas.width = canvas.clientWidth * dpr;
canvas.height = canvas.clientHeight * dpr;
ctx.scale(dpr, dpr);
}
// 2. 触摸事件优化
canvas.addEventListener(
'touchstart',
(e) => {
e.preventDefault(); // 防止缩放和滚动
},
{ passive: false }
);
// 3. 降低分辨率
function reduceResolution() {
const scale = 0.5; // 根据需要调整
canvas.width *= scale;
canvas.height *= scale;
ctx.scale(1 / scale, 1 / scale);
}
性能优化
Canvas 性能优化
提高 Canvas 渲染性能的关键技巧。
// 1. 使用请求动画帧
function animate() {
requestAnimationFrame(animate);
// 更新和绘制逻辑
}
// 2. 避免频繁的状态更改
ctx.save();
// 批量绘制相同样式的图形
for (const shape of shapes) {
// 绘制图形
}
ctx.restore();
// 3. 使用离屏渲染
const buffer = document.createElement('canvas');
const bufferCtx = buffer.getContext('2d');
// 在缓冲区绘制
ctx.drawImage(buffer, 0, 0);
内存管理
proper memory management techniques.
// 1. 清理不再使用的资源
function cleanup() {
// 释放图像资源
image.src = '';
image = null;
// 清理画布
ctx.clearRect(0, 0, canvas.width, canvas.height);
}
// 2. 重用对象
const objectPool = [];
function getObject() {
return objectPool.pop() || createNewObject();
}
function releaseObject(obj) {
objectPool.push(obj);
}
渲染策略
优化渲染策略以提高性能。
// 1. 分层渲染
const backgroundCanvas = document.createElement('canvas');
const middleCanvas = document.createElement('canvas');
const foregroundCanvas = document.createElement('canvas');
// 2. 按需渲染
function render() {
if (needsUpdate) {
// 只在需要时更新
updateCanvas();
needsUpdate = false;
}
}
// 3. 视口裁剪
function drawVisibleArea() {
const viewport = getViewport();
for (const object of objects) {
if (isInViewport(object, viewport)) {
object.draw(ctx);
}
}
}
移动设备优化
针对移动设备的特殊优化。
// 1. 设备像素比适配
function setupHiDPI() {
const dpr = window.devicePixelRatio || 1;
canvas.width = canvas.clientWidth * dpr;
canvas.height = canvas.clientHeight * dpr;
ctx.scale(dpr, dpr);
}
// 2. 触摸事件优化
canvas.addEventListener(
'touchstart',
(e) => {
e.preventDefault(); // 防止缩放和滚动
},
{ passive: false }
);
// 3. 降低分辨率
function reduceResolution() {
const scale = 0.5; // 根据需要调整
canvas.width *= scale;
canvas.height *= scale;
ctx.scale(1 / scale, 1 / scale);
}
预渲染技术
预先渲染到离屏 Canvas 以提高性能。
// 创建离屏 Canvas
const offscreenCanvas = document.createElement('canvas');
const offscreenCtx = offscreenCanvas.getContext('2d');
// 预渲染复杂图形
function prerender() {
// 渲染操作...
}
// 在主 Canvas 上使用
ctx.drawImage(offscreenCanvas, x, y);
批量处理
将多个绘制操作合并成一个路径。
// 低效方式
paths.forEach((path) => {
ctx.beginPath();
ctx.moveTo(path.start.x, path.start.y);
ctx.lineTo(path.end.x, path.end.y);
ctx.stroke();
});
// 优化方式
ctx.beginPath();
paths.forEach((path) => {
ctx.moveTo(path.start.x, path.start.y);
ctx.lineTo(path.end.x, path.end.y);
});
ctx.stroke();
调试与测试
性能监控
使用 Performance API 监控渲染性能。
// 使用 Performance API 监控渲染性能
let lastTime = performance.now();
function animate() {
const currentTime = performance.now();
const delta = currentTime - lastTime;
console.log(`Frame time: ${delta}ms`);
lastTime = currentTime;
// 渲染代码...
requestAnimationFrame(animate);
}
可视化调试
绘制碰撞箱/边界。
// 绘制碰撞箱/边界
function debugDraw() {
ctx.save();
ctx.strokeStyle = 'red';
ctx.strokeRect(object.x, object.y, object.width, object.height);
ctx.restore();
}
单元测试
Canvas 应用程序的测试策略。
// 使用 Jest 进行测试
describe('CanvasManager', () => {
let canvasManager;
beforeEach(() => {
// 设置测试环境
document.body.innerHTML = '<div id="container"></div>';
canvasManager = new CanvasManager(document.getElementById('container'), 800, 600);
});
test('should initialize correctly', () => {
expect(canvasManager.canvas.width).toBe(800);
expect(canvasManager.canvas.height).toBe(600);
});
test('should handle resize', () => {
canvasManager.resize(1024, 768);
expect(canvasManager.canvas.width).toBe(1024);
expect(canvasManager.canvas.height).toBe(768);
});
});
性能测试
定期进行性能测试和优化。
工程实践
Canvas 封装
创建可重用的 Canvas 组件。
class CanvasManager {
constructor(container, width, height) {
this.canvas = document.createElement('canvas');
this.ctx = this.canvas.getContext('2d');
this.width = width;
this.height = height;
this.setup();
container.appendChild(this.canvas);
}
setup() {
this.canvas.width = this.width;
this.canvas.height = this.height;
this.setupEvents();
}
setupEvents() {
// 事件处理设置
}
render() {
// 渲染逻辑
}
update() {
// 更新逻辑
}
destroy() {
// 清理资源
}
}
状态管理
管理 Canvas 应用程序的状态。
class CanvasState {
constructor() {
this.objects = [];
this.selectedObject = null;
this.isDragging = false;
this.lastUpdate = 0;
this.needsRender = true;
}
addObject(object) {
this.objects.push(object);
this.needsRender = true;
}
removeObject(object) {
const index = this.objects.indexOf(object);
if (index > -1) {
this.objects.splice(index, 1);
this.needsRender = true;
}
}
update(timestamp) {
const deltaTime = timestamp - this.lastUpdate;
// 更新状态
this.lastUpdate = timestamp;
}
}
最佳实践
- 使用 requestAnimationFrame 进行动画
- 适当使用离屏渲染
- 批量处理绘制操作
- 避免频繁的状态改变
- 合理使用分层 Canvas
- 注意移动设备优化
- 实现性能监控
- 保持代码整洁和模块化
- 做好错误处理和降级方案
- 定期进行性能测试和优化
错误处理
处理可能出现的错误和降级方案。