首页 > 代码库 > javascript动画系列第一篇——模拟拖拽

javascript动画系列第一篇——模拟拖拽

×
目录
[1]原理介绍 [2]代码实现 [3]代码优化[4]拖拽冲突[5]IE兼容

前面的话

  从本文开始,介绍javascript动画系列。javascript本身是具有原生拖放功能的,但是由于兼容性问题,以及功能实现的方式,用的不是很广泛。javascript动画广泛使用的还是模拟拖拽。本文将详细介绍该内容

 

原理介绍

  模拟拖拽最终效果和在桌面上移动文件夹的效果类似

技术分享

  鼠标按下时,拖拽开始。鼠标移动时,被拖拽元素跟着鼠标一起移动。鼠标抬起时,拖拽结束

  所以,拖拽的重点是确定被拖拽元素是如何移动的

<iframe id="embed_dom" style="border: 1px solid #000; display: block; width: 600px; height: 360px;" name="embed_dom" src="https://www.processon.com/embed/57e3ba22e4b0a16a6724a5e9" frameborder="0" width="320" height="240"></iframe>

  假设,鼠标按下时,鼠标对象的clientX和clientY分别为x1和x2。元素距离视口左上角x轴和y轴分别为x0和y0

  鼠标移动的某一时刻,clientX和clientY分别为x2和y2

  所以,元素移动的x轴和y轴距离分别为x2-x1和y2-y1

  元素移动后,元素距离视口左上角x轴和y轴的位置分别为

    X = x0 + (x2-x1)    Y = y0 + (y2-y1)

 

代码实现

  将上面的原理用代码实现如下

  鼠标按下时,初始态的x0和y0分别用offsetLeft和offsetTop表示

  鼠标移动时,瞬时态的x和y分别赋值为定位后元素的left和top

