在本文中,我们将使用EaselJS和Backbone.js构建一个简单的拖放应用程序。 骨干网将通过提供模型 , 集合和视图来为我们的应用程序提供结构。 画架将使使用HTML5 canvas
元素变得容易。 尽管对于这样一个简单的应用程序,我们不一定需要Backbone,但是以这种方式开始使用Backbone很有趣。
开始使用
首先,我们如下创建目录结构:
.
|-- index.html
+-- js
|-- main.js
|-- models
| +-- stone.js
+-- views
+-- view.js
接下来,在index.html
包括JavaScript文件和canvas
元素,如以下代码示例所示。 完成此操作后,我们就可以操纵canvas
。
<body>
<!-- Canvas Element -->
<canvas id="testcanvas" height="640" width="480"/>
<script src="/bower_components/jquery/jquery.min.js"></script>
<!-- underscore is needed by backbone.js -->
<script src="/bower_components/underscore/underscore-min.js"></script>
<script src="/bower_components/backbone/backbone.js"></script>
<script src="/bower_components/easeljs/lib/easeljs-0.7.1.min.js"></script>
<!-- tweenjs is for some animations -->
<script src="/bower_components/createjs-tweenjs/lib/tweenjs-0.5.1.min.js"></script>
<script src="/js/models/stone.js"></script>
<script src="/js/views/view.js"></script>
<script src="/js/main.js"></script>
</body>
骨干模型
通过创建Backbone模型,我们将在该模型上具有键值绑定和自定义事件。 这意味着我们可以听取模型属性的更改并相应地渲染视图。 骨干网集合是一组有序的模型。 您可以绑定change
事件,以便在集合中的任何模型发生更改时得到通知。 接下来,让我们创建一个石头模型和一个石头收藏。 以下代码属于js/models/stone.js
。
var Stone = Backbone.Model.extend({
});
var StoneCollection = Backbone.Collection.extend({
model: Stone
});
使用EaselJS初始化主干视图
骨干视图无法确定任何有关HTML的内容,并且可以与任何JavaScript模板库一起使用。 在我们的例子中,我们没有使用模板库。 相反,我们操纵canvas
。 您可以将视图的render()
函数绑定到模型的change
事件,以便在模型数据更改时,视图会自动更新。
为了开始使用画架,我们创建了一个包装canvas
元素的阶段,并将对象添加为子对象。 稍后,我们将这一阶段传递给我们的骨干观点。 js/main.js
中实现此目的的代码如下所示。
$(document).ready(function() {
var stage = new createjs.Stage("testcanvas");
var view = new CanvasView({stage: stage}).render();
});
我们已经创建了CanvasView
并调用了它的render()
函数来渲染它。 我们将很快回顾render()
的实现。 首先,让我们看一下我们的initialize()
函数,该函数在js/views/view.js
定义。
var CanvasView = Backbone.View.extend({
initialize: function(args) {
// easeljs stage passed as argument.
this.stage = args.stage;
// enableMouseOver is necessary to enable mouseover event http://www.createjs.com/Docs/EaselJS/classes/DisplayObject.html#event_mouseover
this.stage.enableMouseOver(20);
// stone collection
this.collection = new StoneCollection();
// bounds of pink area and our stones. the pink area is called "rake".
this.rakeOffsets = {
x: 10,
y: 400,
height: 150,
width: 300,
stoneWidth: 50,
stoneHeight: 50
};
// listen to collection's add remove and reset events and call the according function to reflect changes.
this.listenTo(this.collection, "add", this.renderStone, this);
this.listenTo(this.collection, "remove", this.renderRake, this);
this.listenTo(this.collection, "reset", this.renderRake, this);
},
//...
});
listenTo()
侦听模型/集合更改,并调用作为第二个参数传递的函数。 我们传递上下文,该函数作为第三个参数被调用。 当我们向集合中添加石头时, add
事件将调度this.renderStone()
并将新的石头传递给函数。 同样,当重置集合时, reset
事件将调度this.renderRake()
。 通过实现这些渲染功能,视图将始终与集合同步。
渲染视图
如下所示的render()
函数仅调用this.renderRake()
并更新舞台。
render: function() {
this.renderRake();
// stage.update is needed to render the display to the canvas.
// if we don't call this nothing will be seen.
this.stage.update();
// The Ticker provides a centralized tick at a set interval.
// we set the fps for a smoother animation.
createjs.Ticker.addEventListener("tick", this.stage);
createjs.Ticker.setInterval(25);
createjs.Ticker.setFPS(60);
},
下面显示了renderRake()
方法,该方法也存储在js/views/view.js
中。
renderRake: function() {
// http://stackoverflow.com/questions/4886632/what-does-var-that-this-mean-in-javascript
var that = this;
// create the rake shape
var rakeShape = new createjs.Shape();
rakeShape.graphics.beginStroke("#000").beginFill("#daa").drawRect(this.rakeOffsets.x, this.rakeOffsets.y, this.rakeOffsets.width, this.rakeOffsets.height);
// assign a click handler
rakeShape.on("click", function(evt) {
// When rake is clicked a new stone is added to the collection.
// Note that we add a stone to our collection, and expect view to reflect that.
that.collection.add(new Stone());
});
// add the shape to the stage
this.stage.addChild(rakeShape);
// a createjs container to hold all the stones.
// we hold all the stones in a compound display so we can
// easily change their z-index inside the container,
// without messing with other display objects.
this.stoneContainer = new createjs.Container();
this.stage.addChild(this.stoneContainer);
// for each stone in our collection, render it.
this.collection.each(function(item) {
this.renderStone(item);
}, this);
},
renderRake()
做两件事。 首先,它在画布上渲染耙形(粉红色矩形),并创建一个click
处理程序。 其次,它遍历stone集合并在每个项目上调用renderStone()
。 click
处理程序将新的石头添加到集合中。
接下来,让我们看一下renderStone()
函数。
renderStone: function(model) {
// var that = this;
var baseView = this;
// build the stone shape
var stoneShape = buildStoneShape();
// make it draggable
// the second argument is a callback called on drop
// we snap the target stone to the rake.
buildDraggable(stoneShape, function(target, x, y) {
rakeSnap(target, false);
});
// add the stone to the stage and update
this.stoneContainer.addChild(stoneShape);
this.stage.update();
function buildStoneShape() {
var shape = new createjs.Shape();
shape.graphics.beginStroke("#000").beginFill("#ddd").drawRect(0, 0, baseView.rakeOffsets.stoneWidth, baseView.rakeOffsets.stoneHeight);
return shape;
};
},
我们调用了buildDraggable()
函数来使石头可拖动。 接下来,我们将看到如何实现。 但是首先,让我们回顾一下我们的骨干视图是如何工作的。 CanvasView
侦听集合的add
事件,并在add
新石头时调用renderStone()
。 render()
方法渲染耙并在集合中的每块石renderStone()
调用renderStone()
。 单击耙时,将新的石头模型添加到石头集合中,然后在新石头上调用renderStone()
。
现在,让我们看一下实现拖放功能的buildDraggable()
函数:
renderStone: function(model) {
// ...
function buildDraggable(s, end) {
// on mouse over, change the cursor to pointer
s.on("mouseover", function(evt) {
evt.target.cursor = "pointer";
});
// on mouse down
s.on("mousedown", function(evt) {
// move the stone to the top
baseView.stoneContainer.setChildIndex(evt.target, baseView.stoneContainer.getNumChildren() - 1);
// save the clicked position
evt.target.ox = evt.target.x - evt.stageX;
evt.target.oy = evt.target.y - evt.stageY;
// update the stage
baseView.stage.update();
});
// on mouse pressed moving (drag)
s.on("pressmove", function(evt) {
// set the x and y properties of the stone and update
evt.target.x = evt.target.ox + evt.stageX;
evt.target.y = evt.target.oy + evt.stageY;
baseView.stage.update();
});
// on mouse released call the end callback if there is one.
s.on("pressup", function(evt) {
if (end) {
end(evt.target, evt.stageX + evt.target.ox, evt.stageY + evt.target.oy);
}
});
};
// ...
},
为了将石头固定在耙子上,这是我们需要的最终实用函数。
// drag the stone, either by animating or not
function dragStone(s, x, y, animate) {
if (animate) {
// Use tween js for animation.
createjs.Tween.get(s).to({x: x, y: y}, 100, createjs.Ease.linear);
} else {
// set x and y attributes without animation
s.x = x;
s.y = y;
}
// update
baseView.stage.update();
};
// calculate x position to snap the rake
function snapX(x) {
if (x < baseView.rakeOffsets.x) {
x = baseView.rakeOffsets.x;
} else if (x > baseView.rakeOffsets.x + baseView.rakeOffsets.width - baseView.rakeOffsets.stoneWidth) {
x = baseView.rakeOffsets.x + baseView.rakeOffsets.width - baseView.rakeOffsets.stoneWidth;
}
return x;
};
// calculate y position to snap the rake
function snapY(y) {
if (y < baseView.rakeOffsets.y) {
y = baseView.rakeOffsets.y;
} else if (y > baseView.rakeOffsets.y + baseView.rakeOffsets.height - baseView.rakeOffsets.stoneHeight) {
y = baseView.rakeOffsets.y + baseView.rakeOffsets.height - baseView.rakeOffsets.stoneHeight;
}
return y;
};
// drag stone within the rake bounds. animation is disabled if second argument is given. animation is enabled by default
function rakeSnap(s, animateDisabled) {
dragStone(s, snapX(s.x), snapY(s.y), !animateDisabled);
};
结论
总之,Backbone不限于DOM操作,可以在需要模型视图结构的任何地方使用。 尽管它可以用于构建单页应用程序,但它不是一个完整的框架,在本文中我们仅看到Backbone的一侧。 如果您想将Backbone用于大型应用程序,我建议使用Marionette.js ,它可以处理Backbone的一些原始问题。
这篇文章的完整代码可以在GitHub上找到 。 Heroku上还提供了现场演示。 首先,只需单击粉红色区域即可创建可拖动的石头。 石头将是可拖动的,并且将被限制在粉红色区域内。
From: https://www.sitepoint.com/implementing-drag-drop-using-backbone-easeljs/