前面的话

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

原理介绍

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

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

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

  假设,鼠标按下时,鼠标对象的clientX和clientY分别为x1和y1。元素距离视口左上角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 + 'px';test.style.top = Y + 'px';}test.onmouseup = function(e){//当鼠标抬起时,拖拽结束,则将onmousemove赋值为null即可
        test.onmousemove = null;}
}
</script>

【另一种实现】

  由于使用上面的DOM0级事件处理程序时,将只能绑定一个函数,将不利于扩展。所以,将其改写为DOM2级事件处理程序及IE事件处理程序的兼容写法

<div id="test" style="height:100px;width:100px;background:pink;position:absolute;top:0;left:0;"></div>
<script>
function addEvent(target,type,handler){if(target.addEventListener){target.addEventListener(type,handler,false);}else{target.attachEvent('on'+type,function(event){return handler.call(target,event);});}
}
(function(){var x0,y0,x1,y1,isMoving;var ele = document.getElementById('test');var mousedownHandler = function(e){e = e || event;//获取元素距离定位父级的x轴及y轴距离
    x0 = this.offsetLeft;y0 = this.offsetTop;//获取此时鼠标距离视口左上角的x轴及y轴距离
    x1 = e.clientX;y1 = e.clientY;//按下鼠标时,表示正在运动
    isMoving = true;
}
var mousemoveHandler = function(e){//如果没有触发down事件,而直接触发move事件,则函数直接返回if(!isMoving){return;}e = e || event;//获取此时鼠标距离视口左上角的x轴及y轴距离var x2 = e.clientX;var y2 = e.clientY;    //计算此时元素应该距离视口左上角的x轴及y轴距离var X = x0 + (x2 - x1);var Y = y0 + (y2 - y1);//将X和Y的值赋给left和top,使元素移动到相应位置
    ele.style.left = X + 'px';ele.style.top = Y + 'px';
}
var mouseupHandler = function(e){//鼠标抬起时,表示停止运动
    isMoving = false;
}addEvent(ele,'mousedown',mousedownHandler);
addEvent(ele,'mousemove',mousemoveHandler)
addEvent(ele,'mouseup',mouseupHandler)})();
</script>

代码优化

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

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

<div id="test" style="height:100px;width:100px;background:pink;position:absolute;top:0;left:0;"></div>
<script>
function addEvent(target,type,handler){if(target.addEventListener){target.addEventListener(type,handler,false);}else{target.attachEvent('on'+type,function(event){return handler.call(target,event);});}
}
(function(){var x0,y0,x1,y1,isMoving;var ele = document.getElementById('test');var mousedownHandler = function(e){e = e || event;//获取元素距离定位父级的x轴及y轴距离
    x0 = this.offsetLeft;y0 = this.offsetTop;//获取此时鼠标距离视口左上角的x轴及y轴距离
    x1 = e.clientX;y1 = e.clientY;//按下鼠标时,表示正在运动
    isMoving = true;
}
var mousemoveHandler = function(e){//如果没有触发down事件,而直接触发move事件,则函数直接返回if(!isMoving){return;}e = e || event;//获取此时鼠标距离视口左上角的x轴及y轴距离var x2 = e.clientX;var y2 = e.clientY;    //计算此时元素应该距离视口左上角的x轴及y轴距离var X = x0 + (x2 - x1);var Y = y0 + (y2 - y1);//将X和Y的值赋给left和top,使元素移动到相应位置
    ele.style.left = X + 'px';ele.style.top = Y + 'px';
}
var mouseupHandler = function(e){//鼠标抬起时,表示停止运动
    isMoving = false;
}addEvent(ele,'mousedown',mousedownHandler);
addEvent(ele,'mousemove',mousemoveHandler)
addEvent(ele,'mouseup',mouseupHandler)})();
</script>

拖拽冲突

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

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

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

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

