HTML5系列(4)--Canvas 绘图详解

发布于:2024-12-07 ⋅ 阅读:(121) ⋅ 点赞:(0)

前端技术探索系列:HTML5 Canvas 绘图详解 🎨

开篇寄语 👋

前端开发者们,

在前四篇文章中,我们探讨了 HTML5 的各种特性。今天,让我们一起探索 Canvas 的神奇世界,看看如何用代码创造出绚丽的视觉效果。

一、Canvas 基础入门 🖌️

1. 创建 Canvas 环境

<canvas id="myCanvas" width="800" height="600">
  您的浏览器不支持 Canvas 元素
</canvas>
const canvas = document.getElementById('myCanvas');
const ctx = canvas.getContext('2d');

// 设置画布尺寸为设备像素比
function setCanvasSize() {
  const dpr = window.devicePixelRatio || 1;
  canvas.width = canvas.clientWidth * dpr;
  canvas.height = canvas.clientHeight * dpr;
  ctx.scale(dpr, dpr);
}

2. 基础图形绘制 ⭐

class CanvasDrawing {
  constructor(ctx) {
    this.ctx = ctx;
  }
  
  // 矩形
  drawRect(x, y, width, height, style = '#000') {
    this.ctx.fillStyle = style;
    this.ctx.fillRect(x, y, width, height);
  }
  
  // 圆形
  drawCircle(x, y, radius, style = '#000') {
    this.ctx.beginPath();
    this.ctx.arc(x, y, radius, 0, Math.PI * 2);
    this.ctx.fillStyle = style;
    this.ctx.fill();
  }
  
  // 线条
  drawLine(startX, startY, endX, endY, style = '#000', width = 1) {
    this.ctx.beginPath();
    this.ctx.moveTo(startX, startY);
    this.ctx.lineTo(endX, endY);
    this.ctx.strokeStyle = style;
    this.ctx.lineWidth = width;
    this.ctx.stroke();
  }
  
  // 文本
  drawText(text, x, y, style = '#000', font = '16px Arial') {
    this.ctx.font = font;
    this.ctx.fillStyle = style;
    this.ctx.fillText(text, x, y);
  }
}

3. 路径与形状 ✨

class PathDrawing {
  constructor(ctx) {
    this.ctx = ctx;
  }
  
  // 绘制多边形
  drawPolygon(points, style = '#000', fill = true) {
    this.ctx.beginPath();
    this.ctx.moveTo(points[0].x, points[0].y);
    
    for (let i = 1; i < points.length; i++) {
      this.ctx.lineTo(points[i].x, points[i].y);
    }
    
    this.ctx.closePath();
    
    if (fill) {
      this.ctx.fillStyle = style;
      this.ctx.fill();
    } else {
      this.ctx.strokeStyle = style;
      this.ctx.stroke();
    }
  }
  
  // 贝塞尔曲线
  drawCurve(startX, startY, cp1x, cp1y, cp2x, cp2y, endX, endY) {
    this.ctx.beginPath();
    this.ctx.moveTo(startX, startY);
    this.ctx.bezierCurveTo(cp1x, cp1y, cp2x, cp2y, endX, endY);
    this.ctx.stroke();
  }
}

二、高级绘图技术 🎯

1. 变换操作

class CanvasTransform {
  constructor(ctx) {
    this.ctx = ctx;
  }
  
  // 保存当前状态
  save() {
    this.ctx.save();
  }
  
  // 恢复状态
  restore() {
    this.ctx.restore();
  }
  
  // 旋转
  rotate(angle) {
    this.ctx.rotate(angle);
  }
  
  // 缩放
  scale(x, y) {
    this.ctx.scale(x, y);
  }
  
  // 平移
  translate(x, y) {
    this.ctx.translate(x, y);
  }
  
  // 矩阵变换
  transform(a, b, c, d, e, f) {
    this.ctx.transform(a, b, c, d, e, f);
  }
}

2. 图像合成与滤镜 🖼️

class CanvasEffects {
  constructor(ctx) {
    this.ctx = ctx;
  }
  
  // 设置混合模式
  setComposite(type = 'source-over') {
    this.ctx.globalCompositeOperation = type;
  }
  
  // 设置透明度
  setAlpha(alpha) {
    this.ctx.globalAlpha = alpha;
  }
  
