我正在使用奇妙的knockout.js将ViewModel属性绑定到DOM。现在,我的GUI的一部分呈现在一个画布元素上。我使用fabric.js绘制画布上的元素。由于这些元素不是dom的一部分(它们是画布绘制方法的包装器),所以我不能使用剔除来绑定到它们。不过,我需要跟踪他们在ViewModel中的位置/颜色/标签。
我想我可以为每个fabric基元类型创建一个自定义绑定,然后像一个dom节点那样绑定它们。但是,自定义绑定需要一个DOM元素作为其第一个参数。其次,我不能(很容易)以编程方式添加绑定。我需要能够这样做,因为我不能用HTML编写绑定。
我还在想这件事,但我现在还有点困住了。有什么想法吗?
发布于 2014-07-26 03:03:56
自定义绑定在计算可观察的内部实现,以便跟踪依赖项并再次触发update功能。
听起来,对于您的功能,您可能需要考虑使用计算的可观察值来跟踪对象、访问依赖项和进行任何必要的更新/api调用。
到目前为止,我还没有使用fabric,但是这里有一个例子,您可以定义一个视图模型来表示一个矩形,并创建一个计算值,以便在任何值发生变化时不断更新fabric对象。
// create a wrapper around native canvas element (with id="c")
var canvas = new fabric.Canvas('c');
var RectangleViewModel = function (canvas) {
this.left = ko.observable(100);
this.top = ko.observable(100);
this.fill = ko.observable("red");
this.width = ko.observable(100);
this.height = ko.observable(100);
this.rect = new fabric.Rect(this.getParams());
canvas.add(this.rect);
this.rectTracker = ko.computed(function () {
this.rect.set(this.getParams());
canvas.renderAll();
}, this);
};
RectangleViewModel.prototype.getParams = function () {
return {
left: +this.left(),
top: +this.top(),
fill: this.fill(),
width: +this.width(),
height: +this.height()
};
};
var vm = new RectangleViewModel(canvas);
ko.applyBindings(vm);另一个简单的想法是,如果您希望将一些fabric/canvas调用保留在视图模型之外(我可能会)。您可以创建一个接受要添加到画布中的形状数组的fabric绑定。您还将传递一个处理程序,该处理程序检索要传递给它的参数。然后,绑定将创建该形状,将其添加到画布中,然后创建一个计算结果来更新更改后的形状。类似于:
ko.bindingHandlers.fabric = {
init: function (element, valueAccessor) {
var shapes = valueAccessor(),
canvas = new fabric.Canvas(element);
ko.utils.arrayForEach(shapes, function (shape) {
//create the new shape and initialize it
var newShape = new fabric[shape.type](shape.params());
canvas.add(newShape);
//track changes to the shape (dependencies accessed in the params() function
ko.computed(function () {
newShape.set(this.params());
canvas.renderAll();
}, shape, {
disposeWhenNodeIsRemoved: element
});
});
}
};你可以把它放在帆布上,就像:
<canvas data-bind="fabric: [ { type: 'Rect', params: rect.getParams }, { type: 'Rect', params: rect2.getParams } ]"></canvas>在这一点上,视图模型可以简化得相当多,只表示矩形的数据。这里的示例:http://jsfiddle.net/rniemeyer/G6MGm/
发布于 2014-07-26 11:16:07
我的业余爱好项目也有类似的问题,http://simonlikesmaps.appspot.com .我需要将一个路径点数组绑定到地图上,我不能通过KO直接完成它,因为路径点隐藏在OpenLayers SVG层中。因此,我在这个模式中使用了一个订阅函数:
首先,一个视图模型,用于您正在显示的东西,用于我的路标,用于元素;大致为-
ElementViewModel = function(data) {
this.position = ko.observable(data.position)
this.label = ko.observable(data.label)
this.color = ko.observable(data.color)
}第二,视图模型包含以下内容的列表:
ElementListViewModel = function() {
this.elements = ko.observableArray()
}然后,使用一些逻辑将元素从the服务加载到视图模型中,大致如下:
var element_list = new ELementListViewModel()
ajax_success_function(ajax_result) {
for (element in ajax_result) {
element_list.elements.push(new ElementViewModel(element))
}
}听起来,您将对此位进行排序;最终目标是得到一个observableArray中保存的元素列表。然后,您可以订阅它,并做您的织物工作,有很多伪代码在这一步!
ElementListViewModel = function() {
this.elements = ko.observableArray()
this.elements.subscribe(function(new_elements) {
// Remove everything from Fabric
Fabric.removeEverything() // <!-- pseudo code alert!
for (element in new_elements) {
Fabric.addThing(convertToFabric(element))
}
}, this);
}这并不是非常复杂,但这确实意味着,每当您的数据更改,以及元素列表发生更改时,这些更改都会通过调用订阅函数立即反映在画布中。新元素列表与画布上已经存在的元素列表之间的“同步”非常粗糙:只需销毁所有内容并重新呈现整个列表。这对我来说没问题,但对你来说可能太贵了。
发布于 2016-09-08 09:03:03
我对这个问题很感兴趣,因为我也一直在尝试将fabric.js对象与淘汰制模型集成--我想我确信没有一个明显的答案。下面的示例展示了我的最新想法:https://jsfiddle.net/whippet71/aky9af6t/,它在DOM对象(文本框)和当前选定的画布对象之间有双向数据绑定。
HTML:
<div>
<canvas id="mycanvas" width="600" height="400"></canvas>
</div>
<div class="form form-inline">
<div class="form-group">X: <input data-bind="textInput: x" class="form-control"></div>
<div class="form-group">Y: <input data-bind="textInput: y" class="form-control"></div>
</div>Javascript:
var jsonFromServer = [{"type":"rectangle","left":10,"top":100,"width":50,"height":50},{"type":"rectangle","left":85,"top":100,"width":50,"height":50},{"type":"circle","left":25,"top":250,"radius":50}];
var selectedObject;
var canvas = new fabric.Canvas('mycanvas');
for (var i=0; i<jsonFromServer.length; i++) {
var thisShape = jsonFromServer[i];
if (thisShape.type == 'rectangle') {
var rect = new fabric.Rect({
width: thisShape.width,
height: thisShape.height,
left: thisShape.left,
top: thisShape.top,
fill: 'blue'
});
canvas.add(rect);
} else if (thisShape.type == 'circle') {
var circle = new fabric.Circle({
radius: thisShape.radius,
left: thisShape.left,
top: thisShape.top,
fill: 'green'
});
canvas.add(circle);
}
}
// Set first object as selected by default
selectedObject = canvas.getObjects()[0];
canvas.setActiveObject(selectedObject);
// A view model to represent the currently selected canvas object
function ShapeViewModel(initX, initY) {
var self = this;
self.x = ko.observable(initX);
self.y = ko.observable(initY);
// Create a computed observable which we subsribe to, to notice change in x or y position
// Use deferred updates to avoid cyclic notifications
self.position = ko.computed(function () {
return { x: self.x(), y: self.y() };
}).extend({ deferred: true });
}
var vm = new ShapeViewModel(selectedObject.left, selectedObject.top);
ko.applyBindings(vm);
// Function to update the knockout observable
function updateObservable(x, y) {
vm.x(x);
vm.y(y);
}
// Fabric event handler to detect when user moves an object on the canvas
var myHandler = function (evt) {
selectedObject = evt.target;
updateObservable(selectedObject.get('left'), selectedObject.get('top'));
}
// Bind the event handler to the canvas
// This does mean it will be triggered by ANY object on the canvas
canvas.on({ 'object:selected': myHandler, 'object:modified': myHandler });
// Make a manual subscription to the computed observable so that we can
// update the canvas if the user types in new co-ordinates
vm.position.subscribe(function (newPos) {
console.log("new x=" + newPos.x + " new y=" + newPos.y);
selectedObject.setLeft(+newPos.x);
selectedObject.setTop(+newPos.y);
selectedObject.setCoords();
canvas.renderAll();
// Update server...
});有几件事要注意:
我对淘汰赛非常陌生,所以欢迎任何意见/建议。
https://stackoverflow.com/questions/24960025
复制相似问题