首页 web前端 js教程 javascript拖放效果深入研究

javascript拖放效果深入研究

Dec 14, 2016 pm 04:05 PM

拖放效果,也叫拖拽,学名Drag-and-drop ,是最常见的js特效之一。
如果忽略很多细节,实现起来很简单,但往往细节才是难点所在。
这个程序的原型是在做图片切割效果的时候做出来的,那时参考了好几个同类的效果,跟muxrwc和BlueDestiny学习了不少东西。
虽然每次整理都觉得很好了,不过每隔一段时间又会发现得某个地方可以改善,某个地方有错误,某些需求需要实现,就像自己学习的知识那样。

这里考虑到有的人可能只需要简单的拖放,所以有一个 简化版的拖放SimpleDrag ,方便学习。

程序原理

这里以SimpleDrag为例说一下基本原理。

首先初始化程序中要一个拖放对象:

this.Drag = $(drag);

还要两个参数在开始时记录鼠标相对拖放对象的坐标:

this._x = this._y = 0;

还有两个事件对象函数用于添加移除事件:

this._fM = BindAsEventListener(this, this.Move); 
this._fS = Bind(this, this.Stop);

分别是拖动程序和停止拖动程序。 
拖放对象的position必须是absolute绝对定位:

this.Drag.style.position = "absolute";

最后把Start开始拖放程序绑定到拖放对象mousedown事件:

addEventHandler(this.Drag, "mousedown", BindAsEventListener(this, this.Start));

鼠标在拖放对象按住,就会触发Start程序,主要是用来准备拖动,在这里记录鼠标相对拖放对象的坐标:

this._x = oEvent.clientX - this.Drag.offsetLeft; 
this._y = oEvent.clientY - this.Drag.offsetTop;

并把_fM拖动程序和_fS停止拖动程序分别绑定到document的mousemove和mouseup事件:

addEventHandler(document, "mousemove", this._fM); 
addEventHandler(document, "mouseup", this._fS);

绑定到document可以保证事件在整个窗口文档中都有效。

当鼠标在文档上移动时,就会触发Move程序了,这里就是实现拖动的程序。 
通过现在鼠标的坐标值跟开始拖动时鼠标相对的坐标值的差就可以得到拖放对象应该设置的left和top了:

this.Drag.style.left = oEvent.clientX - this._x + "px"; 
this.Drag.style.top = oEvent.clientY - this._y + "px";

最后放开鼠标后就触发Stop程序结束拖放。 
这里的主要作用是把Start程序中给document添加的事件移除:

removeEventHandler(document, "mousemove", this._fM); 
removeEventHandler(document, "mouseup", this._fS);

这样一个简单的拖放程序就做好了,下面说说其他扩展和细节部分。

拖放锁定

锁定分三种,分别是:水平方向锁定(LockX)、垂直方向锁定(LockY)、完全锁定(Lock)。 
这个比较简单,水平和垂直方向的锁定只要在Move判断是否锁定再设置left和top就行,如果是完全锁定就直接返回。

if(!this.LockX){ this.Drag.style.left = ...; } 
if(!this.LockY){ this.Drag.style.top = ...; }

触发对象

触发对象是用来触发拖放程序的。有的时候不需要整个拖放对象都用来触发,这时就需要触发对象了。 
使用了触发对象后,进行移动的还是拖放对象,只是用触发对象来触发拖放(一般的使用是把触发对象放到拖放对象里面)。

范围限制

要设置范围限制必须先把Limit设为true。范围限制分两种,分别是固定范围和容器范围限制,主要在Move程序中设置。 
原理是当比较的值超过范围时,修正left和top要设置的值使拖放对象能保持在设置的范围内。

固定范围限制

容器范围限制就是指定上下左右的拖放范围。 
各个属性的意思是:

上(mxTop):top限制;

下(mxBottom):top+offsetHeight限制;

左(mxLeft):left限制;

右(mxRight):left+offsetWidth限制。

如果范围设置不正确,可能导致上下或左右同时超过范围的情况,程序中有一个Repair程序用来修正范围参数的。
Repair程序会在程序初始化和Start程序中执行,在Repair程序中修正mxRight和mxBottom:

this.mxRight = Math.max(this.mxRight, this.mxLeft + this.Drag.offsetWidth); 
this.mxBottom = Math.max(this.mxBottom, this.mxTop + this.Drag.offsetHeight);

其中mxLeft+offsetWidth和mxTop+offsetHeight分别是mxRight和mxBottom的最小范围值。

