【翻译作品】JavaScript CSS修改学习第六章:拖拽

这是一个简单可用的拖拽代码。用鼠标和键盘都可以操作。

当示例的box上的#链接处于活动状态的时候(不论是用tab然后点击enter或者使用鼠标点击)这个元素就能够通过方向键拖拽。然后点击enter或者Esc释放。(可以随意改变这些键。我不确定释放键应该设置成为什么所以enter和Esc都可以)

 

使用

1、复制文章后面的dragDrop对象。

2、复制我的addEventSimple和removeEventSimple函数,这里需要。

3、设定keyHTML和keySpeed属性(下面有解释)。

4、确定你所要拖拽的元素都有位置属性:absolute或者fixed。

5、把所有可拖拽的元素发送到对象的initElement函数。可以发送一个对象或者对象ID的字符串。例如:

dragDrop.initElement('test');
dragDrop.initElement(document.getElementById('test2'));

6、当元素被拖拽过后,代码会自动添加dragged类。你可以添加一些CSS效果。

7、如果你想当用户放开元素之后做一些事情,你可以给releaseElement添加自己的函数。

属性

你需要设置两个属性。

keyHTML包含一个需要拖拽的元素的键盘能访问到的链接的内容。为了保持HTML简洁,这里只添加一个有简单样式的类。你可以随意构建你的HTML,但是要记住一点就是必须有一个链接让键盘能够访问到,键盘用户需要一个焦点来触发拖拽事件。

keySpeed用来设置键盘拖拽的速度,每次按键移动多少像素。我喜欢设置为10,你也可以尝试一下其他的值。

这里还有7个属性,但是都是在代码内部的。初始化的时候都设置为undefined,然后相应的函数会设置他们。

 

拖拽对象

复制下面这个对象到你的页面,不要忘了addEventSimple和removeEventSimple

dragDrop = {
	keyHTML: '<a href="#" class="keyLink">#</a>',
	keySpeed: 10, // pixels per keypress event
	initialMouseX: undefined,
	initialMouseY: undefined,
	startX: undefined,
	startY: undefined,
	dXKeys: undefined,
	dYKeys: undefined,
	draggedObject: undefined,
	initElement: function (element) {
		if (typeof element == 'string')
			element = document.getElementById(element);
		element.onmousedown = dragDrop.startDragMouse;
		element.innerHTML += dragDrop.keyHTML;
		var links = element.getElementsByTagName('a');
		var lastLink = links[links.length-1];
		lastLink.relatedElement = element;
		lastLink.onclick = dragDrop.startDragKeys;
	},
	startDragMouse: function (e) {
		dragDrop.startDrag(this);
		var evt = e || window.event;
		dragDrop.initialMouseX = evt.clientX;
		dragDrop.initialMouseY = evt.clientY;
		addEventSimple(document,'mousemove',dragDrop.dragMouse);
		addEventSimple(document,'mouseup',dragDrop.releaseElement);
		return false;
	},
	startDragKeys: function () {
		dragDrop.startDrag(this.relatedElement);
		dragDrop.dXKeys = dragDrop.dYKeys = 0;
		addEventSimple(document,'keydown',dragDrop.dragKeys);
		addEventSimple(document,'keypress',dragDrop.switchKeyEvents);
		this.blur();
		return false;
	},
	startDrag: function (obj) {
		if (dragDrop.draggedObject)
			dragDrop.releaseElement();
		dragDrop.startX = obj.offsetLeft;
		dragDrop.startY = obj.offsetTop;
		dragDrop.draggedObject = obj;
		obj.className += ' dragged';
	},
	dragMouse: function (e) {
		var evt = e || window.event;
		var dX = evt.clientX - dragDrop.initialMouseX;
		var dY = evt.clientY - dragDrop.initialMouseY;
		dragDrop.setPosition(dX,dY);
		return false;
	},
	dragKeys: function(e) {
		var evt = e || window.event;
		var key = evt.keyCode;
		switch (key) {
			case 37:	// left
			case 63234:
				dragDrop.dXKeys -= dragDrop.keySpeed;
				break;
			case 38:	// up
			case 63232:
				dragDrop.dYKeys -= dragDrop.keySpeed;
				break;
			case 39:	// right
			case 63235:
				dragDrop.dXKeys += dragDrop.keySpeed;
				break;
			case 40:	// down
			case 63233:
				dragDrop.dYKeys += dragDrop.keySpeed;
				break;
			case 13: 	// enter
			case 27: 	// escape
				dragDrop.releaseElement();
				return false;
			default:
				return true;
		}
		dragDrop.setPosition(dragDrop.dXKeys,dragDrop.dYKeys);
		if (evt.preventDefault)
			evt.preventDefault();
		return false;
	},
	setPosition: function (dx,dy) {
		dragDrop.draggedObject.style.left = dragDrop.startX + dx + 'px';
		dragDrop.draggedObject.style.top = dragDrop.startY + dy + 'px';
	},
	switchKeyEvents: function () {
		// for Opera and Safari 1.3
		removeEventSimple(document,'keydown',dragDrop.dragKeys);
		removeEventSimple(document,'keypress',dragDrop.switchKeyEvents);
		addEventSimple(document,'keypress',dragDrop.dragKeys);
	},
	releaseElement: function() {
		removeEventSimple(document,'mousemove',dragDrop.dragMouse);
		removeEventSimple(document,'mouseup',dragDrop.releaseElement);
		removeEventSimple(document,'keypress',dragDrop.dragKeys);
		removeEventSimple(document,'keypress',dragDrop.switchKeyEvents);
		removeEventSimple(document,'keydown',dragDrop.dragKeys);
		dragDrop.draggedObject.className = dragDrop.draggedObject.className.replace(/dragged/,'');
		dragDrop.draggedObject = null;
	}
}

 

