04:JS 拖拽功能的实现

JavaScript012

04:JS 拖拽功能的实现,第1张

首先是三个事件,分别是 mousedown , mousemove , mouseup

当鼠标点击按下的时候,需要一个 tag 标识此时已经按下,可以执行 mousemove 里面的具体方法。

clientX , clientY 标识的是鼠标的坐标,分别标识横坐标和纵坐标,并且我们用 offsetX 和 offsetY 来表示元素的元素的初始坐标,移动的举例应该是:

鼠标移动时候的坐标-鼠标按下去时候的坐标。

也就是说定位信息为:

鼠标移动时候的坐标-鼠标按下去时候的坐标+元素初始情况下的 offetLeft.

还有一点也是原理性的东西,也就是拖拽的同时是绝对定位,我们改变的是绝对定位条件下的 left 以及 top 等等值。

补充:也可以通过 html5 的拖放(Drag 和 drop)来实现

Javascript的特点是dom的处理与网页效果,大多数情况我们只用到了这个语言的最简单的功能,比如制作图片轮播/网页的tab等等,这篇文章将向你展示如何在自己的网页上制作拖拽.

有很多理由让你的网站加入拖拽功能,最简单的一个是数据重组.例如:你有一个序列的内容让用户排序,用户需要给每个条目进行输入或者用select 选择,替代前面这个方法的就是拖拽.或许你的网站也需要一个用户可以拖动的导航窗口!那么这些效果都是很简单:因为你可以很容易的实现!

网页上实现拖拽其实也不是很复杂.第一你需要知道鼠标坐标,第二你需要知道用户鼠标点击一个网页元素并实现拖拽,最后我们要实现移动这个元素.

获取鼠标移动信息

第一我们需要获取鼠标的坐标.我们加一个用户函数到document.onmousemove就可以了:

Java代码 收藏代码

document.onmousemove = mouseMove

function mouseMove(ev){

ev = ev || window.event

var mousePos = mouseCoords(ev)

}

function mouseCoords(ev){

if(ev.pageX || ev.pageY){

return {x:ev.pageX, y:ev.pageY}

}

return {

x:ev.clientX + document.body.scrollLeft - document.body.clientLeft,

y:ev.clientY + document.body.scrollTop - document.body.clientTop

}

}

你首先要声明一个evnet对象.不论何时你移动鼠标/点击/按键等等,会对应一个event的事件.在Internet Explorer里event是全局变量,会被存储在window.event里. 在firefox中,或者其他浏览器,event事件会被相应的自定义函数获取.当我们将mouseMove函数赋值于document.onmousemove,mouseMove会获取鼠标移动事件.

(ev = ev || window.event) 这样让ev在所有浏览器下获取了event事件,在Firefox下"||window.event"将不起作用,因为ev已经有了赋值.在MSIE下ev是空的,所以ev将设置为window.event.

因为我们在这篇文章中需要多次获取鼠标坐标,所以我们设计了mouseCoords这个函数,它只包含了一个参数,就是the event.

我们需要运行在MSIE与Firefox为首的其他浏览器下.Firefox以event.pageX和event.pageY来代表鼠标相应于文档左上角的位置.如果你有一个500*500的窗口,而且你的鼠标在正中间,那么paegX和pageY将是250,当你将页面往下滚动500px,那么 pageY将是750.此时pageX不变,还是250.

MSIE和这个相反,MSIE将event.clientX与event.clientY来代表鼠标与ie窗口的位置,并不是文档.当我们有一个 500*500的窗口,鼠标在正中间,那么clientX与clientY也是250,如果你垂直滚动窗口到任何位置,clientY仍然是250,因为相对ie窗口并没有变化.想得到正确的结果,我们必须加入scrollLeft与scrollTop这两个相对于文档鼠标位置的属性.最后,由于MSIE 并没有0,0的文档起始位置,因为通常会设置2px的边框在周围,边框的宽度包含在document.body.clientLeft与 clientTop这两个属性中,我们再加入这些到鼠标的位置中.

很幸运,这样mouseCoords函数就完成了,我们不再为坐标的事操心了.

