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

最佳实践

  1. 使用 requestAnimationFrame 进行动画
  2. 适当使用离屏渲染
  3. 批量处理绘制操作
  4. 避免频繁的状态改变
  5. 合理使用分层 Canvas
  6. 注意移动设备优化
  7. 实现性能监控
  8. 保持代码整洁和模块化
  9. 做好错误处理和降级方案
  10. 定期进行性能测试和优化

错误处理

处理可能出现的错误和降级方案。

标签: none

添加新评论