<div id="test" style="height:100px;width:100px;background:pink;position:absolute;top:0;left:0;">测试文字</div>
<script>
function addEvent(target,type,handler){if(target.addEventListener){target.addEventListener(type,handler,false);}else{target.attachEvent('on'+type,function(event){return handler.call(target,event);});}
}
(function(){var x0,y0,x1,y1,isMoving;var ele = document.getElementById('test');var mousedownHandler = function(e){e = e || event;//获取元素距离定位父级的x轴及y轴距离
    x0 = this.offsetLeft;y0 = this.offsetTop;//获取此时鼠标距离视口左上角的x轴及y轴距离
    x1 = e.clientX;y1 = e.clientY;//按下鼠标时,表示正在运动
    isMoving = true;
}
var mousemoveHandler = function(e){//如果没有触发down事件,而直接触发move事件,则函数直接返回if(!isMoving){return;}e = e || event;//获取此时鼠标距离视口左上角的x轴及y轴距离var x2 = e.clientX;var y2 = e.clientY;    //计算此时元素应该距离视口左上角的x轴及y轴距离var X = x0 + (x2 - x1);var Y = y0 + (y2 - y1);//将X和Y的值赋给left和top,使元素移动到相应位置
    ele.style.left = X + 'px';ele.style.top = Y + 'px';
}
var mouseupHandler = function(e){//鼠标抬起时,表示停止运动
    isMoving = false;
}
var preventDefaultHandler = function(e){e = e || event;if(e.preventDefault){e.preventDefault();}else{e.returnValue = false;}
}
addEvent(ele,'mousedown',mousedownHandler);
addEvent(ele,'mousedown',preventDefaultHandler);
addEvent(document,'mousemove',mousemoveHandler)
addEvent(document,'mouseup',mouseupHandler)})();
</script>

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>

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

<div id="test" style="height: 100px;width: 100px;background:pink;position:absolute;top:0;left:0;">测试文字</div>
<script>
function addEvent(target,type,handler){if(target.addEventListener){target.addEventListener(type,handler,false);}else{target.attachEvent('on'+type,function(event){return handler.call(target,event);});}
}
(function(){var x0,y0,x1,y1,isMoving;var ele = document.getElementById('test');var mousedownHandler = function(e){e = e || event;//获取元素距离定位父级的x轴及y轴距离
    x0 = this.offsetLeft;y0 = this.offsetTop;//获取此时鼠标距离视口左上角的x轴及y轴距离
    x1 = e.clientX;y1 = e.clientY;//按下鼠标时,表示正在运动
    isMoving = true;
}
var mousemoveHandler = function(e){//如果没有触发down事件,而直接触发move事件,则函数直接返回if(!isMoving){return;}e = e || event;//获取此时鼠标距离视口左上角的x轴及y轴距离var x2 = e.clientX;var y2 = e.clientY;    //计算此时元素应该距离视口左上角的x轴及y轴距离var X = x0 + (x2 - x1);var Y = y0 + (y2 - y1);//将X和Y的值赋给left和top,使元素移动到相应位置
    ele.style.left = X + 'px';ele.style.top = Y + 'px';
}
var mouseupHandler = function(e){//鼠标抬起时,表示停止运动
    isMoving = false;//释放全局捕获if(ele.releaseCapture){ele.releaseCapture();}
}
var preventDefaultHandler = function(e){e = e || event;if(e.preventDefault){e.preventDefault();}else{e.returnValue = false;}//IE8-浏览器阻止默认行为if(ele.setCapture){ele.setCapture();}
}
addEvent(ele,'mousedown',mousedownHandler);
addEvent(ele,'mousedown',preventDefaultHandler);
addEvent(document,'mousemove',mousemoveHandler)
addEvent(document,'mouseup',mouseupHandler)})();
</script>

源码查看

