我面临一个关于织物事件的奇怪问题。看看这个片段
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
属性实际上是添加到对象中的。关于上面的代码片段有两点需要注意。path:cleared
来保存该操作。 .但是,在以编程方式绘制线的情况下(例如,在执行重做时),您可能不想存储操作。要添加此自定义属性,请执行以下操作:fabric.Object.prototype.bypassHistory = false;//默认值 false
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/