You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用Preact实现Canvas声明式渲染的正确方法是什么?

声明式Canvas组件的实现方法

核心思路是利用Preact的组件钩子(或生命周期)管理Canvas绘制逻辑:组件挂载时获取Canvas上下文,当依赖的状态变化时重新生成像素并绘制,完全跳过VDOM对Canvas内容的对比,只关注数据变化触发重绘。

函数式组件(推荐,Preact + Hooks)

用useRef保存Canvas元素引用,useEffect监听绘制依赖的状态变化,每次变化时执行像素生成逻辑:

import { useState, useRef, useEffect } from 'preact/hooks';

// 点到颜色映射的示例函数
const getColorForPoint = (x, y, width, height) => {
  const r = Math.floor((x / width) * 255);
  const g = Math.floor((y / height) * 255);
  const b = 128;
  return { r, g, b };
};

export default function CanvasRenderer() {
  const [dimensions, setDimensions] = useState({ width: 400, height: 300 });
  const canvasRef = useRef(null);

  useEffect(() => {
    const canvas = canvasRef.current;
    if (!canvas) return;

    const ctx = canvas.getContext('2d');
    const { width, height } = dimensions;
    
    // 设置Canvas实际像素尺寸(避免模糊)
    canvas.width = width;
    canvas.height = height;

    // 获取图像数据对象,直接操作像素
    const imageData = ctx.createImageData(width, height);
    const data = imageData.data;

    // 遍历每个点,填充颜色数据
    for (let y = 0; y < height; y++) {
      for (let x = 0; x < width; x++) {
        const index = (y * width + x) * 4;
        const { r, g, b } = getColorForPoint(x, y, width, height);
        
        data[index] = r;     // 红通道
        data[index + 1] = g; // 绿通道
        data[index + 2] = b; // 蓝通道
        data[index + 3] = 255; // 透明度(不透明)
      }
    }

    // 将像素数据绘制到Canvas
    ctx.putImageData(imageData, 0, 0);
  }, [dimensions]); // 当dimensions变化时重新绘制

  return (
    <div>
      <canvas ref={canvasRef} style={{ border: '1px solid #ccc' }} />
      {/* 示例:调整尺寸触发重绘 */}
      <button onClick={() => setDimensions({ width: 500, height: 400 })}>
        放大Canvas
      </button>
    </div>
  );
}

类组件写法

如果使用类组件,可在componentDidMount和componentDidUpdate中执行绘制逻辑:

import { Component } from 'preact';

const getColorForPoint = (x, y, width, height) => {
  const r = Math.floor((x / width) * 255);
  const g = Math.floor((y / height) * 255);
  const b = 128;
  return { r, g, b };
};

export default class CanvasRenderer extends Component {
  constructor(props) {
    super(props);
    this.state = { width: 400, height: 300 };
    this.canvasRef = null;
  }

  setCanvasRef = (el) => {
    this.canvasRef = el;
  };

  draw = () => {
    const canvas = this.canvasRef;
    if (!canvas) return;

    const ctx = canvas.getContext('2d');
    const { width, height } = this.state;
    
    canvas.width = width;
    canvas.height = height;

    const imageData = ctx.createImageData(width, height);
    const data = imageData.data;

    for (let y = 0; y < height; y++) {
      for (let x = 0; x < width; x++) {
        const index = (y * width + x) * 4;
        const { r, g, b } = getColorForPoint(x, y, width, height);
        
        data[index] = r;
        data[index + 1] = g;
        data[index + 2] = b;
        data[index + 3] = 255;
      }
    }

    ctx.putImageData(imageData, 0, 0);
  };

  componentDidMount() {
    this.draw();
  }

  componentDidUpdate() {
    this.draw();
  }

  render() {
    return (
      <div>
        <canvas ref={this.setCanvasRef} style={{ border: '1px solid #ccc' }} />
        <button onClick={() => this.setState({ width: 500, height: 400 })}>
          放大Canvas
        </button>
      </div>
    );
  }
}

关键注意点

  • 用Ref获取Canvas元素:直接操作DOM元素,跳过VDOM对Canvas内部内容的处理,完全符合你只生成像素的需求。
  • 监听依赖状态:只在绘制逻辑依赖的状态(比如尺寸、颜色映射参数)变化时才重新绘制,避免不必要的性能消耗。
  • 直接操作ImageData:相比逐点调用fillRect,操作ImageData是更高效的批量像素生成方式。
  • 设置Canvas实际尺寸:必须将canvas.width和canvas.height设为实际像素值,避免绘制内容模糊。

内容的提问来源于stack exchange,提问作者Corbin

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.15 00:40:41