<div id="test" style="height: 100px;width: 100px;background:pink;position:absolute;top:0;left:0;"></div><script>test.onmousedown = function(e){    e = e || event;    //获取元素距离定位父级的x轴及y轴距离    var x0 = this.offsetLeft;    var y0 = this.offsetTop;    //获取此时鼠标距离视口左上角的x轴及y轴距离    var x1 = e.clientX;    var y1 = e.clientY;    test.onmousemove = function(e){        e = e || event;        //获取此时鼠标距离视口左上角的x轴及y轴距离        x2 = e.clientX;        y2 = e.clientY;            //计算此时元素应该距离视口左上角的x轴及y轴距离        var X = x0 + (x2 - x1);        var Y = y0 + (y2 - y1);        //将X和Y的值赋给left和top,使元素移动到相应位置        test.style.left = X;        test.style.top = Y;    }    test.onmouseup = function(e){        //当鼠标抬起时,拖拽结束,则将onmousemove赋值为null即可        test.onmousemove = null;    }}</script>

<iframe style="width: 100%; height: 200px;" src="http://sandbox.runjs.cn/show/zoh2tdsy" frameborder="0" width="320" height="240"></iframe>

代码优化

  使用上面的代码时,会出现一个问题。当鼠标拖动的太快,比onmousemove事件的触发间隔还要快时,鼠标就会从元素上离开。这样就停止了元素的拖拽过程

  此时,如果把mousemove和mouseup事件都加在document上时,即可解决

<div id="test" style="height: 100px;width: 100px;background:pink;position:absolute;top:0;left:0;"></div><script>test.onmousedown = function(e){    e = e || event;    //获取元素距离定位父级的x轴及y轴距离    var x0 = this.offsetLeft;    var y0 = this.offsetTop;    //获取此时鼠标距离视口左上角的x轴及y轴距离    var x1 = e.clientX;    var y1 = e.clientY;    document.onmousemove = function(e){        e = e || event;        //获取此时鼠标距离视口左上角的x轴及y轴距离        x2 = e.clientX;        y2 = e.clientY;            //计算此时元素应该距离视口左上角的x轴及y轴距离        var X = x0 + (x2 - x1);        var Y = y0 + (y2 - y1);        //将X和Y的值赋给left和top,使元素移动到相应位置        test.style.left = X;        test.style.top = Y;    }    document.onmouseup = function(e){        //当鼠标抬起时,拖拽结束,则将onmousemove赋值为null即可        document.onmousemove = null;    }}</script>

<iframe style="width: 100%; height: 200px;" src="http://sandbox.runjs.cn/show/ofwxnyik" frameborder="0" width="320" height="240"></iframe>

拖拽冲突

  由于文字和图片默认支持原生拖放,如果将原生拖放和模拟拖拽掺杂在一起,将造成与预想效果不符的情况

  如果拖放的元素内容存在文字,且文字被选中会触发文字的原生拖放效果

  在文字上面双击鼠标,即可选中文字,再移动鼠标时,会触发文字的原生拖放效果,如下所示

<iframe style="width: 100%; height: 200px;" src="http://sandbox.runjs.cn/show/tuhjeakp" frameborder="0" width="320" height="240"></iframe>

  只要在onmousedown事件阻止浏览器的默认行为即可

<div id="test" style="height: 100px;width: 100px;background:pink;position:absolute;top:0;left:0;">测试文字</div><script>test.onmousedown = function(e){    e = e || event;    //获取元素距离定位父级的x轴及y轴距离    var x0 = this.offsetLeft;    var y0 = this.offsetTop;    //获取此时鼠标距离视口左上角的x轴及y轴距离    var x1 = e.clientX;    var y1 = e.clientY;    document.onmousemove = function(e){        e = e || event;        //获取此时鼠标距离视口左上角的x轴及y轴距离        x2 = e.clientX;        y2 = e.clientY;            //计算此时元素应该距离视口左上角的x轴及y轴距离        var X = x0 + (x2 - x1);        var Y = y0 + (y2 - y1);        //将X和Y的值赋给left和top,使元素移动到相应位置        test.style.left = X;        test.style.top = Y;    }    document.onmouseup = function(e){        //当鼠标抬起时,拖拽结束,则将onmousemove赋值为null即可        document.onmousemove = null;    }    //阻止默认行为    return false;}</script>

<iframe style="width: 100%; height: 200px;" src="http://sandbox.runjs.cn/show/haxmlisa" frameborder="0" width="320" height="240"></iframe>

IE兼容

  以上代码在IE8-浏览器中仍然无法阻止默认行为。此时,为了实现IE兼容,需要使用全局捕获setCapture()和释放捕获releaseCapture()

  首先,先看一下全局捕获的效果

  下面代码中,开启全局捕获之后,页面中的所有点击效果,都相当于针对按钮一的点击效果。释放捕获后,效果消失

  [注意]IE浏览器完全支持全局捕获;chrome不支持,使用全局捕获会报错;firefox不报错,但静默失败

<button id="btn1">按钮一</button><button id="btn2">开启按钮一的全局捕获</button><script>btn1.onclick = function(){    alert(1);}btn2.onclick = function(){    if(btn1.setCapture){        if(btn2.innerHTML.charAt(0) == ){            btn1.setCapture();            btn2.innerHTML = 关闭按钮一的全局捕获;        }else{            btn1.releaseCapture();            btn2.innerHTML = 开启按钮一的全局捕获;            }    }}</script>

<iframe style="width: 100%; height: 50px;" src="http://sandbox.runjs.cn/show/od4voiyk" frameborder="0" width="320" height="240"></iframe>

  通过在IE浏览器设置全局捕获来达到取消文字原生拖放的默认行为

<div id="test" style="height: 100px;width: 100px;background:pink;position:absolute;top:0;left:0;">测试文字</div><script>test.onmousedown = function(e){    e = e || event;    //获取元素距离定位父级的x轴及y轴距离    var x0 = this.offsetLeft;    var y0 = this.offsetTop;    //获取此时鼠标距离视口左上角的x轴及y轴距离    var x1 = e.clientX;    var y1 = e.clientY;    document.onmousemove = function(e){        e = e || event;        //获取此时鼠标距离视口左上角的x轴及y轴距离        x2 = e.clientX;        y2 = e.clientY;            //计算此时元素应该距离视口左上角的x轴及y轴距离        var X = x0 + (x2 - x1);        var Y = y0 + (y2 - y1);        //将X和Y的值赋给left和top,使元素移动到相应位置        test.style.left = X;        test.style.top = Y;    }    document.onmouseup = function(e){        //当鼠标抬起时,拖拽结束,则将onmousemove赋值为null即可        document.onmousemove = null;        //释放全局捕获        if(test.releaseCapture){            test.releaseCapture();        }    }    //阻止默认行为    return false;    //IE8-浏览器阻止默认行为    if(test.setCapture){        test.setCapture();    }}</script>

<iframe style="width: 100%; height: 200px;" src="http://sandbox.runjs.cn/show/91wwt0jz" frameborder="0" width="320" height="240"></iframe>

源码查看

<script type="text/javascript">// 0){ return; } if(select[i].getBoundingClientRect().top <= 0 && select[i+1]){ if(select[i+1].getBoundingClientRect().top > 0){ change(oCon.children[i+2]) } }else{ change(oCon.children[select.length+1]) } }}document.body.onmousewheel = wheel;document.body.addEventListener(‘DOMMouseScroll‘,wheel,false);var oCon = document.getElementById("content");var close = oCon.getElementsByTagName(‘span‘)[0];close.onclick = function(){ if(this.innerHTML == ‘显示目录‘){ this.innerHTML = ‘ב; this.style.background = ‘‘; oCon.style.border = ‘2px solid #ccc‘; oCon.style.width = ‘‘; oCon.style.height = ‘‘; oCon.style.overflow = ‘‘; oCon.style.lineHeight = ‘30px‘; }else{ this.innerHTML = ‘显示目录‘; this.style.background = ‘#3399ff‘; oCon.style.border = ‘none‘; oCon.style.width = ‘60px‘; oCon.style.height = ‘30px‘; oCon.style.overflow = ‘hidden‘; oCon.style.lineHeight = ‘‘; }}for(var i = 2; i < oCon.children.length; i++){ oCon.children[i].onmouseover = function(){ this.style.color = ‘#3399ff‘; } oCon.children[i].onmouseout = function(){ this.style.color = ‘inherit‘; if(this.mark){ this.style.color = ‘#3399ff‘; } } oCon.children[i].onclick = function(){ change(this); } }function change(_this){ for(var i = 2; i < oCon.children.length; i++){ oCon.children[i].mark = false; oCon.children[i].style.color = ‘inherit‘; oCon.children[i].style.textDecoration = ‘none‘; oCon.children[i].style.borderColor = ‘transparent‘; } _this.mark = true; _this.style.color = ‘#3399ff‘; _this.style.textDecoration = ‘underline‘; _this.style.borderColor = ‘#2175bc‘; }// ]]></script><script type="text/javascript" src="http://files.cnblogs.com/files/xiaohuochai/contextMenu.js"></script>

javascript动画系列第一篇——模拟拖拽