面向对象编程三要素:封装、继承、多态,在这节中将结合设计器的组件设计来阐述javascript如何实现。
1.封装:在设计器中的组件是很适合来说明封装的概念,组件是可以拖放到设计器画布上的元素,他可以有不同的形状,不同的背景,但通过抽象和提炼后具备以下共性
- 都可以拖放到画布上
- 都可以被选中和反选
- 都有大小(宽、高)和位置,背景填充色,边框线颜色,文字颜色
- 都可以被拖动
- 都可以有文字标签
- 一个组件可能包含多个子的元素组成。
- 组件可以有背景图片
- 组件可以调整大小
- 组件间可以有连线,
- 组件可以有不同种状态
- 组件可以重绘
因此我们抽象出一个类Component作为基类,定义三个继承组件Circle,Retangle,Ellipse
function Component() { } Component.prototype.init = function (options) { if (options == undefined) options = {}; this.properties = $.extend(options, Component.DEFAULTS); this.group = new paper.Group(); var me = this; var drag = false; this.group.onClick = function (event) { me.group.children[0].selected = !me.group.children[0].selected; } this.group.onMouseDown = function (event) { drag = (event.event.button == 0); } this.group.onMouseUp = function () { drag = false; document.body.style.cursor = 'default'; } this.group.onMouseDrag = function (event) { if (drag) { this.translate(event.delta.x, event.delta.y); document.body.style.cursor = 'move'; } } return this; } Component.prototype.select = function () { this.group.children[0].selected = true; } Component.prototype.unselect = function () { this.group.children[0].selected = false; } Component.DEFAULTS = $.extend({}, { width: 50, height: 50, x: 0, y: 0, id: "", typeName:"Component", backgroundColor:"white", backgroundImage:'', fontColor:'black', borderColor:'black', lineWeight:1, title: '', status: 1, runMode: 1, capacity:1 });
基类包括默认属性DEFAULTS,初始化方法init,选中select和取消选中unselect方法。
javascript的继承是通过原型扩展来实现的,如下三个继承类的实现:
function Circle() { } Circle.prototype = $.extend({}, Component.prototype); Circle.prototype = $.extend(Circle.prototype, { render: function (options) { this.properties = $.extend(this.properties, options); var circle = new paper.Path.Circle({ center: [this.properties.x, this.properties.y], radius: 25, fillColor: this.properties.backgroundColor }); this.group.addChild(circle); return this; } }); function Retangle() { } Retangle.prototype = $.extend({}, Component.prototype); Retangle.prototype = $.extend(Retangle.prototype, { render: function (options) { this.properties = $.extend(this.properties, options); var rect = new paper.Path.Rectangle({ point: [this.properties.x, this.properties.y], size: [this.properties.width, this.properties.height], radius: 5, strokeWidth: 1, strokeColor: this.properties.borderColor, fillColor: this.properties.backgroundColor, opacity: this.properties.opacity }); this.group.addChild(rect); return this; } }); function Ellipse() { } Ellipse.prototype = $.extend({}, Component.prototype); Ellipse.prototype = $.extend(Ellipse.prototype, { render: function (options) { this.properties = $.extend(this.properties, options); var ellipse = new paper.Path.Ellipse( { point: [this.properties.x, this.properties.y], size: [this.properties.width, this.properties.height], fillColor: this.properties.fillColor, opacity: this.properties.opacity }); this.group.addChild(ellipse); return this; } });
注意看上图,
Ellipse.prototype = $.extend({}, Component.prototype); 将基类原型方法扩展过来,
Ellipse.prototype = $.extend(Ellipse.prototype, { 重写render方法,如果基类有定义render,在实例化调用时会调用重写的方法。 render: function (options) {} } 我们再看拖拉生成实例的代码onDrop
this.$element.on("drop", function (event) { event.preventDefault(); debugger; var data = null; if (event.dataTransfer == undefined && event.originalEvent != undefined) data = event.originalEvent.dataTransfer.getData("text"); else if (event.dataTransfer != undefined) data = event.dataTransfer.getData("text"); var drag = false; switch (data) { case "圆": debugger; var circle = new Circle().init().render({ x: event.originalEvent.offsetX, y: event.originalEvent.offsetY }); break; case "矩形": var rect = new Retangle().init().render({ x: event.originalEvent.offsetX, y: event.originalEvent.offsetY, width: 200, height: 100, opacity: 0.5 }); break; case "椭圆": var path = new Ellipse().init().render({ x: event.originalEvent.offsetX, y: event.originalEvent.offsetY, width: 120, height: 60, fillColor: 'green' }); break; } });
注意对比上一节的示例代码,此处在生成实例时代码有简化,
以var circle = new Circle().init().render({ x: event.originalEvent.offsetX, y: event.originalEvent.offsetY });为例,此处new Circle() 会调用function Circle()的构造函数,init()在Circle中并没有定义,但通过原型继承了过来,所以是调用Component.init()方法,注意方法内部return this,实际 返回的是Circle对象,接着.render是调用的Circle重写了的render方法。
此处不再需要在每一个组件实例化后调用onClick,onMouseDown,onMouseUp,OnMouseDrag了,因为在基类初始化时已经定义了。只是需要理解this.group的概念,因为一个组件可能由多个图形或文字组成,划成一个Group当作一个整体。
本节介绍就到这里,paperjs提供了许多的形状,事件,大家可以从paperjs.org官网了解更多,站在巨人的肩膀总比独自从造轮子来得要快。
本节源代码下载:
(本文为原创,在引用代码和文字时请注明出处)