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;
修复说明
- 使用
useRef存储Canvas实例,确保组件生命周期内始终引用同一个实例; addRect函数通过canvasRef.current获取最新实例,保证操作与事件绑定的是同一个Canvas对象;- 添加组件卸载时的清理逻辑,调用
dispose()销毁Canvas实例,避免内存泄漏。
内容的提问来源于stack exchange,提问作者onour1
相关产品推荐
相关产品推荐

