在做WEB UI设计的时候,拖动某个HTML元素已经成为一种不能忽视的用户界面模式,比较典型的应用例子就是Dialog,一个元素是怎么实现拖动的呢?其实原理非常简单,要想实现首先得了解几个基本知识。

Tips

绝对定位:只有把元素的position属性设置为absolute并且或者fixed才可以实现拖动,默认情况下元素会按文档流中的位置自行决定其出现在页面上的位置,是不能移动的,而绝对定位的元素可以使元素脱离文档流,相对于其定位的父元素或者屏幕定位,可以利用这点儿,通过改变元素与已定位父元素的位移来实现元素拖动。关于定位知识具体可以看看CSS布局 ——从display,position, float属性谈起

鼠标事件:当鼠标按下、移动、弹起的时候都会触发相应事件,当鼠标按下的时候同时会触发相应元素click事件,并且冒泡到document,上面提到改变元素与定位父容器位移可以在这些事件中实现。关于事件相关知识可以看看JavaScript与HTML交互——事件

要拖动的Dialog

写个简易的Dialog供拖动测试使用

<!DOCTYPE html>
<html>
<head>
<title>Test</title>
<style type="text/css" >
html,body
{
height:100%;
width:100%;
padding:0;
margin:0;
} .dialog
{
width:250px;
height:250px;
position:absolute;
background-color:#ccc;
-webkit-box-shadow:1px 1px 3px #292929;
-moz-box-shadow:1px 1px 3px #292929;
box-shadow:1px 1px 3px #292929;
margin:10px;
} .dialog-title
{
color:#fff;
background-color:#404040;
font-size:12pt;
font-weight:bold;
padding:4px 6px;
cursor:move;
} .dialog-content
{
padding:4px;
}
</style>
</head>
<body>
<div id="dlgTest" class="dialog">
<div class="dialog-title">Dialog</div>
<div class="dialog-content">
This is a draggable test.
</div>
</div>
</body>
</html>

看起来是酱紫的

拖动一下

为了简单,这里就不照顾浏览器兼容性问题了,先基于Chrome实现。上面的Dialog定位夫容器为document,鼠标event对象包含clientX和clientY两个属性,标识鼠标当前相对ViewPort(可视窗口)位置,可以在移动的时候改变Dialog的left和top属性值实现其移动。

var isDialogTitle=false;

            function down(e){
if(e.target.className.indexOf('dialog-title')!=-1){
isDialogTitle=true;
}
} function move(e){
var dialog=document.getElementById('dlgTest');
if(isDialogTitle){//只有点击Dialog Title的时候才能拖动
dialog.style.left=e.clientX+'px';
dialog.style.top=e.clientY+'px';
}
} function up(e){
isDialogTitle=false;
} document.addEventListener('mousedown',down);
document.addEventListener('mousemove',move);
document.addEventListener('mouseup',up);

这样拖动效果就实现了,为了确保只有鼠标点击Dialog Title的时候才拖动,当鼠标按下的时候要判断事件源,如果是Dialog Title区域的话,把isDialogTitle标记设为true,鼠标移动的时候首先要判断isDialogTitle,在鼠标弹起的时候将标记设为false。

一跳一跳的

亲自试过demo的同学肯定可以当开始移动的时候Dialog会跳一下,这是怎么个情况?仔细看看代码发现在移动初始,代码就把Dialog的left和top设为了鼠标当前位置,可是用户在拖动的时候不会刻意去点Dialog的左上角,这样就跳了,soga!改进一下

var draggingObj=null; //dragging Dialog
var diffX=0;
var diffY=0; function down(e){
if(e.target.className.indexOf('dialog-title')!=-1){
draggingObj=e.target.offsetParent;
diffX=event.clientX-draggingObj.offsetLeft;
diffY=event.clientY-draggingObj.offsetTop;
}
} function move(e){
var dialog=document.getElementById('dlgTest');
if(draggingObj){//只有点击Dialog Title的时候才能拖动
dialog.style.left=(e.clientX-diffX)+'px';
dialog.style.top=(e.clientY-diffY)+'px';
}
} function up(e){
draggingObj=null;
diffX=0;
diffY=0;
} document.addEventListener('mousedown',down);
document.addEventListener('mousemove',move);
document.addEventListener('mouseup',up);

好赤果果

经过改动后不再跳跃了,但是很暴露的感觉,最开始定义的三个变量都暴露在window下,而且这种写法相当的没有通用性,万一以后Dialog Title变了呢,凡是用过此方法的地方都得改一遍,万一Title内部还有子元素,点击其子元素的时候怎么办?既然如此,穿件衣服封装一下