捕捉鼠标点击

下次我们将知道鼠标何时点击与何时放开.如果我们跳过这一步,我们在做拖拽时将永远不知道鼠标移动上面时的动作,这将是恼人的与违反直觉的.

这里有两个函数帮助我们:onmousedown与onmouseup.我们预先设置函数来接收document.onmousemove,这样看起来很象我们会获取document.onmousedown与document.onmouseup.但是当我们获取 document.onmousedown时,我们同时获取了任何对象的点击属性如:text,images,tables等等.我们只想获取那些需要拖拽的属性,所以我们设置函数来获取我们需要移动的对象.

移动一个元素

我们知道了怎么捕捉鼠标移动与点击.剩下的就是移动元素了.首先,要确定一个明确的页面位置,css样式表要用'absolute'.设置元素绝对位置意味着我们可以用样式表的.top和.left来定位,可以用相对位置来定位了.我们将鼠标的移动全部相对页面top-left,基于这点,我们可以进行下一步了.

当我们定义item.style.position='absolute',所有的操作都是改变left坐标与top坐标,然后它移动了.

Java代码 收藏代码

document.onmousemove = mouseMove

document.onmouseup = mouseUp

var dragObject = null

var mouseOffset = null

function getMouseOffset(target, ev){

ev = ev || window.event

var docPos= getPosition(target)

var mousePos = mouseCoords(ev)

return {x:mousePos.x - docPos.x, y:mousePos.y - docPos.y}

}

function getPosition(e){

var left = 0

var top = 0

while (e.offsetParent){

left += e.offsetLeft

top += e.offsetTop

e = e.offsetParent

}

left += e.offsetLeft

top += e.offsetTop

return {x:left, y:top}

}

function mouseMove(ev){

ev = ev || window.event

var mousePos = mouseCoords(ev)

if(dragObject){

dragObject.style.position = 'absolute'

dragObject.style.top = mousePos.y - mouseOffset.y

dragObject.style.left = mousePos.x - mouseOffset.x

return false

}

}

function mouseUp(){

dragObject = null

}

function makeDraggable(item){

if(!item) return

item.onmousedown = function(ev){

dragObject = this

mouseOffset = getMouseOffset(this, ev)

return false

}

}

你会注意到这个代码几乎是前面的全集,将前面的合在一起就实现了拖拽效果了.

当我们点击一个item时,我们就获取了很多变量,如鼠标位置,鼠标位置自然就包含了那个item的坐标信息了.如果我们点击了一个20*20px图像的正中间,那么鼠标的相对坐标为{x:10,y:10}.当我们点击这个图像的左上角那么鼠标的相对坐标为 {x:0,y:0}.当我们点击时,我们用这个方法取得一些鼠标与图片校对的信息.如果我们不能加载页面item,那么信息将是document信息,会忽略了点击的item信息.

mouseOffset函数使用了另一个函数getPosition.getPosition的作用是返回 item相对页面左上角的坐标,如果我们尝试获取item.offsetLeft或者item.style.left,那么我们将取得item相对与父级的位置,不是整个document.所有的脚本我们都是相对整个document,这样会更好一些.

为了完成getPosition任务,必须循环取得item的父级,我们将加载内容到item的左/上的位置.我们需要管理想要的top与left列表.

自从定义了mousemove这个函数,mouseMove就会一直运行.第一我们确定item的 style.position为absolute,第二我们移动item到前面定义好的位置.当mouse点击被释放,dragObject被设置为 null,mouseMove将不在做任何事.

Dropping an Item

前面的例子目的很简单,就是拖拽item到我们希望到的地方.我们经常还有其他目的如删除item,比如我们可以将item拖到垃圾桶里,或者其他页面定义的位置.

很不幸,我们有一个很大的难题,当我们拖拽,item会在鼠标之下,比如mouseove,mousedown,mouseup或者其他mouse action.如果我们拖拽一个item到垃圾桶上,鼠标信息还在item上,不在垃圾桶上.

