对象:删除事件未触发 - Fabric JS

时间:2016-05-17 10:25:02

标签: javascript jquery canvas html5-canvas fabricjs

我面临着一个关于面料事件的奇怪问题。看一下这个片段

canvas.on('object:added', function(e) { 
        console.log(e.target.type);
        console.log("Something was Added");

    });

 canvas.on('object:removed', function(e) { 
            console.log(e.target.type);
            console.log("Something was removed");

        });

鉴于此代码库,我正在尝试撤消/重做功能。同时撤消& redo可以添加,修改或删除我希望在画布中添加或删除某些内容时通知的对象(我不太担心在此阶段修改了对象)。

但无论是否使用撤消/重做功能在画布中添加或删除对象,这都很奇怪。 我总是得到输出 - 添加了一些东西

撤消/重做例程:

// Undo Redo Clear
        canvas.counter = 0;
        var newleft = 0;
        canvas.selection = false;
        var state = [];
        var mods = 0;
        canvas.on(
            'object:modified', function () {
                updateModifications(true);
            },
            'object:added', function () {
                updateModifications(true);
            },
            'object:removed' , function(e){
                updateModifications(true);
                console.log('test me');
            });

        function updateModifications(savehistory) {
            if (savehistory === true) {
                myjson = JSON.stringify(canvas);
                state.push(myjson);
                console.log(myjson);
            }
        }

        undo = function undo() {
            if (mods < state.length) {
                canvas.clear().renderAll();
                canvas.loadFromJSON(state[state.length - 1 - mods - 1]);
                canvas.renderAll();

                mods += 1;
                //check_team();

                //compare_states(state[state.length - 1 - mods - 1] , state[state.length - 1 - mods + 1])

            }
            //make_objects_selectable();
        }

        redo = function redo() {
            if (mods > 0) {
                canvas.clear().renderAll();
                canvas.loadFromJSON(state[state.length - 1 - mods + 1]);
                canvas.renderAll();

                mods -= 1;

                //check_team(); 
            }
            //make_objects_selectable();
        }

        clearcan = function clearcan() {
            canvas.clear().renderAll();
            newleft = 0;
        }

Fabric版本:“1.6.0-rc.1”

更新:在正常删除操作的情况下,事件正常工作。因此我添加了Undo和Redo Routines。

此致

1 个答案:

答案 0 :(得分:1)

你的undo和redo函数基本上做同样的事情,擦除画布,加载一个新状态并渲染它。清除画布时,没有object:removed事件,但会触发另一个事件,称为canvas:cleared。这就是为什么在进行撤消/重做时你永远不会看到你的object:removed事件被触发的原因。另一方面,你确实看到object:added同时触发了undo和redo,因为我猜测canvas.renderAll将当前状态的每个对象都添加到画布中(因为它以前是用canvas.clear删除的) ())。

修改

更好的解决方案是存储在画布上发生的每个操作,例如添加,修改或删除,并使每个操作与某些对象数据相关联。例如,您可以将object_added操作与添加的对象的序列化相关联,或者与已删除对象的序列化相关联的object_removed操作。对于object_modified,您需要两个关联的对象序列化,一个先前修改,一个修改后。如果是canvas_cleared操作,则必须将整个画布状态存储为关联数据。

简单的堆栈结构可以很好地用于操作存储。

function SimpleStackException(msg) {
  this.message = msg;
  this.name = 'SimpleStackException';
}

function SimpleStack() {
  var MAX_ENTRIES = 2048;
  var self = this;
  self.sp = -1; // stack pointer
  self.entries = []; // stack heap

  self.push = function(newEntry) {
    if (self.sp > MAX_ENTRIES - 1) {
      throw new SimpleStackException('Can not push on a full stack.');
    }
    self.sp++;
    self.entries[self.sp] = newEntry;
    // make sure to clear the "future" stack after a push occurs
    self.entries.splice(self.sp + 1, self.entries.length);
  };

  self.pop = function() {
    if (self.sp < 0) {
      throw new SimpleStackException('Can not pop from an empty stack.');
    }
    var entry = self.entries[self.sp];
    self.sp--;
    return entry;
  };

  self.reversePop = function() {
    self.sp++;
    if (!self.entries[self.sp]) {
      self.sp--;
      throw new SimpleStackException('Can not reverse pop an entry that has never been created.');
    }
    return self.entries[self.sp];
  }

}