  // 应用阴影
  setShadow(color, blur, offsetX, offsetY) {
    this.ctx.shadowColor = color;
    this.ctx.shadowBlur = blur;
    this.ctx.shadowOffsetX = offsetX;
    this.ctx.shadowOffsetY = offsetY;
  }
  
  // 创建渐变
  createGradient(x1, y1, x2, y2, colorStops) {
    const gradient = this.ctx.createLinearGradient(x1, y1, x2, y2);
    colorStops.forEach(([offset, color]) => {
      gradient.addColorStop(offset, color);
    });
    return gradient;
  }
}

3. 像素操作 📊

class PixelManipulation {
  constructor(ctx) {
    this.ctx = ctx;
  }
  
  // 获取像素数据
  getPixelData(x, y, width, height) {
    return this.ctx.getImageData(x, y, width, height);
  }
  
  // 设置像素数据
  setPixelData(imageData, x, y) {
    this.ctx.putImageData(imageData, x, y);
  }
  
  // 像素处理(如:灰度化)
  toGrayscale(imageData) {
    const data = imageData.data;
    for (let i = 0; i < data.length; i += 4) {
      const avg = (data[i] + data[i + 1] + data[i + 2]) / 3;
      data[i] = avg;     // R
      data[i + 1] = avg; // G
      data[i + 2] = avg; // B
    }
    return imageData;
  }
}

三、动画与交互 🎬

1. 动画循环管理

class AnimationManager {
  constructor() {
    this.animations = new Set();
    this.isRunning = false;
    this.lastTime = 0;
  }
  
  // 添加动画
  addAnimation(callback) {
    this.animations.add(callback);
    if (!this.isRunning) {
      this.start();
    }
  }
  
  // 移除动画
  removeAnimation(callback) {
    this.animations.delete(callback);
    if (this.animations.size === 0) {
      this.stop();
    }
  }
  
  // 开始动画循环
  start() {
    this.isRunning = true;
    this.lastTime = performance.now();
    this.animate();
  }
  
  // 停止动画循环
  stop() {
    this.isRunning = false;
  }
  
  // 动画帧
  animate = (currentTime) => {
    if (!this.isRunning) return;
    
    const deltaTime = currentTime - this.lastTime;
    this.lastTime = currentTime;
    
    for (const animation of this.animations) {
      animation(deltaTime);
    }
    
    requestAnimationFrame(this.animate);
  }
}

2. 交互事件处理 🖱️

class CanvasInteraction {
  constructor(canvas) {
    this.canvas = canvas;
    this.ctx = canvas.getContext('2d');
    this.events = new Map();
    this.setupEvents();
  }
  
  setupEvents() {
    this.canvas.addEventListener('mousedown', this.handleMouseDown.bind(this));
    this.canvas.addEventListener('mousemove', this.handleMouseMove.bind(this));
    this.canvas.addEventListener('mouseup', this.handleMouseUp.bind(this));
    this.canvas.addEventListener('click', this.handleClick.bind(this));
  }
  
  // 获取画布相对坐标
  getCanvasPoint(event) {
    const rect = this.canvas.getBoundingClientRect();
    return {
      x: event.clientX - rect.left,
      y: event.clientY - rect.top
    };
  }
  
  // 添加事件监听
  on(eventName, callback) {
    if (!this.events.has(eventName)) {
      this.events.set(eventName, new Set());
    }
    this.events.get(eventName).add(callback);
  }
  
  // 触发事件
  trigger(eventName, data) {
    if (this.events.has(eventName)) {
      for (const callback of this.events.get(eventName)) {
        callback(data);
      }
    }
  }
  
  handleMouseDown(event) {
    const point = this.getCanvasPoint(event);
    this.trigger('mousedown', point);
  }
  
  handleMouseMove(event) {
    const point = this.getCanvasPoint(event);
    this.trigger('mousemove', point);
  }
  
  handleMouseUp(event) {
    const point = this.getCanvasPoint(event);
    this.trigger('mouseup', point);
  }
  
  handleClick(event) {
    const point = this.getCanvasPoint(event);
    this.trigger('click', point);
  }
}

四、实践项目:交互式图表 📊