怎么解决这个问题呢?有几个方法可以来解决.第一,这是以前比较推荐的,我们在移动鼠标时item会跟随鼠标,并占用了mouseover/mousemove等鼠标事件,我们不这样做,只是让item跟随着鼠标,并不占用mouseover等鼠标事件,这样会解决问题,但是这样并不好看,我们还是希望item能直接跟在mouse下.

另一个选择是不做item的拖拽.你可以改变鼠标指针来显示需要拖拽的item,然后放在鼠标释放的位置.这个解决方案,也是因为美学原因不予接受.

最后的解决方案是,我们并不去除拖拽效果.这种方法比前两种繁杂许多,我们需要定义我们需要释放目标的列表,当鼠标释放时,手工去检查释放的位置是否是在目标列表位置上,如果在,说明是释放在目标位置上了.

Java代码 收藏代码

/*

All code from the previous example is needed with the exception

of the mouseUp function which is replaced below

*/

var dropTargets = []

function addDropTarget(dropTarget){

dropTargets.push(dropTarget)

}

function mouseUp(ev){

ev = ev || window.event

var mousePos = mouseCoords(ev)

for(var i=0i<dropTargets.lengthi++){

var curTarget = dropTargets[i]

var targPos= getPosition(curTarget)

var targWidth = parseInt(curTarget.offsetWidth)

var targHeight = parseInt(curTarget.offsetHeight)

if(

(mousePos.x >targPos.x)&&

(mousePos.x <(targPos.x + targWidth)) &&

(mousePos.y >targPos.y)&&

(mousePos.y <(targPos.y + targHeight))){

// dragObject was dropped onto curTarget!

}

}

dragObject = null

}

鼠标释放时会去取是否有drop属性,如果存在,同时鼠标指针还在drop的范围内,执行drop操作.我们检查鼠标指针位置是否在目标范围是用(mousePos.x>targetPos.x),而且还要符合条件(mousePos.x<(targPos.x + targWidth)).如果所有的条件符合,说明指针确实在范围内,可以执行drop指令了.

Pulling It All Together

最后我们拥有了所有的drag/drop的脚本片断!下一个事情是我们将创建一个DOM处理.

下面的代码将创建container(容器),而且使任何一个需要drag/drop的item变成一个容器的item.代码在这个文章第二个demo的后面,它可以用户记录一个list(列表),定为一个导航窗口在左边或者右边,或者更多的函数你可以想到的.

下一步我们将通过"假代码"让reader看到真代码,下面为推荐:

1、当document第一次载入时,创建dragHelper DIV.dragHelper将给移动的item加阴影.真实的item没有被dragged,只是用了insertBefor和appendChild来移动了,我们隐藏了dragHelper

2、有了mouseDown与mouseUp函数.所有的操作会对应到当到iMouseDown的状态中,只有当mouse左键为按下时iMouseDown才为真,否则为假.

3、我们创建了全局变量DragDrops与全局函数CreateDragContainer.DragDrops包含了一系列相对彼此的容器.任何参数(containers)将通过CreatedcragContainer进行重组与序列化,这样可以自由的移动.CreateDragContainer函数也将item进行绑定与设置属性.

4、现在我们的代码知道每个item的加入,当我们移动处mouseMove,mouseMove函数首先会设置变量target,鼠标移动在上面的item,如果这个item在容器中(checked with getAttribute):

* 运行一小段代码来改变目标的样式.创造rollover效果

* 检查鼠标是否没有放开,如果没有

o 设置curTarget代表当前item

o 记录item的当前位置,如果需要的话,我们可以将它返回

o 克隆当前的item到dragHelper中,我们可以移动带阴影效果的item.

o item拷贝到dragHelper后,原有的item还在鼠标指针下,我们必须删除掉dragObj,这样脚本起作用,dragObj被包含在一个容器中.

o 抓取容器中所有的item当前坐标,高度/宽度,这样只需要记录一次,当item被drag时,每随mouse移动,每移钟就会记录成千上万次.

* 如果没有,不需要做任何事,因为这不是一个需要移动的item

5、检查curTarget,它应该包含一个被移动的item,如果存在,进行下面操作:

