[转帖]Mootools源码分析-39 -- Drag
原帖地址:http://space.flash8.net/space/?uid-18713-action-viewspace-itemid-408445
原作者:我佛山人
![](https://images.cnblogs.com/OutliningIndicators/ExpandedBlockStart.gif)
//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));
}
});
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));
}
});