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

Fabric.js+React动态添加矩形无法交互移动问题求助

问题:Fabric.js 动态添加的矩形无法交互移动

使用React结合Fabric.js实现了带添加按钮的Canvas功能:点击按钮可在Canvas内添加红色矩形,但动态添加的矩形无法被选中和拖动。但在useEffect中硬编码添加的矩形却能正常交互移动,可排除Fabric.js库本身的问题。

问题代码

import './App.css';
import React, { useState, useEffect } from "react";
import { fabric } from 'fabric';

function App() {
  const [canvas, setCanvas] = useState(null);

  const initCanvas = () => (
    new fabric.Canvas("canvas", {
      width: 400,
      height: 400,
      backgroundColor: "#45F19C"
    })
  )

  const addRect = () => {
    if (canvas) {
      const rect = new fabric.Rect({
        left: 100,
        top: 50,
        width: 100,
        height: 100,
        fill: "red",
        selectable: true,
        hasControls: true,
      });
      canvas.add(rect);
      canvas.renderAll();
    }
  }

  useEffect(() => {
    const newCanvas = initCanvas();
    setCanvas(newCanvas);

    newCanvas.lowerCanvasEl.style.userSelect = "none";
    
    newCanvas.on("object:moving", (options) => {
      const obj = options.target;

      if (obj.left < 0) {
        obj.left = 0;
      }
      if (obj.top < 0) {
        obj.top = 0;
      }
      if (obj.left + obj.width * obj.scaleX > newCanvas.width) {
        obj.left = newCanvas.width - obj.width * obj.scaleX;
      }
      if (obj.top + obj.height * obj.scaleY > newCanvas.height) {
        obj.top = newCanvas.height - obj.height * obj.scaleY;
      }
    });
  }, []);

  return (
    <div className="App">
      <button onClick={addRect}>Add</button>
      <canvas id="canvas"></canvas>
    </div>
  );
}

export default App;

现象说明

点击「Add」按钮后,红色矩形可正常显示在Canvas中,但无法选中、拖动或调整大小;若在useEffect中直接硬编码创建并添加矩形,该矩形可正常进行交互操作。


解决方案

问题原因

useState设计用于保存不可变状态,而Fabric.js的Canvas实例是可变对象。组件重新渲染时,addRect函数会捕获useState中保存的Canvas实例快照,导致操作的实例与绑定事件的实例出现上下文不一致,最终引发交互失效。

修复代码

将useState替换为useRef保存Canvas实例,useRef会保持对同一个实例的稳定引用,不会随组件重新渲染而改变:

import './App.css';
import React, { useRef, useEffect } from "react";
import { fabric } from 'fabric';

function App() {
  // 用useRef替代useState保存canvas实例
  const canvasRef = useRef(null);

  const initCanvas = () => (
    new fabric.Canvas("canvas", {
      width: 400,
      height: 400,
      backgroundColor: "#45F19C"
    })
  )

  const addRect = () => {
    // 通过ref.current获取最新的canvas实例
    if (canvasRef.current) {
      const rect = new fabric.Rect({
        left: 100,
        top: 50,
        width: 100,
        height: 100,
        fill: "red",
        selectable: true,
        hasControls: true,
      });
      canvasRef.current.add(rect);
      canvasRef.current.renderAll();
    }
  }

  useEffect(() => {
    const newCanvas = initCanvas();
    // 将实例赋值给ref.current
    canvasRef.current = newCanvas;

    newCanvas.lowerCanvasEl.style.userSelect = "none";
    
    newCanvas.on("object:moving", (options) => {
      const obj = options.target;

      if (obj.left < 0) {
        obj.left = 0;
      }
      if (obj.top < 0) {
        obj.top = 0;
      }
      if (obj.left + obj.width * obj.scaleX > newCanvas.width) {
        obj.left = newCanvas.width - obj.width * obj.scaleX;
      }
      if (obj.top + obj.height * obj.scaleY > newCanvas.height) {
        obj.top = newCanvas.height - obj.height * obj.scaleY;
      }
    });

    // 组件卸载时销毁canvas实例,避免内存泄漏
    return () => {
      newCanvas.dispose();
    }
  }, []);

  return (
    <div className="App">
      <button onClick={addRect}>Add</button>
      <canvas id="canvas"></canvas>
    </div>
  );
}

export default App;

修复说明

  1. 使用useRef存储Canvas实例,确保组件生命周期内始终引用同一个实例;
  2. addRect函数通过canvasRef.current获取最新实例,保证操作与事件绑定的是同一个Canvas对象;
  3. 添加组件卸载时的清理逻辑,调用dispose()销毁Canvas实例,避免内存泄漏。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.06 13:54:53