* 开始移动带有阴影的item,这个item就是前文所创建的

* 检查每个当前容器中的container,是否鼠标已经移动到这些范围内了

o 我们检查看一下正在拖动的item是属于哪个container

o 放置item在一个container的某一个item之前,或者整个container之后

o 确认item是可见的

* 如果鼠标不在container中,确认item是不可见了.

本文实例讲述了JS基于面向对象实现的拖拽库。分享给大家供大家参考。具体如下:

这是一个面向对象的JS拖拽库,可设置水平锁定、垂直锁定、锁定位置、锁定范围等,设定这些范围后,只能在设定的模式下拖动,我觉得这是个挺不错的拖拽实例。

运行效果截图如下:

在线演示地址如下:

http://demo.jb51.net/js/2015/js-mxdx-draw-plug-codes/

具体代码如下:

<!DOCTYPE

html

PUBLIC

"-//W3C//DTD

XHTML

1.0

Transitional//EN"

"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html

xmlns="http://www.w3.org/1999/xhtml">

<head>

<meta

http-equiv="Content-Type"

content="text/html

charset=utf-8"

/>

<title>拖拽库</title>

<style

type="text/css">

div,h2,p{margin:0padding:0}

body{font:14px/1.5

arial}

#box{width:100pxheight:100pxbackground:#fef4ebpadding:5pxmargin:50pxborder:1px

solid

#f60}

#box

