javascript - 对象 :removed Event Not Fired - Fabric JS

标签 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");

        });

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

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

撤消/重做例程:
// 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”

更新:在正常删除操作的情况下,该事件工作正常。因此,我添加了撤消和重做例程。

问候

最佳答案

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

编辑

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

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

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

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];
}


为了让结构对象有一个新的 uuid 属性,您需要将它添加到对象原型(prototype)以及对象序列化方法中

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;
}


现在您需要监听 Canvas 上的事件,并更新 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属性实际上是添加到对象中的。关于上面的代码片段有两点需要注意。
  • bypassHistory 是 Canvas 对象和 Canvas 本身的自定义属性。您只想将用户愿意执行的操作存储到 Canvas 上。如果用户手绘一条线,您想保存该操作,您可以通过收听 path:cleared 来保存该操作。 .但是,在以编程方式绘制线的情况下(例如,在执行重做时),您可能不想存储操作。要添加此自定义属性,请执行以下操作:

    fabric.Object.prototype.bypassHistory = false;//默认值 false
  • object_modified 是一个特殊的 Action ,因为它需要存储两个对象表示:修改前和修改后。而“之后”版本可以通过 event.target 轻松获得的object:modified事件,必须以编程方式跟踪“之前”版本。在我的解决方案中,我有一个高水平 latestTouchedObject跟踪 Canvas 上最新修改对象的变量。


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


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

    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();
      }
    }


    我不知道这个解决方案(和代码)是否符合您的开箱即用需求。也许它在某种程度上与我的特定应用程序有关。我希望你能理解我的建议并加以利用。

    关于javascript - 对象 :removed Event Not Fired - Fabric JS,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/37273470/

    相关文章:

    javascript - 粘贴后绘制图像

    javascript - 如何用图像填充 Canvas 矩形

    javascript - 让子 div 按设定比例填充父 div

    javascript - 如何使用 Jquery 中的某些条件覆盖外部 CSS?

    javascript - 从图像映射中的链接在 div 内平滑滚动

    javascript - Phonegap - 自动重定向到页面

    Java Swing - 闪烁的 Canvas 图形

    javascript - 日期和随机数如何在 Javascript 中用于作恶?

    javascript - 打印不带引号和括号的 JSON 字符串

    javascript - 从 mysql 到 javascript 的变量