拖拽是什么

拖拽是在屏幕上移动元素的一种方法。为了让元素能够移动,元素必须有position属性:absolute或者fixed,这样才能通过修改它的坐标(style.top和style.left)让它移动。

(理论上position:relative也可以,但是几乎没用。另外,那样需要额外的数据来计算,这里我没有写)

设置坐标很简单;找到需要设置的元素的坐标是这个代码比较难的部分。大多数代码都是用来处理这个问题的。

另外,保持易用性也比较重要。传统上通过鼠标来拖拽一个元素是最好的办法,但是也要考虑到没有鼠标的用户,所以也要保证键盘的可用性。

 

基础知识

让我们先来看看一些基础知识

初始化一个元素

每个拖拽代码都从初始化元素开始。这个工作通过下面的函完成:

initElement: function (element) {
	if (typeof element == 'string')
		element = document.getElementById(element);
	element.onmousedown = dragDrop.startDragMouse;
	element.innerHTML += dragDrop.keyHTML;
	var links = element.getElementsByTagName('a');
	var lastLink = links[links.length-1];
	lastLink.relatedElement = element;
	lastLink.onclick = dragDrop.startDragKeys;
},

如果函数接收到一个字符串,那么就会当做元素ID来处理。然后给这个元素设置一个onmousedown事件,用来开始鼠标部分的代码。注意这里我使用的是传统事件注册方式;因为我希望this关键字能够在startDragDrop里起作用。

然后把用户定义的keyHTML添加到元素上,我相信这个链接是用来触发键盘事件的。然后为这个链接设置键盘的触发程序。然后存储主元素在relatedElement里面,我们后面需要。

现在代码就等用户动作了

基本位置信息

我打算使用下面的方法来:首先我会读取拖拽元素的初始位置,保存在startX和startY里面。然后计算鼠标移动的位置或者键盘控制下移动的位置来决定元素从初始位置移动的范围。

image

startX和startY通过startDrag函数来设置,这个函数在鼠标和键盘事件里都会用到。

startDrag: function (obj) {
	if (dragDrop.draggedObject)
		dragDrop.releaseElement();
	dragDrop.startX = obj.offsetLeft;
	dragDrop.startY = obj.offsetTop;
	dragDrop.draggedObject = obj;
	obj.className += ' dragged';
},

首先,如果元素处于拖拽状态,那么我们就释放他(我们后面再讲)。

然后函数会找到元素在起始位置的坐标(offsetLeft和offsetTop),然后保存在startX和startY以备后用。

然后在draggedObject里面保存一个对象的引用。然后给他添加dragged类,这样就可以通过CSS来设置拖拽时候的样式了

当用户使用鼠标或者键盘拖拽元素的时候,代码的最复杂的部分就要跟踪位置的变化。然后给出dX和dY(X和Y的变化)。然后加上startX和startY就是元素现在的位置。

下面的函数用来设定位置:

setPosition: function (dx,dy) {
	dragDrop.draggedObject.style.left = dragDrop.startX + dx + 'px';
	dragDrop.draggedObject.style.top = dragDrop.startY + dy + 'px';
},

函数通过鼠标和键盘的移动计算所得的dX和dY与初始位置,来设定元素的新位置。

这部分很简单,复杂的地方就在于dx和dy的获得,鼠标部分和键盘部分的处理非常的不同,我们分别来看。

 

鼠标部分的代码

鼠标部分的计算方面比键盘的要复杂一些,但是在浏览器兼容性方面问题不大。所以我们从鼠标部分开始。

事件

首先我们来讨论事件。很明显的,在拖拽过程中需要mousedown,mousemove,mouseup用来完成选择对象,拖拽,拖拽完成这几个动作。

