javascript动画系列第一篇——模拟拖拽
前面的话
从本文开始,介绍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动画系列第一篇——模拟拖拽的更多相关文章
- 深入理解javascript函数系列第一篇——函数概述
× 目录 [1]定义 [2]返回值 [3]调用 前面的话 函数对任何一门语言来说都是一个核心的概念.通过函数可以封装任意多条语句,而且可以在任何地方.任何时候调用执行.在javascript里,函数即 ...
- 深入理解javascript函数系列第一篇
前面的话 函数对任何一门语言来说都是核心的概念.通过函数可以封装任意多条语句,而且可以在任何地方.任何时候调用执行.在javascript里,函数即对象,程序可以随意操控它们.函数可以嵌套在其他函数中 ...
- javascript运动系列第一篇——匀速运动
× 目录 [1]简单运动 [2]定时器管理 [3]分享到效果[4]移入移出[5]运动函数[6]透明度[7]多值[8]多物体[9]回调[10]函数完善[11]最终函数 前面的话 除了拖拽以外,运动也是j ...
- 深入理解javascript对象系列第一篇——初识对象
× 目录 [1]定义 [2]创建 [3]组成[4]引用[5]方法 前面的话 javascript中的难点是函数.对象和继承,前面已经介绍过函数系列.从本系列开始介绍对象部分,本文是该系列的第一篇——初 ...
- 深入理解javascript作用域系列第一篇——内部原理
× 目录 [1]编译 [2]执行 [3]查询[4]嵌套[5]异常[6]原理 前面的话 javascript拥有一套设计良好的规则来存储变量,并且之后可以方便地找到这些变量,这套规则被称为作用域.作用域 ...
- 深入理解javascript作用域系列第一篇
前面的话 javascript拥有一套设计良好的规则来存储变量,并且之后可以方便地找到这些变量,这套规则被称为作用域.作用域貌似简单,实则复杂,由于作用域与this机制非常容易混淆,使得理解作用域的原 ...
- javascript动画系列第二篇——磁性吸附
× 目录 [1]范围限定 [2]拖拽范围 [3]磁性吸附 前面的话 上一篇,我们介绍了元素拖拽的实现.但在实际应用中,常常需要为拖拽的元素限定范围.而通过限定范围,再增加一些辅助的措施,就可以实现磁性 ...
- javascript面向对象系列第一篇——构造函数和原型对象
× 目录 [1]构造函数 [2]原型对象 [3]总结 前面的话 一般地,javascript使用构造函数和原型对象来进行面向对象编程,它们的表现与其他面向对象编程语言中的类相似又不同.本文将详细介绍如 ...
- JavaScript动画-模拟拖拽
模拟拖拽的原理: x1等于div.offsetLeft y1等于div.offsetTop x2等于ev.clientX(ev表示event事件) y2等于ev.clientY 当我们在方块上按下鼠标 ...
随机推荐
- ASP.NET Core 之 Identity 入门(一)
前言 在 ASP.NET Core 中,仍然沿用了 ASP.NET里面的 Identity 组件库,负责对用户的身份进行认证,总体来说的话,没有MVC 5 里面那么复杂,因为在MVC 5里面引入了OW ...
- 用php做注册审核
做注册审核就像前面讲的注册登录一样,也是要连接数据库 首先在数据库内要做这样一张表: 表名为users表 里面的列名分别为用户名,密码,姓名,性别,生日,账户的状态,照片 然后就可以写代码了,要注册的 ...
- Python高手之路【四】python函数装饰器
def outer(func): def inner(): print('hello') print('hello') print('hello') r = func() print('end') p ...
- excel 日期/数字格式不生效需要但双击才会生效的解决办法
原因: Excel2007设置过单元格格式后,并不能立即生效必须挨个双击单元格,才能生效.数据行很多.效率太低. 原因:主要是一些从网上拷贝过来的日期或数字excel默认为文本格式或特殊-中文数字格式 ...
- 【声明】前方不设坑位,不收费!~ 我为NET狂官方学习计划
发个通知,过段时间学习计划相关的东西就出来了,上次写了篇指引文章后有些好奇心颇重的人跟我说:“发现最近群知识库和技能库更新的频率有点大,这是要放大招的节奏啊!” 很多想学习却不知道如何规划的人想要一个 ...
- OpenCASCADE AIS Manipulator
OpenCASCADE AIS Manipulator eryar@163.com Abstract. OpenCASCADE7.1.0 introduces new built-in interac ...
- Android raw to bmp
Android raw 格式转 bmp 图像 raw 保存的为裸数据,转换时都需要把它转成RGBA 的方式来显示.其中: 8位RAW: 四位RGBA 来表示一位灰度; 24位RAW: 三位RGB相同, ...
- 套用JQuery EasyUI列表显示数据、分页、查询
声明,本博客从csdn搬到cnblogs博客园了,以前的csdn不再更新,朋友们可以到这儿来找我的文章,更多的文章会发表,谢谢关注! 有时候闲的无聊,看到extjs那么肥大,真想把自己的项目改了,最近 ...
- 分布式学习系列【dubbo入门实践】
分布式学习系列[dubbo入门实践] dubbo架构 组成部分:provider,consumer,registry,monitor: provider,consumer注册,订阅类似于消息队列的注册 ...
- jquery-treegrid树状表格的使用(.Net平台)
上一篇介绍了DataTable,这一篇在DT的基础之上再使用jquery的一款插件:treegrid,官网地址:http://maxazan.github.io/jquery-treegrid/ 一. ...