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

Fabric.js+Canvas实现拖拽着色时大量对象导致画布更新延迟问题

React+Fabric.js 网格拖拽着色延迟问题

我正在使用React和HTML5 Canvas开发应用,采用Fabric矩形对象绘制简单网格,点击或拖拽可修改单元格颜色,且网格支持尺寸调整。20x20规格的网格着色功能运行正常,但升级到50x50规格后出现延迟,拖拽路径上的部分单元格未更新。我推测是颜色变更的渲染/处理逻辑存在问题。

问题代码

class Grid extends Component {
    constructor(props){
        super(props);
        this.state = {
            height: 500,
            width:500,
            gaugeFactor: 1.4,
            mousedown: false
        }
    }

    componentDidMount(){
        this.init();
    }

    init = () => {
        var canvas = new fabric.Canvas('canvas', {renderOnAddRemove: false});
        var grid = [];
    
        canvas.selection = false;
        fabric.Object.prototype.objectCaching = false;
        
        this.generateGrid(canvas, grid);
        this.allowColoring(canvas);

        canvas.renderAll();
        this.setState({ canvas });
        this.setState({ grid: grid });
      };
    

    generateGrid = (canvas, grid) => {
        var xPos = -1
        var yPos = -1
        var x = 0, y = 0;
        while(y < this.props.rows){
            var row = [];
            while(x < this.props.stitches){

                var rect = new fabric.Rect({
                    left: xPos,
                    top: yPos,
                    fill: 'white',
                    width: this.state.height/this.props.rows * this.props.gaugeFactor,
                    height: this.state.height/this.props.rows,
                    stroke: 'black',
                    strokeWidth: 1,
                    selectable: false,
                    hoverCursor: 'default',
                    
                });
                
                row.push(rect);

      
                canvas.add(rect);
                xPos += this.state.height/this.props.rows * this.props.gaugeFactor;
                x += 1;
            }
            yPos += this.state.height/this.props.rows;
            xPos = -1;
            y += 1;
            x = 0;
            grid.push(row);
        }
    }

    allowColoring = (canvas) => {
        canvas.on('mouse:down', (options) => {
            this.setState({mousedown: true})
                if(options.target){
                    options.target.set('fill', this.props.activeColor);
                }
        });

        canvas.on('mouse:up', () => {
            this.setState({mousedown: false})
        });

        canvas.on('mouse:over', (options)=>{
            if(options.target){
                if(this.state.mousedown){
                    options.target.set('fill', this.props.activeColor);
                    canvas.renderAll();
                }
            }
        });
    }
    
    updateGauge = () => {
        var grid = this.state.grid;
        var canvas = this.state.canvas;

        var x = 0, y = 0;
        var xPos = -1, yPos = -1;

        while(y < this.props.rows){
            while(x < this.props.stitches){
             
                grid[y][x].set({
                    left: xPos,
                    top: yPos,
                    width: this.state.height/this.props.rows * this.props.gaugeFactor,
                    height: this.state.height/this.props.rows,
                });
                grid[y][x].setCoords();
         
                xPos += this.state.height/this.props.rows * this.props.gaugeFactor;
                x += 1;
            }
            yPos += this.state.height/this.props.rows;
            xPos = -1;
            y += 1;
            x = 0;
        }
        canvas.renderAll();
    }

    clearCanvas = () => {
        var grid = this.state.grid;
        var canvas = this.state.canvas;

        var x = 0, y = 0;

        while(y < this.props.rows){
            while(x < this.props.stitches){
       
                grid[y][x].set({
                    fill: '#ffffff',
    
                });
    
                x += 1;
            }
            y += 1;
            x = 0;
        }
        
        canvas.renderAll();
    }
    render() {
        return (
            <div id="canvasContainer" style = {containerStyle}>
                <canvas id="canvas"  width="1000" height="1000" style = {canvasStyle}></canvas>
            </div>
        )
    }
}
const containerStyle = {
        width: '800px', 
        height: '500px', 
        overflow: 'auto', 
        border: '1px solid'
    }
    
    const canvasStyle = {
        border: '1px solid #000000'
    }

优化建议

  • 替换全量重绘为单对象重绘:mouse:over事件中修改单元格颜色后,不要调用全量重绘方法canvas.renderAll(),改为仅重绘当前修改的矩形canvas.renderObject(options.target),可大幅降低渲染开销。
  • 移除不必要的React状态:将mousedown状态从React state改为类实例属性存储(直接赋值this.mousedown = true/false),避免高频触发React状态更新带来的额外开销。
  • 开启Fabric对象缓存:删除fabric.Object.prototype.objectCaching = false配置,Fabric的对象缓存机制会将静态对象缓存为位图,避免每次重绘都重新计算渲染属性,大幅提升大数量级对象的渲染性能。
  • 提前计算重复变量:在循环外提前计算好单元格的宽高、步长等固定值,避免循环内重复计算。
  • 批量合并渲染操作:如果拖拽速度过快触发大量着色请求,可通过requestAnimationFrame将多次修改合并到同一帧渲染,避免掉帧。

已验证的解决方案

通过自定义构建Fabric包解决了该问题,由于仅需要用到fabric.Rect相关功能,裁剪掉Fabric其他冗余模块后性能大幅提升。注意如果采用该方案,需要勾选Animation选项,避免功能异常。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.05 12:24:00