在fabric.js中使用stateProperties和hasStateChanged实现撤消/重做

Posted

技术标签:

【中文标题】在fabric.js中使用stateProperties和hasStateChanged实现撤消/重做【英文标题】:Using stateProperties and hasStateChanged to implement undo / redo in fabric.js 【发布时间】:2014-10-11 01:07:52 【问题描述】:

编辑:这个问题的范围有所改变。请参阅下面的更新。

我一直在为fabric.js 进行各种撤消/重做(参见fiddle)。虽然相当幼稚,但它在大多数情况下都有效(用于添加/删除/移动或调整单个对象的大小或添加/删除对象组),但不适用于移动或调整对象组的大小。在小提琴中,注意 onObjectSelected() 函数的控制台输出。

要明白我的意思,请在小提琴画布上绘制一些对象,然后单独移动或调整它们的大小。撤消/重做按预期工作。 However, when groups are selected and moved, i can't see how to retrieve the updated positions of the objects within the group, which means the undo / redo can't restore the correct values.

// onObjectSelected function quoted here to satisfy *** 
// question requirements. See full jsfiddle for context.
for (i in canvas.getObjects()) 
        if (canvas.item(i).active === true ) 
            console.log(canvas.item(i));
            selectedObject.push(
                "itemNum"   : i,
                "svdObject" : fabric.util.object.clone(canvas.item(i))
            );
        
    

原因似乎是对象位置/比例在作为一个组移动或调整大小时没有更新。我尝试了各种方法,包括为所有对象运行 setCoords() ,但这并没有帮助。因此,如果有人能发现我做错了什么,或者知道如何解决这个问题,我将不胜感激。

编辑:澄清一下,这只是一个普通的撤消/重做系统,使用两个数组在用户修改对象时存储画布上对象的属性。单击撤消或重做时,属性将应用回画布上的对象。我所拥有的问题是选择并随后修改对象的组 em>,我不知道如何捕获某些属性,例如组中对象的位置,因为属性当对象由于某种原因被修改为一个组时,组内的对象不会更新。

更新:我了解到当对象在用户选择的组中时,对象的位置是相对于它所在的组记录的,而不是画布,这是有道理的。因此,在处理组中的对象时,添加它的左/上坐标到组左/上坐标,以计算对象相对于画布的坐标。 scaleX/Y 应乘以组 scaleX/Y。

更新:我已经设法让它做我想做的事(见new fiddle)。从那以后,我了解了fabric 的“有状态”标志、stateProperties 数组和hasStateChanged 方法,这将简化事情。这些文档很少,到目前为止我在此解决方案中使用它们还没有成功。使用提示或使用这些的示例代码将不胜感激。具体来说,我希望能够控制保存哪些属性,因为我不需要保存所有可更改的属性。

onObjectSelected 目前是这样的:

function onObjectSelected() 

  selectedObject = [];

  for (i in canvas.getObjects()) 

    if (canvas.item(i).active === true) 

      // shft-ctrl-j to see item properties on click (chrome)
      console.log(canvas.item(i));

      // objects can be flipped, but not groups 
      var groupLeft = 0;
      var groupTop = 0;
      var groupScaleX = 1;
      var groupScaleY = 1;
      var groupAngle = 0;


      // if it's a group, add group coords also
      if (typeof canvas.item(i).group != "undefined") 
        var groupLeft = canvas.item(i).group.left;
        var groupTop = canvas.item(i).group.top;
        var groupScaleX = canvas.item(i).group.scaleX;
        var groupScaleY = canvas.item(i).group.scaleY;
        var groupAngle = canvas.item(i).group.angle;
      

      selectedObject.push(
        "itemNum": i,
        "left": canvas.item(i).left + groupLeft,
        "top": canvas.item(i).top + groupTop,
        "scaleX": canvas.item(i).scaleX * groupScaleX,
        "scaleY": canvas.item(i).scaleY * groupScaleY,
        "angle": canvas.item(i).angle + groupAngle,
        "flipX": canvas.item(i).flipX,
        "flipY": canvas.item(i).flipY
      );
    

  

;

【问题讨论】:

【参考方案1】:

Fabricjs 默认不支持自动缩放或定位。支持这一点的唯一方法是实现您自己的缩放/定位机制。我也尝试了很多东西,但目前这些线条效果最好。

所以基本上,请在你的js中添加这些功能:

function prepareRatio(o)
    saveRatio(o);
    o.on('modified', function(e)
        saveRatio(o);
    );


function restoreRatio(o)
    o.set(
        left: o._ratio._left * _cv.width,
        top: o._ratio._top * _cv.height,
        width: o._ratio._width * _cv.width,
        height: o._ratio._height * _cv.height
    );

    o.setCoords();


function saveRatio(obj)
    obj._ratio =  
        _left: obj.left / _cv.width,
        _top: obj.top / _cv.height,
        _width: obj.width / _cv.width,
        _height: obj.height / _cv.height
    ;

我在这里使用了 2 个自定义变量,其中 _cv 是用于存储目标画布的宽度和高度的简化结构,_ratio 是每个对象的(常量)比率值。

在创建对象或调整画布大小时明智地使用它们:

fabric.Image.fromURL('xxxx.png', function(img) 

    img.set(
        // TODO: give object width and height, left and top
    );

    // save ratio once you create an object
    prepareRatio(img);

    canvas.add(img);

    // OPTIONAL: add your own codes
);

function redraw() 

    // TODO: resize your canvas as you want

    _cv = 
        width: canvas.getWidth(),
        height: canvas.getHeight()
    ;

    canvas.forEachObject(function(obj) 
        // restore ratio every time when you resize your canvas
        restoreRatio(obj);
    );


window.addEventListener('resize', redraw, false);

这看起来有点长,但实际上非常简单明了的解决方案。我很确定这将立即在您身边起作用。希望这会有所帮助。

【讨论】:

非常感谢您的回复,这是有用的信息,看起来当我保存/恢复到不同大小的画布时它会派上用场。我在描述我遇到的具体问题时可能不够清楚,所以我更新了这个问题以更清楚。再次感谢您的反馈,非常感谢!

以上是关于在fabric.js中使用stateProperties和hasStateChanged实现撤消/重做的主要内容,如果未能解决你的问题,请参考以下文章

在 node.js 中使用 fabric.js 渲染和操作服务器端画布

使用 fabric.js 在画布中加载 JSON

Fabric.js 橡皮擦问题画布

在 Fabric.js 中重置画布并使用 JSON 重新加载

如何设置 Fabric.js?

文本对齐在 fabric.js 中没有按预期工作