继续创建这样的结构: var actionHistory = new SimpleStack();

基于操作的撤消/重做工作所需的另一个功能是能够在画布中“引用”对象。在fabric.js中,您可以引用canvas.getObjects()中的对象,但这是一个普通的js数组并没有多大帮助。我已经以UUID的形式添加了对象ID。 这是一个函数(在SO中某处,现在没有链接)可以生成UUID

var lut = [];
for (var i = 0; i < 256; i++) {
  lut[i] = (i < 16 ? '0' : '') + (i).toString(16);
}

function generateUuid() {
  var d0 = Math.random() * 0xffffffff | 0;
  var d1 = Math.random() * 0xffffffff | 0;
  var d2 = Math.random() * 0xffffffff | 0;
  var d3 = Math.random() * 0xffffffff | 0;
  return lut[d0 & 0xff] + lut[d0 >> 8 & 0xff] + lut[d0 >> 16 & 0xff] + lut[d0 >> 24 & 0xff] + '-' +
    lut[d1 & 0xff] + lut[d1 >> 8 & 0xff] + '-' + lut[d1 >> 16 & 0x0f | 0x40] + lut[d1 >> 24 & 0xff] + '-' +
    lut[d2 & 0x3f | 0x80] + lut[d2 >> 8 & 0xff] + '-' + lut[d2 >> 16 & 0xff] + lut[d2 >> 24 & 0xff] +
    lut[d3 & 0xff] + lut[d3 >> 8 & 0xff] + lut[d3 >> 16 & 0xff] + lut[d3 >> 24 & 0xff];
}

为了使Fabric对象具有新的uuid属性,您需要将其添加到对象原型中,以及对象序列化方法

fabric.Object.prototype.uuid = "";
fabric.Object.prototype.toObject = (function(toObject) {
  return function() {
    return fabric.util.object.extend(toObject.call(this), {
      uuid: this.uuid,
    });
  };
})(fabric.Object.prototype.toObject);

最后,您需要一个通过此uuid属性“引用”对象的函数。

function getFabricObjectByUuid(uuid) {
  var fabricObject = null;
  canvas.getObjects().forEach(function(object) {
    if (object.uuid === uuid) {
      fabricObject = object;
    }
  });
  return fabricObject;
}

现在您需要在画布上侦听事件,并相应地更新actionHistory

canvas.on('path:created', function(path) {
  var object = path.path;
  object.uuid = generateUuid();
  actionHistory.push({
    type: 'object_added',
    object: JSON.stringify(object)
  });
});
canvas.on('object:added', function(e) {
  var object = e.target;
  // bypass the event for path objects, as they are handled by `path:created`
  if (object.type === 'path') {
    return;
  }
  // if the object has not been given an uuid, that means it is a fresh object created by this client
  if (!object.uuid) {
    object.uuid = generateUuid();
  }
  if (!object.bypassHistory) {
    actionHistory.push({
      type: 'object_added',
      object: JSON.stringify(object)
    });
  }
});
canvas.on('object:modified', function(e) {
  var object = e.target;
  actionHistory.push({
    type: 'object_modified',
    objectOld: JSON.stringify(latestTouchedObject),
    objectNew: JSON.stringify(object)
  });
});
canvas.on('text:changed', function(e) {
  var object = e.target;
  actionHistory.push({
    type: 'text_changed',
    objectOld: JSON.stringify(latestTouchedObject),
    objectNew: JSON.stringify(object)
  });
});
canvas.on('object:removed', function(e) {
  var object = e.target;
  if (!object.bypassHistory) {
    actionHistory.push({
      type: 'object_removed',
      object: JSON.stringify(object)
    });
  }
});
canvas.on('canvas:cleared', function(e) {
  if (!canvas.bypassHistory) {
    actionHistory.push({
      type: 'canvas_cleared',
      canvas: JSON.stringify(canvas)
    });
  }
});

