[英]object:removed Event Not Fired - Fabric JS
我在面料活动方面面临着一个怪异的问题。 看看这个片段
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");
});
给定此代码库,我正在尝试使用撤消/重做功能。 鉴于undo和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;
}
结构版本:“ 1.6.0-rc.1”
更新:如果执行正常的删除操作,则事件运行正常。 因此,我添加了撤消和重做例程。
问候
撤消和重做功能基本上都做相同的事情,擦除画布,加载新状态并进行渲染。 当您清除画布时,没有object:removed
事件,但会触发另一个事件,称为canvas:cleared
。 这就是为什么在执行撤消/重做操作时从未看到过触发object:removed
事件的原因。 另一方面,您确实看到了在撤消和重做时均触发的object:added
,因为我猜测canvas.renderAll
将当前状态下的每个对象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
属性实际添加到对象时也要注意。 关于以上代码段,有两点需要注意。
passbyHistory是画布对象和画布本身的自定义属性。 您只想将用户愿意执行的操作存储在画布上。 如果用户手绘线,则要保存该动作,可以通过监听path:cleared
。 但是,在以编程方式绘制线的情况下(例如,执行重做时),您可能不想存储该动作。 要添加此自定义属性,请执行以下操作:
fabric.Object.prototype.bypassHistory = false; //默认值false
object_modified是一种特殊的操作,因为它需要存储两个对象表示形式:修改之前和之后。 尽管可以通过object:modified
事件的event.target
轻松获得“之后”版本,但必须以编程方式跟踪“之前”版本。 在我的解决方案中,我有一个高级的latestTouchedObject
变量,可跟踪画布上最新修改的对象。
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(); } }
我不知道此解决方案(和代码)是否可以立即满足您的需求。 也许在某种程度上与我的特定应用有关。 希望您设法理解我的建议并加以利用。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.