[转帖]Mootools源码分析-39 -- Drag

原帖地址:http://space.flash8.net/space/?uid-18713-action-viewspace-itemid-408445

原作者:我佛山人

 

代码
//UI插件,拖动/缩放基类
var Drag = new Class({
    
//继承自Events和Options,UI组件的基本要求:事件支持及属性可选
    Implements: [Events, Options],

    options: {
/*
        //开始拖动前事件
        onBeforeStart: $empty,
        //开始拖动事件
        onStart: $empty,
        //拖动事件
        onDrag: $empty,
        //取消拖动事件
        onCancel: $empty,
        //拖动完成事件
        onComplete: $empty,
*/
        
//自动吸附距离
        snap: 6,
        
//单位
        unit: 'px',
        
//网格大小
        grid: false,
        
//指定是否CSS属性
        style: true,
        
//拖动范围限制
        limit: false,
        
//可拖动的句柄
        handle: false,
        
//反转
        invert: false,
        
//鼠标拖动时对象改变的样式属性,当x,y为left和top时是移动,为width和height时为缩放
        modifiers: {x: 'left', y: 'top'}
    },

    
//构造函数
    initialize: function()    {
        
//Array.link对参数处理,实现配置属性与作用对象的位置无关性
        var params = Array.link(arguments, {'options': Object.type, 'element': $defined});
        
//拖动作用的对象
        this.element = $(params.element);
        
//拖动作用对象所属的文档对象
        this.document = this.element.getDocument();
        
//调用Options类的方法,实现可选参数
        this.setOptions(params.options || {});
        
//句柄参数的类型
        var htype = $type(this.options.handle);
        
//触发拖动的句柄,可支持多个对象,也可为单个,当不提供handle参数时使用拖动作用的对象
        this.handles = (htype == 'array' || htype == 'collection'? $$(this.options.handle) : $(this.options.handle) || this.element;
        
//鼠标的相关值集合
        this.mouse = {'now': {}, 'pos': {}};
        
//对象的坐标值集合
        this.value = {'start': {}, 'now': {}};
        
//选取事件名,ie内核浏览器使用onselectstart
        this.selection = (Browser.Engine.trident) ? 'selectstart' : 'mousedown';

        
//使用闭包绑定当前对象的事件方法集
        this.bound = {
            
//开始
            start: this.start.bind(this),
            
//检查
            check: this.check.bind(this),
            
//拖动
            drag: this.drag.bind(this),
            
//拖动
            stop: this.stop.bind(this),
            
//取消
            cancel: this.cancel.bind(this),
            
//停止事件冒泡,使用$lambda快速构造出functon(){return false;}这样的函数
            eventStop: $lambda(false)
        };
        
//附加事件
        this.attach();
    },

    
//附加事件
    attach: function()    {
        
this.handles.addEvent('mousedown'this.bound.start);
        
return this;
    },

    
//移除事件
    detach: function()    {
        
//为句柄对象移除鼠标按下事件,之所以要提前绑定并保存方法,是为了事件的可移除性
        //如果addevent和removeEvent中都使用this.start.bind(this),移除事件将失败
        this.handles.removeEvent('mousedown'this.bound.start);
        
return this;
    },

    
//开始拖动处理
    start: function(event)    {
        
//触发拖动前事件
        this.fireEvent('onBeforeStart'this.element);
        
//鼠标的初始位置信息
        this.mouse.start = event.page;
        
//范围限制
        var limit = this.options.limit;
        
this.limit = {'x': [], 'y': []};
        
//遍历作用的属性名
        for (var z in this.options.modifiers)    {
            
//如果值为false,忽略
            if (!this.options.modifiers[z])    continue;
            
//如果指定使用样式属性,从样式中取对应属性值
            if (this.options.style)    this.value.now[z] = this.element.getStyle(this.options.modifiers[z]).toInt();
            
//否则从属性中取值
            else    this.value.now[z] = this.element[this.options.modifiers[z]];
            
//如果指定反转,取负值(正取负,负取正)
            if (this.options.invert)    this.value.now[z] *= -1;
            
//计算鼠标相对位置
            this.mouse.pos[z] = event.page[z] - this.value.now[z];
            
//如果指定相应方向上的范围限制
            if (limit && limit[z])    {
                
//因为范围的限制是一个两项的数组,指定上限及下限,所以循环2次(为什么不直接用(2).times?)
                for (var i = 2; i--; i)    {
                    
//如果有定义,取值,因为是每次重新求值,并且使用了$lambda,所以可以使用函数返回值
                    //比如limit :{x : false, y : [function(){return 1}, function(){return 10}]}
                    //当然,你的函数里可以做更复杂的处理
                    if ($chk(limit[z][i]))    this.limit[z][i] = $lambda(limit[z][i])();
                }
            }
        }
        
//网格方式的移动/缩放
        if ($type(this.options.grid) == 'number')    this.options.grid = {'x'this.options.grid, 'y'this.options.grid};
        
//监听当前文档的鼠标移动和弹起事件
        this.document.addEvents({mousemove: this.bound.check, mouseup: this.bound.cancel});
        
//屏蔽当前文档的选择
        this.document.addEvent(this.selection, this.bound.eventStop);
    },

    
//检查条件约束
    check: function(event)    {
        
//计算当前鼠标移动的距离
        var distance = Math.round(Math.sqrt(Math.pow(event.page.x - this.mouse.start.x, 2+ Math.pow(event.page.y - this.mouse.start.y, 2)));
        
//如果超出吸附距离
        if (distance > this.options.snap)    {
            
//移除拖动前检查约束的事件监听
            this.cancel();
            
//添加拖动处理相关的事件监听
            this.document.addEvents({
                mousemove: 
this.bound.drag,
                mouseup: 
this.bound.stop
            });
            
//触发onStart和onSnap事件
            this.fireEvent('onStart'this.element).fireEvent('onSnap'this.element);
        }
    },

    
//拖动
    drag: function(event)    {
        
//当前鼠标坐标
        this.mouse.now = event.page;
        
//遍历拖动作用的属性
        for (var z in this.options.modifiers)    {
            
//如果值为false,忽略该属性
            if (!this.options.modifiers[z])    continue;
            
//计算当前值
            this.value.now[z] = this.mouse.now[z] - this.mouse.pos[z];
            
//反转值
            if (this.options.invert)    this.value.now[z] *= -1;
            
//范围限制处理
            if (this.options.limit && this.limit[z])    {
                
//如果当前方向上的值大于上限
                if ($chk(this.limit[z][1]) && (this.value.now[z] > this.limit[z][1]))    {
                    
//取上限值
                    this.value.now[z] = this.limit[z][1];
                    
//如果当前值小于下限
                }    else    if ($chk(this.limit[z][0]) && (this.value.now[z] < this.limit[z][0]))    {
                    
//取下限值
                    this.value.now[z] = this.limit[z][0];
                }
            }
            
//相对于网格大小取值
            if (this.options.grid[z]) this.value.now[z] -= (this.value.now[z] % this.options.grid[z]);
            
//根据指定样式还是属性的值设置
            if (this.options.style)    this.element.setStyle(this.options.modifiers[z], this.value.now[z] + this.options.unit);
            
else    this.element[this.options.modifiers[z]] = this.value.now[z];
        }
        
//触发onDrag事件
        this.fireEvent('onDrag'this.element);
    },

    
//取消
    cancel: function(event)    {
        
//移除事件监听
        this.document.removeEvent('mousemove'this.bound.check);
        
this.document.removeEvent('mouseup'this.bound.cancel);
        
if (event)    {
            
this.document.removeEvent(this.selection, this.bound.eventStop);
            
//触发onCancel事件
            this.fireEvent('onCancel'this.element);
        }
    },

    
//停止,打扫战场
    stop: function(event)    {
        
//移除各事件
        this.document.removeEvent(this.selection, this.bound.eventStop);
        
this.document.removeEvent('mousemove'this.bound.drag);
        
this.document.removeEvent('mouseup'this.bound.stop);
        
//触发onComplete事件
        if (event) this.fireEvent('onComplete'this.element);
    }

});

//根据Drag为Element扩展功能
Element.implement({
    
//使当前对象可缩放
    makeResizable: function(options)    {
        
return new Drag(this, $merge({modifiers: {'x''width''y''height'}}, options));
    }
});

 

posted @ 2009-12-01 11:09  webgis松鼠  阅读(358)  评论(0编辑  收藏  举报