仔细检查每个事件处理程序,以了解将存储在actionHistory上的实际数据。当uuid属性实际添加到对象时也要注意。关于上述代码段有两点需要注意。

  1. bypassHistory是画布对象和画布本身的自定义属性。您只想将用户自愿执行的操作存储到画布上。如果用户手绘线条,您希望保存该操作,并通过聆听path:cleared来执行此操作。但是,在以编程方式绘制的行(例如,执行重做时)的情况下,您可能不想存储操作。要添加此自定义属性,请执行以下操作:

    fabric.Object.prototype.bypassHistory = false; //默认值false

  2. object_modified是一个特殊操作,因为它需要存储两个对象表示:修改前后。虽然可以通过event.target事件的object:modified轻松获取“之后”版本,但必须以编程方式跟踪“之前”版本。在我的解决方案中,我有一个高级latestTouchedObject变量,用于跟踪画布上最新修改的对象。

  3. canvas.on('mouse:down', function(options) {
      if (options.target) {
        latestTouchedObject = fabric.util.object.clone(options.target);
      }
    });

    现在已经设置了动作存储和所有侦听器,是时候实现撤消和重做功能了

    function undoAction() {
      var action, objectCandidate;
      try {
        action = actionHistory.pop();
      } catch (e) {
        console.log(e.message);
        return;
      }
      if (action.type === 'object_added') {
        objectCandidate = JSON.parse(action.object);
        var object = getFabricObjectByUuid(objectCandidate.uuid);
        object.bypassHistory = true;
        canvas.remove(object);
      } else if (action.type === 'object_removed') {
        objectCandidate = JSON.parse(action.object);
        fabric.util.enlivenObjects([objectCandidate], function(actualObjects) {
          actualObjects[0].uuid = objectCandidate.uuid;
          var object = actualObjects[0];
          object.bypassHistory = true;
          canvas.add(object);
          object.bypassHistory = false;
        });
      } else if (action.type === 'object_modified' || action.type === 'text_changed') {
        objectCandidate = JSON.parse(action.objectOld);
        fabric.util.enlivenObjects([objectCandidate], function(actualObjects) {
          actualObjects[0].uuid = objectCandidate.uuid;
          var object = actualObjects[0];
          var existingObject = getFabricObjectByUuid(objectCandidate.uuid);
          if (existingObject) {
            existingObject.bypassRemoveEvent = true;
            existingObject.bypassHistory = true;
            canvas.remove(existingObject);
          }
          object.bypassHistory = true;
          canvas.add(object);
          object.bypassHistory = false;
        });
      } else if (action.type === 'canvas_cleared') {
        var canvasPresentation = JSON.parse(action.canvas);
        canvas.bypassHistory = true;
        canvas.loadFromJSON(canvasPresentation);
        canvas.renderAll();
        canvas.bypassHistory = false;
      }
    }
    
    function redoAction() {
      var action, objectCandidate;
      try {
        action = actionHistory.reversePop();
      } catch (e) {
        console.log(e.message);
        return;
      }
      if (action.type === 'object_added') {
        objectCandidate = JSON.parse(action.object);
        fabric.util.enlivenObjects([objectCandidate], function(actualObjects) {
          actualObjects[0].uuid = objectCandidate.uuid;
          var object = actualObjects[0];
          object.bypassHistory = true;
          canvas.add(object);
          object.bypassHistory = false;
        });
      } else if (action.type === 'object_removed') {
        objectCandidate = JSON.parse(action.object);
        var object = getFabricObjectByUuid(objectCandidate.uuid);
        object.bypassHistory = true;
        canvas.remove(object);
        object.bypassHistory = false;
      } else if (action.type === 'object_modified' || action.type === 'text_changed') {
        objectCandidate = JSON.parse(action.objectNew);
        fabric.util.enlivenObjects([objectCandidate], function(actualObjects) {
          actualObjects[0].uuid = objectCandidate.uuid;
          var object = actualObjects[0];
          var existingObject = getFabricObjectByUuid(objectCandidate.uuid);
          if (existingObject) {
            existingObject.bypassRemoveEvent = true;
            existingObject.bypassHistory = true;
            canvas.remove(existingObject);
          }
          object.bypassHistory = true;
          canvas.add(object);
          object.bypassHistory = false;
        });
      } else if (action.type === 'canvas_cleared') {
        canvas.clear();
      }
    }

    我不知道这个解决方案(和代码)是否符合您的开箱即用需求。也许它在某种程度上与我的具体应用相结合。我希望你能理解我的建议并使用它。