.title{height:25pxbackground:#f60}

#tool{margin-bottom:10px}

</style>

<script

type="text/javascript">

function

Drag()

{

//初始化

this.initialize.apply(this,

arguments)

}

Drag.prototype

=

{

//初始化

initialize

:

function

(drag,

options)

{

this.drag

=

this.$(drag)

this._x

=

this._y

=

0

this._moveDrag

=

this.bind(this,

this.moveDrag)

this._stopDrag

=

this.bind(this,

this.stopDrag)

this.setOptions(options)

this.handle

=

this.$(this.options.handle)

this.maxContainer

=

this.$(this.options.maxContainer)

this.maxTop

=

Math.max(this.maxContainer.clientHeight,

this.maxContainer.scrollHeight)

-

this.drag.offsetHeight

this.maxLeft

=

Math.max(this.maxContainer.clientWidth,

this.maxContainer.scrollWidth)

-

this.drag.offsetWidth

this.limit

=

this.options.limit

this.lockX

=

this.options.lockX

this.lockY

=

this.options.lockY

this.lock

=

this.options.lock

this.onStart

=

this.options.onStart

this.onMove

=

this.options.onMove

this.onStop

=

this.options.onStop

this.handle.style.cursor

=

"move"

this.changeLayout()

this.addHandler(this.handle,

"mousedown",

this.bind(this,

this.startDrag))

},

changeLayout

:

function

()

{

this.drag.style.top

=

this.drag.offsetTop

+

"px"

this.drag.style.left

=

this.drag.offsetLeft

+

"px"

this.drag.style.position

=

"absolute"

this.drag.style.margin

=

"0"

},

startDrag

:

function

(event)

{

var

event

=

event

||

window.event

this._x

=

event.clientX

-

this.drag.offsetLeft

this._y

=

event.clientY

-

this.drag.offsetTop

this.addHandler(document,

"mousemove",

this._moveDrag)

this.addHandler(document,

"mouseup",

this._stopDrag)

event.preventDefault

&&

event.preventDefault()

this.handle.setCapture

&&

this.handle.setCapture()

this.onStart()

},

moveDrag

:

function

(event)

{

var

event

=

event

||

window.event

var

iTop

=

event.clientY

-

this._y

var

iLeft

=

event.clientX

-

this._x

if

(this.lock)

return

this.limit

&&

(iTop

<

0

&&

(iTop

=

0),

iLeft

<

0

&&

(iLeft

=

0),

iTop

>

this.maxTop

&&

(iTop

=

this.maxTop),

iLeft

>

this.maxLeft

&&

(iLeft

=

this.maxLeft))

this.lockY

||

(this.drag.style.top

=

iTop

+

"px")

this.lockX

||

(this.drag.style.left

=

iLeft

+

"px")

event.preventDefault

&&

event.preventDefault()

this.onMove()

},

stopDrag

:

function

()

{

this.removeHandler(document,

"mousemove",

this._moveDrag)

this.removeHandler(document,

"mouseup",

this._stopDrag)

this.handle.releaseCapture

&&

this.handle.releaseCapture()

this.onStop()

},

//参数设置

setOptions

:

function

(options)

{

this.options

=

{

handle:

this.drag,

//事件对象

limit:

true,

//锁定范围

lock:

false,

//锁定位置

lockX:

false,

//锁定水平位置

lockY:

false,

//锁定垂直位置

maxContainer:

document.documentElement

||

document.body,

//指定限制容器

onStart:

function

()

{},

//开始时回调函数

onMove:

function

()

{},

//拖拽时回调函数

onStop:

function

()

{}

//停止时回调函数

}

for

(var

p

in

options)

this.options[p]

=

options[p]

},

//获取id

$

:

function

(id)

{

return

typeof

id

===

"string"

?

document.getElementById(id)

:

id

},

//添加绑定事件

addHandler

:

function

(oElement,

sEventType,

fnHandler)

{

return

oElement.addEventListener

?

oElement.addEventListener(sEventType,

fnHandler,

false)

:

oElement.attachEvent("on"

+

sEventType,

fnHandler)

},

//删除绑定事件

removeHandler

:

function

(oElement,

sEventType,

fnHandler)

{

return

oElement.removeEventListener

?

oElement.removeEventListener(sEventType,

fnHandler,

false)

:

oElement.detachEvent("on"

+

sEventType,

fnHandler)

},

//绑定事件到对象

bind

:

function

(object,

fnHandler)

{

return

function

()

{

return

fnHandler.apply(object,

arguments)

}

}

}

//应用

window.onload

=

function

()

{

var

oBox

=

document.getElementById("box")

var

oTitle

=

oBox.getElementsByTagName("h2")[0]

var

oSpan

=

document.getElementsByTagName("span")[0]

var

oDrag

=

new

Drag(oBox,

{handle:oTitle,

limit:false})

var

aInput

=

document.getElementsByTagName("input")

//锁定范围接口

aInput[0].onclick

=

function

()

{

oDrag.limit

=

!oDrag.limit

this.value

=

oDrag.limit

?

"取消锁定范围"

:

"锁定范围"

}

//水平锁定接口

aInput[1].onclick

=

function

()

{

oDrag.lockX

=

!oDrag.lockX

this.value

=

oDrag.lockX

?

"取消水平锁定"

:

"水平锁定"

}

//垂直锁定接口

aInput[2].onclick

=

function

()

{

oDrag.lockY

=

!oDrag.lockY

this.value

=

oDrag.lockY

?

"取消垂直锁定"

:

"垂直锁定"

}

//锁定位置接口

aInput[3].onclick

=

function

()

{

oDrag.lock

=

!oDrag.lock

this.value

=

oDrag.lock

?

"取消锁定位置"

:

"锁定位置"

}

//开始拖拽时方法

oDrag.onStart

=

function

()

{

oSpan.innerHTML

=

"开始拖拽"

}

//开始拖拽时方法

oDrag.onMove

=

function

()

{

oSpan.innerHTML

=

"left:"

+

this.drag.offsetLeft

+

",

top:"

+

this.drag.offsetTop

}

//开始拖拽时方法

oDrag.onStop

=

function

()

{

oSpan.innerHTML

=

"结束拖拽"

}

}

</script>

</head>

<body>

<div

id="tool">

<input

type="button"

value="锁定范围"

/>

<input

type="button"

value="水平锁定"

/>

<input

type="button"

value="垂直锁定"

/>

<input

type="button"

value="锁定位置"

/>

</div>

<p>拖放状态:<span>未开始</span></p>

<div

id="box">

<h2

class="title"></h2>

</div>

</body>

</html>

希望本文所述对大家的JavaScript程序设计有所帮助。