简体   繁体   English

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

[英]object:removed Event Not Fired - Fabric JS

I am facing a weird issue with fabric events. 我在面料活动方面面临着一个怪异的问题。 Take a look at this snippet 看看这个片段

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

        });

Given this code base I am experimenting on an undo / redo functionality. 给定此代码库,我正在尝试使用撤消/重做功能。 Given both undo & redo can add , modify or remove an object I would like to be notified if something was added or removed in the canvas (I am not much worried about object modified at this stage). 鉴于undo和redo都可以添加,修改或删除对象,因此如果在画布中添加或删除了某些对象,我希望得到通知(我不担心在此阶段修改对象)。

But strange enough no matter if an object is added or removed from the canvas using the undo / redo functionality. 但是,无论使用撤消/重做功能从画布中添加对象还是从画布中删除对象,这都足够奇怪。 I always get the output - Something was Added 我总是得到输出-添加了一些东西

Undo / Redo Routines: 撤消/重做例程:

// 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 version:"1.6.0-rc.1" 结构版本:“ 1.6.0-rc.1”

Update: The Event is working fine in case of a normal delete action. 更新:如果执行正常的删除操作,则事件运行正常。 Hence I added the Undo and Redo Routines. 因此,我添加了撤消和重做例程。

Regards 问候

Both your undo and redo functions do basically the same thing, erase canvas, load a new state and render it. 撤消和重做功能基本上都做相同的事情,擦除画布,加载新状态并进行渲染。 When you clear the canvas, there is no object:removed event, but another event is fired, called canvas:cleared . 当您清除画布时,没有object:removed事件,但会触发另一个事件,称为canvas:cleared That is why you never see your object:removed event fired when doing undo/redo. 这就是为什么在执行撤消/重做操作时从未看到过触发object:removed事件的原因。 On the other hand, you do see object:added fired on both undo and redo, because I am guessing that canvas.renderAll adds every object on the current state into the canvas (since it was previously removed with canvas.clear()). 另一方面,您确实看到了在撤消和重做时均触发的object:added ,因为我猜测canvas.renderAll将当前状态下的每个对象canvas.renderAll添加到画布中(因为先前已使用canvas.clear()删除了它)。

EDIT 编辑

A better solution is to store every action that happens on canvas, like add, modify or remove, and have each action associated with some object data. 更好的解决方案是存储在画布上发生的每个动作,例如添加,修改或删除,并使每个动作与某些对象数据相关联。 For example, you could have an object_added action associated with a serialization of the added object, or an object_removed action associated with a serialization of the removed object. 例如,您可能具有与添加的对象的序列化关联的object_added动作,或与已移除的对象的序列化关联的object_removed动作。 For object_modified you would need two associated object serializations, one prior modification and one after modification. 对于object_modified您将需要两个关联的对象序列化,一个在修改之前,一个在修改之后。 In case of a canvas_cleared action, you would have to store the whole canvas state as associative data. 如果使用canvas_cleared动作,则必须将整个画布状态存储为关联数据。

A simple stack structure can work great for the purpose of action storage. 简单的堆栈结构可以很好地用于操作存储。

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

Go ahead and create such a structure: var actionHistory = new SimpleStack(); 继续创建这样的结构: var actionHistory = new SimpleStack();

Another feature you will need for the action-based undo/redo to work, is the ability to "reference" objects in the canvas. 要使基于动作的撤消/重做工作需要的另一个功能是能够“引用”画布中的对象。 In fabric.js you can reference objects from canvas.getObjects() , but that is a plain js array and does not help much. 在fabric.js中,您可以从canvas.getObjects()引用对象,但这是一个普通的js数组,并没有太大帮助。 I have added object IDs, in the form of UUID. 我以UUID的形式添加了对象ID。 Here is a function (taken somewhere in SO, dont have the link now) tha generates UUIDs 这是一个函数(在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]; } 

In order for fabric objects to have a new uuid property you need to add it to the object prototype, and to the object serialization method as well 为了使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); 

Finally you need a function to "reference" objects via this uuid property. 最后,您需要一个函数来通过此uuid属性“引用”对象。

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

Now you need to listen for events on the canvas, and update the actionHistory accordingly: 现在,您需要在画布上侦听事件,并相应地更新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) }); } }); 

Check out each event handler carefully to understand the actual data that will be stored on actionHistory . 仔细检查每个事件处理程序,以了解将存储在actionHistory上的实际数据。 Also be mindful when the uuid property is actually added to an object. 当将uuid属性实际添加到对象时也要注意。 There are two things to note about the above snippet. 关于以上代码段,有两点需要注意。

  1. bypassHistory is a custom property of canvas objects and the canvas itself. passbyHistory是画布对象和画布本身的自定义属性。 You only want to store actions that are willingly performed by a user onto the canvas. 您只想将用户愿意执行的操作存储在画布上。 If a user hand-draws a line, you want to save that action, and you do so by listening to path:cleared . 如果用户手绘线,则要保存该动作,可以通过监听path:cleared However, in the case of a programmatically drawn line (eg. when performing a redo), you may not want to store the action. 但是,在以编程方式绘制线的情况下(例如,执行重做时),您可能不想存储该动作。 To add this custom property do as follows: 要添加此自定义属性,请执行以下操作:

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

  2. object_modified is a special action because it needs to store two object representations: before and after modification. object_modified是一种特殊的操作,因为它需要存储两个对象表示形式:修改之前和之后。 While the "after" version is obtained easily via event.target of the object:modified event, the "before" version has to be tracked programmatically. 尽管可以通过object:modified事件的event.target轻松获得“之后”版本,但必须以编程方式跟踪“之前”版本。 In my solution i have a high level latestTouchedObject variable that keeps track of the latest modified object on the canvas. 在我的解决方案中,我有一个高级的latestTouchedObject变量,可跟踪画布上最新修改的对象。

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

Now that the action storage and all listeners have been setup, it's time to implement the undo and redo functions 现在已经设置了动作存储和所有侦听器,是时候实现撤消和重做功能了

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

I dont know if this solution (and code) fits your needs out-of-the-box. 我不知道此解决方案(和代码)是否可以立即满足您的需求。 Maybe it is at some degree coupled to my specific application. 也许在某种程度上与我的特定应用有关。 I hope you manage to understand what i propose and make use of it. 希望您设法理解我的建议并加以利用。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM