本文介绍使用JavaScript实现DOM元素拖拽功能的方法,涵盖element.style.left、offsetLeft及addEventListener等核心知识点。同时针对拖拽过程中第二次操作失效的常见Bug,分析了mousedown、mousemove和mouseup事件的处理逻辑及潜在问题。
摘要:
DOM元素拖拽实现原理
近期在项目开发中需要实现图片预览功能,由于用户上传的图片尺寸往往超出当前视图区域,因此决定在固定宽高范围内实现图片的拖动查看功能。基于此需求,编写了一个兼容IE5+、Chrome、Firefox及Opera等主流浏览器的DOM元素拖拽插件。
实现该功能主要涉及以下JavaScript基础知识:
1. 获取元素位置属性 element.style.left
style.left 返回的是一个字符串类型的值,属于可读写变量,修改该值可直接影响元素的布局位置。
2. 获取元素偏移量 element.offsetLeft
offsetLeft 返回的是整数类型的数值,属于只读变量。修改此变量的值不会改变元素的实际布局,仅用于读取当前相对于父级定位元素的偏移距离。
3. 获取鼠标坐标 event.clientX
clientX 事件属性用于返回鼠标指针相对于浏览器页面(或客户区)的水平坐标值,在拖拽计算中用于确定鼠标移动的距离。
4. 操作元素类名 element.className
通过修改 className 可以动态切换元素的CSS类,从而改变样式表现。
上述三点属于较为常见的DOM操作知识,以下重点介绍事件监听机制:
4. 为对象添加事件监控
使用 element.addEventListener("event", "fun", "boolean") 方法注册事件监听器。
event 参数表示要监听的事件类型。
fun 参数表示事件触发后执行的回调函数。
项目开源地址:https://github.com/baixuexiyang/drag
拖拽实例与常见问题分析
实例演示:
var drag = new Drag("test", {
onStart: function(){
},
onMove: function(){
document.getElementById('position').innerHTML = '距离左边:' + drag.getPositions().left + ';距离顶部:' + drag.getPositions().top;
},
onStop: function(){
}
});
在实例测试中遇到一个棘手的问题,经过长时间排查仍未找到完美的解决方案。
为了便于测试,将相关代码整合在一个文件中展示。
文件名为 drag.html。
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<title>鼠标拖拽</title>
<style type="text/css">
<!--
#dragdiv{
background-color:#0A0909;
height:200px;
width:157px;
position:absolute;
}
.ondrag{
filter:alpha(opacity=200);
cursor:move;
}
.enddrag{
filter:alpha(opacity=100);
cursor:default;
}
#father{
width:600px;
height:700px;
background:#C63535;
}
-->
</style>
</head>
<body>
<div id="father">
<div id="dragdiv"></div>
</div>
<script language="JavaScript">
var dragdiv=document.getElementById("dragdiv");
var father=document.getElementById("father");
var offsetx=0;
var offsety=0;
var draging=false;
function beforeDrag(ev){
if (!draging) {
draging = true;
var l = dragdiv.offsetLeft;
var t = dragdiv.offsetTop;
offsetx = ev.clientX - l;
offsety = ev.clientY - t;
}
else {
dragdiv.className = "enddrag";
dragdiv.removeEventListener("mousemove",onDrag);
draging = false;
return;
}
}
function endDrag(){
draging=false;
dragdiv.className="enddrag";
}
function onDrag(ev){
if (!draging) {
dragdiv.className = "enddrag";
return;
}
else {
dragdiv.className = "ondrag";
dragdiv.style.left = (ev.clientX - offsetx) + "px";
dragdiv.style.top = (ev.clientY - offsety) + "px";
}
}
dragdiv.addEventListener("mousedown",beforeDrag,true);
dragdiv.addEventListener("mousemove",onDrag,false);
dragdiv.addEventListener("mouseup",endDrag,true);
</script>
</body>
</html>
代码逻辑相对简单,仔细阅读即可理解其基本结构。
第一次拖拽操作非常顺利,按住鼠标即可拖动。然而第二次拖拽时出现异常,必须点击页面其他非拖拽区域重新获取焦点后,才能正常进行第二次拖拽。
以下是涉及的具体鼠标事件说明:
"mousedown" 表示鼠标按键按下时触发,上述代码中触发了 beforeDrag 函数。
"mousemove" 表示鼠标移动时触发,上述代码中触发了 onDrag 函数。
"mouseup" 表示鼠标按键松开时触发,上述代码中触发了 endDrag 函数。
注:网上资料常提及 "onmousedown" 事件名,但笔者实际测试发现该写法无事件响应,使用 "mousedown" 才能正常工作。
针对上述第二次拖拽失效的问题,尝试了多种实验方案,包括调整 addEventListener 的调用顺序以及探讨第三个参数 true/false 所代表的事件捕获与冒泡阶段,但仍未找到完美的解决方法。
问题现象为:"mouseup" 事件在第二次监听时失效,必须通过鼠标点击其他区域重新获取焦点后,拖拽功能才能恢复正常。