这一系列事件从需要拖拽元素的mousedown事件开始。所以所有的拖拽元素都需要这个事件来标明拖拽开始了。我们看到:

element.onmousedown = dragDrop.startDragMouse;

然而mousemove和mouseup事件不应该设置在元素上而应该设置在整个文档上。因为用户可能很快很疯狂的移动鼠标,然后丢失拖拽元素。如果设置在元素上,因为鼠标不在元素上了所以可能会无法控制,这对于易用性来说不是好事。

如果我们再文档上设置mousemove和mouseup,就没有这个问题。不管鼠标在哪,元素都会响应mousemove和mouseup。这个的易用性就很强。

另外你只能在拖拽开始以后再设置mousemove和mouseup,然后当用户释放元素之后删除它们。这样代码很干净而且节省系统资源,因为mousemove对系统的消耗很大。

Mousedown

当拖拽元素发生mousedown事件的时候,startDragMouse函数就开始执行:

startDragMouse: function (e) {
	dragDrop.startDrag(this);
	var evt = e || window.event;
	dragDrop.initialMouseX = evt.clientX;
	dragDrop.initialMouseY = evt.clientY;
	addEventSimple(document,'mousemove',dragDrop.dragMouse);
	addEventSimple(document,'mouseup',dragDrop.releaseElement);
	return false;
},

首先会执行我们之前讨论过的startDrag。然后查找鼠标的坐标然后保存在initialMouseX和initialMouseY中。后面我们会把鼠标位置跟这个比较。

最后会返回false,这个用来阻止默认鼠标事件:选择文本。我们不想再拖拽的时候有文本被选中,这很烦人。

image

然后给文档设置mouseup和mousemove事件处理程序。因为有可能文档有他自己的mouseup和mousemove事件处理程序,所以我使用我的addEventSimple函数防止原来的事件处理程序失效。

Mousemove

现在,当用户移动鼠标的时候dragMouse函数就执行了。

dragMouse: function (e) {
	var evt = e || window.event;
	var dX = evt.clientX - dragDrop.initialMouseX;
	var dY = evt.clientY - dragDrop.initialMouseY;
	dragDrop.setPosition(dX,dY);
	return false;
},

这个函数会读取鼠标现在的坐标,然后减去之前的坐标,把得到的dX和dY传递给sePosition。

然后通过返回false来阻止鼠标选择文本的默认属性。

image

Mouseup

当用户松开鼠标的时候,会调用releaseElement。我们后面讨论。

 

键盘部分代码

现在我们开始更复杂的键盘部分代码。不像鼠标拖拽那样,键盘拖拽并没有一个标准。虽说基本的交互不是太复杂,但是最好还是简要说明一下。

基本交互

用来拖拽的键最好是方向键,这很简单。

激活和释放元素是比较有技巧的,在这里我的代码还需要加强。

我觉得如果用键盘来激活的话就应该使用一个我添加的额外的链接。这里没有太多选择:因为链接能够在所有的浏览器里面获得焦点(好吧,表单也可以,你也可是选择复选框),而且把一个链接放置在可拖拽的元素里面也是合乎逻辑的(你可以放在任何地方,但是如何让用户知道那个是用来激活拖拽的呢?)。

我假设当用户点击enter或者Esc的时候释放元素,至少我没找到其他合适的键。你想选择其他的话可以在这里查找键盘代码

case 13: // enter
case 27: // escape
dragDrop.releaseElement();
return false;

 

事件

点击可以激活元素。当鼠标点击链接或者当元素获得焦点的时候点击enter键就能激活。所以键盘代码的激活可以使点击enter键或者点击链接。

(严格来说,当你用鼠标点击链接的时候,元素先被鼠标事件激活然后释放了然后再被键盘模式激活。)

事件的其余部分也非常的模糊。当你想检测方向键的时候键盘事件尤为麻烦。

首先我们需要一个允许重复点击的事件,因为用户可能按着方向键不放,那么事件就需要一遍遍的触发,这样拖拽才能继续。所以我们使用keypress事件。

不幸的是,IE在keypress的情况下不支持方向键。在IE里面keydown会重复发生,看起来我们需要使用keydown事件了。

你可能才到事情没那么简单。在Opera和Safari里面keydown事件只能触发一次,所以当用户按下键之后,元素移动一次之后就不动了。在这些浏览器中我们需要keypress。

所以理想情况下,我们使用keypress,如果不支持就是用keydown。但是怎么切换事件呢?你又怎么知道keypress在这个时候不能用呢?

我的解决办法就是给keypress事件设置一个事件处理程序。如果这个程序执行了说明支持keypress,我们就可以安全的切换了。

startDragKeys函数用来设置keydown和keypress事件:

addEventSimple(document,'keydown',dragDrop.dragKeys);
addEventSimple(document,'keypress',dragDrop.switchKeyEvents);

