面向对象编程三要素:封装、继承、多态,在这节中将结合设计器的组件设计来阐述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官网了解更多,站在巨人的肩膀总比独自从造轮子来得要快。

本节源代码下载:sample1.3

直接运行查看

(本文为原创,在引用代码和文字时请注明出处)