根据范围参数修正移动参数:

iLeft = Math.max(Math.min(iLeft, mxRight - this.Drag.offsetWidth), mxLeft); 
iTop = Math.max(Math.min(iTop, mxBottom - this.Drag.offsetHeight), mxTop);

对于左边上边要取更大的值,对于右边下面就要取更小的值。

容器范围限制

容器范围限制的意思就是把范围限制在一个容器_mxContainer内。 
要注意的是拖放对象必须包含在_mxContainer中,因为程序中是使用相对定位来设置容器范围限制的(如果是在容器外就要用绝对定位,这样处理就比较麻烦了),还有就是容器空间要比拖放对象大,这个就不用说明了吧。 
原理跟固定范围限制差不多,只是范围参数是根据容器的属性的设置的。

当设置了容器,会自动把position设为relative来相对定位:

!this._mxContainer || CurrentStyle(this._mxContainer).position == "relative" || (this._mxContainer.style.position = "relative");

注意relative要在获取offsetLeft和offsetTop即设置_x和_y之前设置,offset才能正确获取值。

由于是相对定位,对于容器范围来说范围参数上下左右的值分别是0、clientHeight、0、clientWidth。

clientWidth和clientHeight是容器可视部分的宽度和高度(详细参考这里)。 
为了容器范围能兼容固定范围的参数,程序中会获取容器范围和固定范围中范围更小的值:

mxLeft = Math.max(mxLeft, 0); 
mxTop = Math.max(mxTop, 0); 
mxRight = Math.min(mxRight, this._mxContainer.clientWidth); 
mxBottom = Math.min(mxBottom, this._mxContainer.clientHeight);

注意如果在程序执行之前设置过拖放对象的left和top而容器没有设置relative,在自动设置relative时会发生移位现象,所以程序在初始化时就执行一次Repair程序防止这种情况。因为offsetLeft和offsetTop要在设置relative之前获取才能正确获取值,所以在Start程序中Repair要在设置_x和_y之前执行。

因为设置相对定位的关系,容器_mxContainer设置过后一般不要取消或修改,否则很容易造成移位异常。

鼠标捕获

我在一个拖放实例中看到,即使鼠标移动到浏览器外面,拖放程序依然能够执行,仔细查看后发现是用了setCapture。 
鼠标捕获(setCapture)是这个程序的重点,作用是将鼠标事件捕获到当前文档的指定的对象。这个对象会为当前应用程序或整个系统接收所有鼠标事件。 
使用很简单:

this._Handle.setCapture();

setCapture捕获以下鼠标事件:onmousedown、onmouseup、onmousemove、onclick、ondblclick、onmouseover和onmouseout。 
程序中主要是要捕获onmousemove和onmouseup事件。 
msdn的介绍中还说到setCapture有一个bool参数,用来设置在容器内的鼠标事件是否都被容器捕获。
容器就是指调用setCapture的对象,大概意思就是: 
参数为true时(默认)容器会捕获容器内所有对象的鼠标事件,即容器内的对象不会触发鼠标事件(跟容器外的对象一样); 
参数为false时容器不会捕获容器内对象的鼠标事件,即容器内的对象可以正常地触发事件和取消冒泡。 
而对于容器外的鼠标事件无论参数是什么都会被捕获, 
可以用下面这个简单的例子测试一下(ie):

 
 

mouseover
 
<script>document.body.setCapture(); </script> 
 

这里的参数是true,一开始body会捕获所有鼠标事件,即使鼠标经过div也不会触发onmousemove事件。 
换成false的话,div就可以捕获鼠标事件,就能触发onmousemove事件了。

拖放结束后还要使用releaseCapture释放鼠标,这个可以放在Stop程序中:

this._Handle.releaseCapture();

setCapture是ie的鼠标捕获方法,对于ff也有对应的captureEvents和releaseEvents方法。 
但这两个方法只能由window来调用,而且muxrwc说这两个方法在DOM2里已经废弃了,在ff里已经没用了。 
不过ff里貌似会自动设置取消鼠标捕获,但具体的情形就不清楚了,找不到一个比较详细的介绍,谁有这方面的资料记得告诉我啊。

下面都是我的猜测,ff的鼠标捕获相当于能自动设置和释放的document.body.setCapture(false)。 
因为我测试下面的程序,发现ie和ff效果是差不多的: 
ie:

 
 

 
<script> <br>document.body.onmousedown=function(){this.setCapture(false)} <br>document.body.onmouseup=function(){this.releaseCapture()} <br>document.onmousemove=function(){aa.innerHTML+=1} <br></script> 
 