javascript动画系列第一篇——模拟拖拽相关推荐

  1. javascript动画系列第二篇——磁性吸附

    前面的话 上一篇,我们介绍了元素拖拽的实现.但在实际应用中,常常需要为拖拽的元素限定范围.而通过限定范围,再增加一些辅助的措施,就可以实现磁性吸附的效果 范围限定 如果我们限定元素只可以在可视范围内移 ...

  2. javascript面向对象系列第一篇——构造函数和原型对象

    前面的话 一般地,javascript使用构造函数和原型对象来进行面向对象编程,它们的表现与其他面向对象编程语言中的类相似又不同.本文将详细介绍如何用构造函数和原型对象来创建对象 构造函数 构造函数是 ...

  3. 模拟拖拽-小火柴博客

    学习他人的博客写出来的东西,记录一下. 原博客写的比我好多了,源博客:小火柴的蓝色理想 - javascript动画系列第一篇--模拟拖拽 模拟拖拽 1 var Div = document.getE ...

  4. Webpack系列-第一篇基础杂记

    系列文章 Webpack系列-第一篇基础杂记 Webpack系列-第二篇插件机制杂记 Webpack系列-第三篇流程杂记 前言 公司的前端项目基本都是用Webpack来做工程化的,而Webpack虽然 ...

  5. 深入理解表单脚本系列第一篇——表单对象

    前面的话 javascript最初的一个应用就是分担服务器处理表单的责任,打破处处依赖服务器的局面.尽管目前的web和javascript已经有了长足的发展,但web表单的变化并不明显.由于web表单 ...

  6. Android 系统(243)---Android进程系列第一篇---进程基础

    Android进程系列第一篇---进程基础 内容预览.png 概述: 本文主要讲解进程基础,更深入的认识有血有肉的进程,内容涉及进程控制块,信号,进程FD泄露等等.仅供参考,欢迎指正. 一.从Linu ...

  7. JavaScript设计模式系列—模式篇总结(上)

    转载请注明预见才能遇见的博客:http://my.csdn.net/ 原文地址:https://blog.csdn.net/pcaxb/article/details/102517956 JavaSc ...

  8. javascript实现对图片的随意拖拽,放大缩小

    [JS]基于javascript实现对图片的随意拖拽,放大缩小 最近写项目的过程中需要对图片进行一个操作,点击之后弹出图片,在可定div范围内对图片进行任意拖拽位置和鼠标滚动时对其放大缩小,双击图片恢 ...

  9. 深入理解javascript函数系列第二篇——函数参数

    前面的话 javascript函数的参数与大多数其他语言的函数的参数有所不同.函数不介意传递进来多少个参数,也不在乎传进来的参数是什么数据类型,甚至可以不传参数.本文是深入理解javascript函数 ...

最新文章

  1. Python 多进程笔记 — 启动进程的方式、守护进程、进程间通信、进程池、进程池之间通信、多进程生产消费模型
  2. 来聊聊可形变卷积及其应用
  3. IAR编译器的常见问题
  4. 提高你开发效率的十五个Visual Studio 2010使用技巧
  5. 使用Dependency Walkers来检查exe文件中缺少的dll
  6. 服务器lIS绑定网站,DataList 中动态绑定服务器子控件的代码
  7. Microsoft.XMLHTTP 使用方式
  8. bootstrap-响应式导航条
  9. 域名,ip,mac地址
  10. 质数之和c语言编程,C程序检查一个数字是否可以表示为两个质数之和
  11. 洛谷P5709、P5710、P5711、P5712题题解(Java语言描述)
  12. 基于css和js的轮播效果图实现
  13. 如何编译Linux kernel
  14. Kiwi browser 手机谷歌浏览器如何同步谷歌扩展插件
  15. kettle-xml输入
  16. 腾讯视频二面【附常见HR面试问题】
  17. 2023最新苹果CMS10暗色系动漫影视网站模板源码+UI高端大气
  18. 计算机网络——基础篇
  19. Java面试——数据库
  20. soul从入门到进阶05——soul-bootstrap数据同步流程

热门文章

  1. 无需服务器的个人博客 (2018.5.22更新)
  2. VR医疗|全球首例“换头手术”成功了么?
  3. selenium--字符串/整型问题Can't convert 'int' object to str implicitly提示解决方法
  4. mp3 音频 音乐 tag ID3 ID3V1 ID3V2 标签 读取信息 获得图片 jpeg bmp 图片转换(上)
  5. CISCO NAT 经典配置合集
  6. 【译】五个ES6功能,让你编写代码更容易
  7. vue-quill-editor + element-ui upload实现富文本图片上传
  8. python基础之数据类型与变量
  9. 【转】mysqldump的锁表的问题
  10. Windows IIS7 下安装配置 PHP7.0