var Dragging=function(validateHandler){ //参数为验证点击区域是否为可移动区域,如果是返回欲移动元素,负责返回null
var draggingObj=null; //dragging Dialog
var diffX=0;
var diffY=0; function mouseHandler(e){
switch(e.type){
case 'mousedown':
draggingObj=validateHandler(e);//验证是否为可点击移动区域
if(draggingObj!=null){
diffX=e.clientX-draggingObj.offsetLeft;
diffY=e.clientY-draggingObj.offsetTop;
}
break; case 'mousemove':
if(draggingObj){
draggingObj.style.left=(e.clientX-diffX)+'px';
draggingObj.style.top=(e.clientY-diffY)+'px';
}
break; case 'mouseup':
draggingObj =null;
diffX=0;
diffY=0;
break;
}
}; return {
enable:function(){
document.addEventListener('mousedown',mouseHandler);
document.addEventListener('mousemove',mouseHandler);
document.addEventListener('mouseup',mouseHandler);
},
disable:function(){
document.removeEventListener('mousedown',mouseHandler);
document.removeEventListener('mousemove',mouseHandler);
document.removeEventListener('mouseup',mouseHandler);
}
}
}

包装一下果真变好看多了,代码不难看懂,有几个注意点,Dragging函数的validateHandler参数并不是什么阿猫阿狗,正如注释所言为了解决刚才提到几个需求变更问题,validateHandler是一个自定义函数的句柄,这个函数用于识别点击元素是否触发移动,是的话需要返回欲移动元素,这样就可以灵活的触发移动并决定移动那个元素了(点击的和移动的不一定是一个),Dragging函数返回一个对象,对象中有两个方法,分别可以使元素可移动/禁止移动,看看怎么使用

function getDraggingDialog(e){
var target=e.target;
while(target && target.className.indexOf('dialog-title')==-1){
target=target.offsetParent;
}
if(target!=null){
return target.offsetParent;
}else{
return null;
}
} Dragging(getDraggingDialog).enable();

首先定义一个识别函数,然后作为参数调用Dragging函数,并调用返回值的enable方法,这样元素就可以拖动了。

源码

<!DOCTYPE html>
<html>
<head>
<title>Test</title>
<style type="text/css" >
html,body
{
height:100%;
width:100%;
padding:0;
margin:0;
} .dialog
{
width:250px;
height:250px;
position:absolute;
background-color:#ccc;
-webkit-box-shadow:1px 1px 3px #292929;
-moz-box-shadow:1px 1px 3px #292929;
box-shadow:1px 1px 3px #292929;
margin:10px;
} .dialog-title
{
color:#fff;
background-color:#404040;
font-size:12pt;
font-weight:bold;
padding:4px 6px;
cursor:move;
} .dialog-content
{
padding:4px;
}
</style>
</head>
<body>
<div id="dlgTest" class="dialog">
<div class="dialog-title">Dialog</div>
<div class="dialog-content">
This is a draggable test.
</div>
</div>
<script type="text/javascript">
var Dragging=function(validateHandler){ //参数为验证点击区域是否为可移动区域,如果是返回欲移动元素,负责返回null
var draggingObj=null; //dragging Dialog
var diffX=0;
var diffY=0; function mouseHandler(e){
switch(e.type){
case 'mousedown':
draggingObj=validateHandler(e);//验证是否为可点击移动区域
if(draggingObj!=null){
diffX=e.clientX-draggingObj.offsetLeft;
diffY=e.clientY-draggingObj.offsetTop;
}
break; case 'mousemove':
if(draggingObj){
draggingObj.style.left=(e.clientX-diffX)+'px';
draggingObj.style.top=(e.clientY-diffY)+'px';
}
break; case 'mouseup':
draggingObj =null;
diffX=0;
diffY=0;
break;
}
}; return {
enable:function(){
document.addEventListener('mousedown',mouseHandler);
document.addEventListener('mousemove',mouseHandler);
document.addEventListener('mouseup',mouseHandler);
},
disable:function(){
document.removeEventListener('mousedown',mouseHandler);
document.removeEventListener('mousemove',mouseHandler);
document.removeEventListener('mouseup',mouseHandler);
}
}
} function getDraggingDialog(e){
var target=e.target;
while(target && target.className.indexOf('dialog-title')==-1){
target=target.offsetParent;
}
if(target!=null){
return target.offsetParent;
}else{
return null;
}
} Dragging(getDraggingDialog).enable();
</script>
</body>
</html>

不足之处

这种拖动处理方式看起来不错了,但是还有几点儿遗憾

1. 前面提到的浏览器兼容性问题,这种写法在低版本IE浏览器上是不能运行的

2. 边界检查,细心的同学发现Dialog不但可以拖动了,还可以使页面出现滚动条无限拖动,大部分情况下我们希望Dialog在可视窗口、文档(固有滚动条内)或者固定区域内拖动,这种方式没有做到此限制

3. 拖动卡顿,在这个demo中不会出现此问题,文档结构简单拖动流畅,可视在庞大的页面中如果鼠标移动速度过快,Dialog会跟不上鼠标,出现卡顿,这时候如果鼠标在Dialog外面,mouseup事件不会生效,拖动就停不下来,只能把鼠标移回Dialog在mouseup