ff:

 
 

 
<script> <br>document.onmousemove=function(){aa.innerHTML+=1} <br></script> 
 

可惜没有权威的资料参考就只能猜猜了,还有很多还没有理解的地方以后再研究拉。

注意ff2下的鼠标捕获有一个bug,当拖放对象内部没有文本内容并拖放到浏览器外时捕获就会失效。 
给拖放对象插入一个空文本,例如 就可以解决,不过这个bug在ff3已经修正了。

焦点丢失

一般情况下,鼠标捕获都能正常捕获事件,但如果浏览器窗口的焦点丢失就会导致捕获失效。 
我暂时测试到会导致焦点丢失的操作包括切换窗口(包括alt+tab),alert和popup等弹出窗体。 
当焦点丢失时应该同时执行Stop程序结束拖放,但当焦点丢失就不能捕获mouseup事件也就是不能触发_fS。 
还好ie有onlosecapture事件会在捕获失效时触发,针对这个情况可以这样设置:

addEventHandler(this._Handle, "losecapture", this._fS);

并在Stop程序中移除:

removeEventHandler(this._Handle, "losecapture", this._fS);

但ff没有类似的方法,不过muxrwc找到一个替代losecapture的window.onblur事件,那么可以在Start程序中设置:

addEventHandler(window, "blur", this._fS);

在Stop程序中移除:

removeEventHandler(window, "blur", this._fS);

那ie也有window.onblur事件,那用window.onblur代替losecapture不就可以省一段代码了吗。 
接着我做了一些测试,发现基本上触发losecapture的情况都会同时触发window.onblur,看来真的可以。 
于是我修改程序用window.onblur代替losecapture,但测试后就出问题了,我发现如果我用alt+tab切换到另一个窗口,拖动还可以继续,但这个时候应该是已经丢失焦点了。

于是我逐一排除测试和程序代码,结果发现如果使用了DTD,那么window.onblur会在再次获得焦点时才会触发。 
大家可以用下面这段代码测试:

 
<script>window.onblur=function(){alert(1)} </script>

在切换到其他程序后,再切换回来才会触发window.onblur,还有几个比较怪异的状况就不说了,反正ie用window.onblur是不理想的了。

默认动作

对选择状态的文本内容、连接和图片等进行拖放操作会触发系统的默认动作,例如ie中拖动图片鼠标会变成禁止操作状态,这样会导致这个拖放程序执行失败。

不过ie在设置了setCapture之后,通过用户界面用鼠标进行拖放操作和内容选择都会被禁止。 
意思就是setCapture之后就不能对文档内容进行拖放和选择,注意这里的拖放是指系统的默认动作,例如ondragstart就不会被触发。 
不过如果setCapture的参数是false的话,容器内的对象还是可以触发事件的(具体看鼠标捕获部分),所以setCapture的参数要设成true或保留默认值。

而ff的鼠标捕获没有这个功能,但可以用preventDefault来取消事件的默认动作来解决:

oEvent.preventDefault();

ps:据说使用preventDefault会出现mouseup丢失的情况,但我在ff3中测试没有发现,如果各位发现任何mouseup丢失的情况,务必告诉我啊

清除选择

ie在设置setCapture之后内容选择都会被禁止,但也因此不会清除在设置之前就已经选择的内容,而且设置之后也能通过其他方式选择内容, 
例如用ctrl+a来选择内容。 
ps:onkeydown、onkeyup和onkeypress事件不会受到鼠标捕获影响。 
而ff在mousedown时就能清除原来选择的内容,但拖动鼠标,ctrl+a时还是会继续选择内容。 
不过在丢弃了系统默认动作之后,这样的选择并不会对拖放操作造成影响,这里设置主要还是为了更好的体验。

以前我用禁止拖放对象被选择的方法来达到目的,即ie中设置拖放对象的onselectstart返回false,在ff中设置样式MozUserSelect(css:-moz-user-select)为none。 
但这种方法只能禁止拖放对象本身被选择,后来找到个更好的方法清除选择,不但不影响拖放对象的选择效果,还能对整个文档进行清除:

ie: document.selection.empty()

ff: window.getSelection().removeAllRanges()

为了防止在拖放过程中选择内容,所以把它放到Move程序中,下面是兼容的写法:

window.getSelection ? window.getSelection().removeAllRanges() : document.selection.empty();

margin

还有一个情况,当拖放对象设置了margin,那么拖放的时候就会错位(给SimpleDrag的拖放对象设置margin就可以测试)。 
原因是在Start程序设置_x和_y时是使用offset获取的,而这个值是包括margin的,所以在设置left和top之前要减去这个margin。 
但如果在Start程序中就去掉margin那么在Move程序中设置范围限制时就会计算错误, 
所以最好是在Start程序中获取值:

this._marginLeft = parseInt(CurrentStyle(this.Drag).marginLeft) || 0; 
this._marginTop = parseInt(CurrentStyle(this.Drag).marginTop) || 0;

其中CurrentStyle是用来获取最终样式,详细看这里的最终样式部分。

在Move程序中设置值:

this.Drag.style.left = iLeft - this._marginLeft + "px"; 
this.Drag.style.top = iTop - this._marginTop + "px";

要注意margin要在范围修正只后再设置,否则会错位。

【透明背景bug】

在ie有一个透明背景bug(不知算不算bug),可以用下面的代码测试:

 
 
 

 
 

会发现背景点击触发不了事件,不过点击边框的话还是可以触发。 
为什么呢?再用下面的代码测试:

 
 
 
 

 
 
 
 
 

应该能看出个大概了,下面两个div超出body(即超出红色框)的部分就触发不了事件。 
也就是说当触发事件的点,在body以外,而背景又是透明的,那么就会误认为触发点是在了body外空白的地方,所以触发不了事件。 
那解决的方法就是,使事件触发点保持在body内,或者设置一个非透明背景。

那程序中只要给拖放对象设一个背景色就可以解决了,但有时需求正好是要透明(例如切割效果),那怎么办呢? 
首先想到的是加上背景色后设置完全透明,但这样连边框,容器内的对象等都完全透明了,这个不好。 
我想到的一个解决方法是在容器里面加一个层,覆盖整个容器,并设置背景色和完全透明:

with(this._Handle.appendChild(document.createElement("div")).style){ 
width = height = "100%"; backgroundColor = "#fff"; filter = "alpha(opacity:0)"; 
}

当发现程序有这个bug出现,把程序可选参数Transparent设为true就会自动插入这样一个层了。
各位如果有更好的方法请多多指点。

暂时就研究到这里,不过还有iframe,滚屏等这些还没考虑到,等以后有需要了再来研究拉。

 更多相关文章请关注PHP中文网(www.php.cn)!


本站声明
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn

热AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover

AI Clothes Remover

用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool

Undress AI Tool

免费脱衣服图片

Clothoff.io

Clothoff.io

AI脱衣机

AI Hentai Generator

AI Hentai Generator

免费生成ai无尽的。

热门文章

R.E.P.O.能量晶体解释及其做什么(黄色晶体)
3 周前 By 尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.最佳图形设置
3 周前 By 尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.如果您听不到任何人,如何修复音频
3 周前 By 尊渡假赌尊渡假赌尊渡假赌
WWE 2K25:如何解锁Myrise中的所有内容
3 周前 By 尊渡假赌尊渡假赌尊渡假赌

热工具

记事本++7.3.1

记事本++7.3.1

好用且免费的代码编辑器

SublimeText3汉化版

SublimeText3汉化版

中文版,非常好用

禅工作室 13.0.1

禅工作室 13.0.1

功能强大的PHP集成开发环境

Dreamweaver CS6

Dreamweaver CS6

视觉化网页开发工具

SublimeText3 Mac版

SublimeText3 Mac版

神级代码编辑软件(SublimeText3)

如何在微软Edge中启用超级拖放模式 如何在微软Edge中启用超级拖放模式 Mar 18, 2024 am 09:40 AM

MicrosoftEdge的拖放功能让您可以方便地打开网页上的链接或文本,这种功能既实用又省时。要使用这一功能,只需将链接或文本拖放到网页的任意位置即可。本文将向您介绍如何在MicrosoftEdge中启用或禁用超级拖放模式。什么是MicrosoftEdge中的超级拖放模式?微软Edge引入了一项名为“超级拖放”的新功能,用户只需简单地拖放链接即可在新标签页中快速打开。只需将链接拖拽到Edge浏览器窗口的任何位置即可轻松实现。Edge会自动在一个新的选项卡中加载该链接。此外,用户还可以根据个人偏