class InteractiveChart {
  constructor(canvas) {
    this.canvas = canvas;
    this.ctx = canvas.getContext('2d');
    this.interaction = new CanvasInteraction(canvas);
    this.animation = new AnimationManager();
    this.data = [];
    this.options = {
      padding: 40,
      animationDuration: 1000,
      barColor: '#4CAF50',
      hoverColor: '#2196F3',
      textColor: '#333',
      gridColor: '#ddd'
    };
    
    this.setupInteractions();
  }
  
  // 设置数据
  setData(data) {
    this.data = data;
    this.calculateScales();
    this.draw();
  }
  
  // 计算比例尺
  calculateScales() {
    const values = this.data.map(d => d.value);
    this.maxValue = Math.max(...values);
    this.minValue = Math.min(...values);
    
    this.barWidth = (this.canvas.width - this.options.padding * 2) / this.data.length;
    this.scaleY = (this.canvas.height - this.options.padding * 2) / (this.maxValue - this.minValue);
  }
  
  // 绘制网格
  drawGrid() {
    const { ctx, canvas, options } = this;
    
    ctx.strokeStyle = options.gridColor;
    ctx.lineWidth = 0.5;
    
    // 横向网格线
    for (let i = 0; i <= 5; i++) {
      const y = options.padding + (canvas.height - options.padding * 2) * (i / 5);
      ctx.beginPath();
      ctx.moveTo(options.padding, y);
      ctx.lineTo(canvas.width - options.padding, y);
      ctx.stroke();
    }
  }
  
  // 绘制柱状图
  drawBars() {
    const { ctx, options } = this;
    
    this.data.forEach((item, index) => {
      const x = options.padding + index * this.barWidth;
      const height = item.value * this.scaleY;
      const y = this.canvas.height - options.padding - height;
      
      ctx.fillStyle = item.hover ? options.hoverColor : options.barColor;
      ctx.fillRect(x, y, this.barWidth - 2, height);
      
      // 绘制标签
      ctx.fillStyle = options.textColor;
      ctx.textAlign = 'center';
      ctx.fillText(item.label, x + this.barWidth / 2, this.canvas.height - options.padding + 20);
    });
  }
  
  // 清空画布
  clear() {
    this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
  }
  
  // 绘制图表
  draw() {
    this.clear();
    this.drawGrid();
    this.drawBars();
  }
  
  // 设置交互
  setupInteractions() {
    this.interaction.on('mousemove', point => {
      const index = Math.floor((point.x - this.options.padding) / this.barWidth);
      
      this.data.forEach((item, i) => {
        item.hover = i === index;
      });
      
      this.draw();
    });
    
    this.interaction.on('click', point => {
      const index = Math.floor((point.x - this.options.padding) / this.barWidth);
      if (index >= 0 && index < this.data.length) {
        console.log('Clicked bar:', this.data[index]);
      }
    });
  }
}

// 使用示例
const chart = new InteractiveChart(document.getElementById('chartCanvas'));
chart.setData([
  { label: '一月', value: 30 },
  { label: '二月', value: 50 },
  { label: '三月', value: 20 },
  { label: '四月', value: 40 },
  { label: '五月', value: 60 }
]);

性能优化建议 🚀

  1. 画布优化

    • 使用 requestAnimationFrame
    • 避免频繁的状态切换
    • 使用离屏 Canvas 缓存
  2. 绘图优化

    • 批量绘制
    • 避免不必要的路径操作
    • 使用适当的图形原语
  3. 事件处理

    • 使用事件委托
    • 节流和防抖
    • 优化碰撞检测

浏览器兼容性 🌐

特性 Chrome Firefox Safari Edge
Canvas 2D
Path2D
ImageData

实用工具推荐 🛠️

  1. 图形库

    • Fabric.js
    • Paper.js
    • Two.js
  2. 图表库

    • Chart.js
    • D3.js
    • ECharts
  3. 调试工具

    • Canvas Debugger
    • FPS Monitor
    • Performance Panel

总结 🎯

Canvas 为我们提供了强大的绘图能力:

  • 基础图形绘制 ✏️
  • 高级变换效果 🎨
  • 动画与交互 🎬
  • 像素级操作 🖼️

如果你觉得这篇文章有帮助,欢迎点赞收藏,也期待在评论区看到你的想法和建议!👇

终身学习,共同成长。

咱们下一期见

💻


网站公告

今日签到

点亮在社区的每一天
去签到