首先keydown触发完成拖拽的dragKeys函数。这是第一个触发的事件,而且元素总会移动。然后我们做其他的话,那么元素在Opera和Safari1.3里面移动一次以后就会停止。

这就是为什么我们还需要keypress。第一个keypress事件会触发switchKeyEvents函数,这个函数会调整事件处理程序:

switchKeyEvents: function () {
	removeEventSimple(document,'keydown',dragDrop.dragKeys);
	removeEventSimple(document,'keypress',dragDrop.switchKeyEvents);
	addEventSimple(document,'keypress',dragDrop.dragKeys);
},

他会先删除掉原来的事件处理程序,然后将keypress设置为触发dragKeys。因为这个函数只会在支持他的浏览器里面执行,所以我们只在这些浏览器里面将keydown改为keypress。

初始键盘代码

当用户点击了连接激活了元素,那么就会调用startDragKeys。

startDragKeys: function () {
	dragDrop.startDrag(this.relatedElement);
	dragDrop.dXKeys = dragDrop.dYKeys = 0;
	addEventSimple(document,'keydown',dragDrop.dragKeys);
	addEventSimple(document,'keypress',dragDrop.switchKeyEvents);
	this.blur();
	return false;
},

首先会调用我们之前讨论过的startDrag函数。他会给这个函数传递relatedElement,也就是要拖拽的元素。

然后将dXKeys和dYKeys设置为0。这些变量用来跟踪元素的位移。

然后设置事件处理程序,上面已经讨论过了。

然后移除刚才点击的链接的焦点。我这样做是因为Enter键会释放元素,但是如果不移除焦点,当用户点击了Enter键之后,元素被释放,但是链接却再次被Enter点击,又成了可拖动的模式。如果我们移除焦点,那么问题就不存在了。

最后返回false来阻止默认动作。

通过键盘拖拽

dragKeys负责键盘拖拽:

dragKeys: function(e) {
	var evt = e || window.event;
	var key = evt.keyCode;

我们首先读取键盘的键值。

然后我们使用switch语句来决定我们怎么做。这部分的目的是更新dXKeys和dYKeys的值,就可以通过设置元素的位置来移动元素了。

	switch (key) {
		case 37:	// left
		case 63234:
			dragDrop.dXKeys -= dragDrop.keySpeed;
			break;
		case 38:	// up
		case 63232:
			dragDrop.dYKeys -= dragDrop.keySpeed;
			break;
		case 39:	// right
		case 63235:
			dragDrop.dXKeys += dragDrop.keySpeed;
			break;
		case 40:	// down
		case 63233:
			dragDrop.dYKeys += dragDrop.keySpeed;
			break;

作者通过设置keySpeed来确定每次移动的像素大小。当用户点击左方向键,就减去keySpeed。

image

这个代码包含63232-63235的情况。因为Safari1.3没有使用标准的37-40的方向键的键值(Safari 3已经支持了)。

		case 13: 	// enter
		case 27: 	// escape
			dragDrop.releaseElement();
			return false;

如果用户点击Enter或者Esc键,就调用releaseElement()函数。如果你想改变释放元素的按键,可以再这里添加。

		default:
			return true;
	}

如果用户按下了其他键,就执行默认动作并且结束函数。

	dragDrop.setPosition(dragDrop.dXKeys,dragDrop.dYKeys);

现在dXKeys和dYKeys已经更新我们发送到setPosition()函数中来改变元素的位置。

	if (evt.preventDefault)
		evt.preventDefault();
	return false;
},

最后我们需要阻止默认事件,如果用户点击下方向键,那么在执行完上面的代码后页面会向下滚动。W3C兼容浏览器中通过preventDefault实现,在IE中通过返回false实现。

 

释放元素

当用户释放了元素,函数releaseElement就会被调用。他会移除所有代码设置的事件处理程序,移除dragged类,清理draggedObject然后等待用户动作。

releaseElement: function() {
	removeEventSimple(document,'mousemove',dragDrop.dragMouse);
	removeEventSimple(document,'mouseup',dragDrop.releaseElement);
	removeEventSimple(document,'keypress',dragDrop.dragKeys);
	removeEventSimple(document,'keypress',dragDrop.switchKeyEvents);
	removeEventSimple(document,'keydown',dragDrop.dragKeys);
	dragDrop.draggedObject.className = dragDrop.draggedObject.className.replace(/dragged/,'');
	dragDrop.draggedObject = null;
}

你或许在用户释放元素之后还想做些什么,可以把你的函数添加在这里。

 

翻译地址:http://www.quirksmode.org/js/dragdrop.html

posted @ 2010-02-18 23:42 BeiYuu 阅读(...) 评论(...) 编辑 收藏