深入探讨模型、数据和框架:一份详尽的54页高效大语言模型综述 深入探讨模型、数据和框架:一份详尽的54页高效大语言模型综述 Jan 14, 2024 pm 07:48 PM

大规模语言模型(LLMs)在许多重要任务中展现出了引人注目的能力,包括自然语言理解、语言生成和复杂推理,并对社会产生了深远的影响。然而,这些出色的能力却需要大量的训练资源(如左图所示)和较长的推理时间(如右图所示)。因此,研究人员需要开发有效的技术手段来解决它们的效率问题。此外,从图的右侧还可以看出,一些高效的LLMs(LanguageModels)如Mistral-7B,已经成功应用于LLMs的设计和部署中。这些高效的LLMs在保持与LLaMA1-33B相近的准确性的同时,能够大大减少推理内存

如何使用WebSocket和JavaScript实现在线语音识别系统 如何使用WebSocket和JavaScript实现在线语音识别系统 Dec 17, 2023 pm 02:54 PM

如何使用WebSocket和JavaScript实现在线语音识别系统引言:随着科技的不断发展,语音识别技术已经成为了人工智能领域的重要组成部分。而基于WebSocket和JavaScript实现的在线语音识别系统,具备了低延迟、实时性和跨平台的特点,成为了一种被广泛应用的解决方案。本文将介绍如何使用WebSocket和JavaScript来实现在线语音识别系

WebSocket与JavaScript:实现实时监控系统的关键技术 WebSocket与JavaScript:实现实时监控系统的关键技术 Dec 17, 2023 pm 05:30 PM

WebSocket与JavaScript:实现实时监控系统的关键技术引言:随着互联网技术的快速发展,实时监控系统在各个领域中得到了广泛的应用。而实现实时监控的关键技术之一就是WebSocket与JavaScript的结合使用。本文将介绍WebSocket与JavaScript在实时监控系统中的应用,并给出代码示例,详细解释其实现原理。一、WebSocket技

如何使用WebSocket和JavaScript实现在线预约系统 如何使用WebSocket和JavaScript实现在线预约系统 Dec 17, 2023 am 09:39 AM

如何使用WebSocket和JavaScript实现在线预约系统在当今数字化的时代,越来越多的业务和服务都需要提供在线预约功能。而实现一个高效、实时的在线预约系统是至关重要的。本文将介绍如何使用WebSocket和JavaScript来实现一个在线预约系统,并提供具体的代码示例。一、什么是WebSocketWebSocket是一种在单个TCP连接上进行全双工

如何利用JavaScript和WebSocket实现实时在线点餐系统 如何利用JavaScript和WebSocket实现实时在线点餐系统 Dec 17, 2023 pm 12:09 PM

如何利用JavaScript和WebSocket实现实时在线点餐系统介绍:随着互联网的普及和技术的进步,越来越多的餐厅开始提供在线点餐服务。为了实现实时在线点餐系统,我们可以利用JavaScript和WebSocket技术。WebSocket是一种基于TCP协议的全双工通信协议,可以实现客户端与服务器的实时双向通信。在实时在线点餐系统中,当用户选择菜品并下单

SD社区的I2V-Adapter:无需配置,即插即用,完美兼容图生视频插件 SD社区的I2V-Adapter:无需配置,即插即用,完美兼容图生视频插件 Jan 15, 2024 pm 07:48 PM

图像到视频生成(I2V)任务是计算机视觉领域的一项挑战,旨在将静态图像转化为动态视频。这个任务的难点在于从单张图像中提取并生成时间维度的动态信息,同时保持图像内容的真实性和视觉上的连贯性。现有的I2V方法通常需要复杂的模型架构和大量的训练数据来实现这一目标。近期,快手主导的一项新研究成果《I2V-Adapter:AGeneralImage-to-VideoAdapterforVideoDiffusionModels》发布。该研究引入了一种创新的图像到视频转换方法,提出了一种轻量级适配器模块,即I

JavaScript和WebSocket:打造高效的实时天气预报系统 JavaScript和WebSocket:打造高效的实时天气预报系统 Dec 17, 2023 pm 05:13 PM

JavaScript和WebSocket:打造高效的实时天气预报系统引言:如今,天气预报的准确性对于日常生活以及决策制定具有重要意义。随着技术的发展,我们可以通过实时获取天气数据来提供更准确可靠的天气预报。在本文中,我们将学习如何使用JavaScript和WebSocket技术,来构建一个高效的实时天气预报系统。本文将通过具体的代码示例来展示实现的过程。We

See all articles