前两个问题好解决,拓展一下模块就可以,至于第三个现在还没想到比较好的解决办法,十一点了,明天再研究研究,然后一块儿发出来,晚安。

可拖动的DIV的更多相关文章

  1. jQuery实现鼠标拖动改变Div高度

    最近项目中需要在DashBoard页面做一个事件通知栏,该通知栏固定位于页面底部,鼠标拖动该DIV实现自动改变高度扩展内容显示区域. 以下是一个设计原型,基于jQuery实现,只实现了拖动效果,没有做 ...

  2. 可拖动的DIV续

    之前写过一篇可拖动的DIV讲如何实现可拖动的元素,最后提出了几点不足,这篇文章主要就是回答着三个问题 1. 浏览器兼容性 2. 边界检查 3. 拖动卡顿.失灵 先附上上次代码 <!DOCTYPE ...

  3. 鼠标拖动改变DIV等网页元素的大小的最佳实践

    1.初次实现 1.1 html代码 <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" la ...

  4. jquery 拖动改变div大小

    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/ ...

  5. 可拖动的div——demo

    可拖动的div——demo 我们经常会遇到这样的注册界面 我们以前经常可以遇到这种需要注册的网站,如上图: 上图有一个特点,即是上述注册框其实是一个div,同时可以拖动,以下做一个简单的实例,就可以实 ...

  6. 【转】弹出可拖动的DIV层提示窗口

    来源:www.divcss5.com <html> <head> <meta http-equiv="Content-Type" content=&q ...

  7. 创建一个可拖动的DIV

    var drag = function(){ var obj = document.getElementById("id"); var s = obj.style; var b = ...

  8. [置顶] 原创鼠标拖动实现DIV排序

    先上效果图: 对比传统的排序,这是一个很不错的尝试,希望对大家有启发. 大家可以参考我的上一篇博文:http://blog.csdn.net/littlebo01/article/details/12 ...

  9. 如何用JavaScript做一个可拖动的div层

    可拖动的层在Web设计中用处很多,比如在某些需要自定义风格布局的应用中,控件就需要拖动操作,下面介绍一个,希望可以满足你的需求,顺便学习一下可拖动的层是如何实现的. 下面是效果演示: 这个DIV可以移 ...

随机推荐

  1. [转]C#在创建完项目后如何重命名项目名称。

    今天写了个C#的小测试程序,一开始使用的默认命名WindowsFormsApplication2,写完后觉得名字不好看,于是想改个名字,但是试了一下,想完整的改名还挺复杂,不但要改解决方案名,项目名, ...

  2. Android手机编程初学遇到的问题及解决方法

    对高手来讲不值一提,可是对我这个初学来讲却是因为这些问题费了老长时间,有的不是编程问题,但不注意也会浪费不少宝贵时间!随时遇到随时更新... 引入第三方类库的问题,开始引用后没什么问题,但发现了该类库 ...

  3. 面向对象程序设计 第二次作业<1>

    Github链接:https://github.com/zora02/object-oriented/tree/master/1001.A%2BB%20Format%20(20) 一.解题 题目 解题 ...

  4. First class ,6 examples anlaysisi

    http://www.fgm.cc/learn/ First class ,6 examples anlaysisi <!DOCTYPE html> <!-- To change t ...

  5. javascript实现字符串的截取

    截取字符串方法有很多的,(不含根据传入参数截取成数组的split()方法)这里说的是子字符串,所以不说split()方法了. slice(),substr(),substring()一共三种方法,其中 ...

  6. Ubuntu上安装Minecraft服务器

    Minecraft由于其独特的魅力吸引了很多玩家.不过游戏的乐趣只有在和朋友一起玩的时候才最有意思,所以很早以前我就想建设自己的服务器.但由于专业知识欠缺,没有实现. 最近接触了Linux服务器,所以 ...

  7. MyBatis使用动态SQL标签的小陷阱

    现在MyBatis越来越受大家的喜爱了,它的优势大家都知道,我就不多说了,直接说重点. MyBatis中提供动态SQL功能,我们可以使用<if><when><where& ...

  8. Block循环引用问题研究

    自从苹果在objc中添加Block功能支持以后已经过了很久.目前网上对于Block的使用有很多介绍.不过对于Block的内存管理问题,则是众说纷纭.再加上objc开始使用ARC以后,对于Block的内 ...

  9. Eclipse JAVA项目的 目录结构 和 导入

    说明:本文所有测试以java工程为例: 1. Eclipse下的java工程目录 eclipse的基本工程目录叫做workspace,每个运行时的eclipse实例只能对应一个workspace,也就 ...

  10. Python和C扩展实现方法

    一.Python和C扩展 cPython是C编写的,python的扩展可以用C来写,也便于移植到C++. 编写的Python扩展,需要编译成一个.so的共享库. Python程序中. 官方文档:htt ...