从零实现JS原生拖拽插件:原理、封装与优化指南
本文从零开始解析JavaScript拖拽插件的开发过程。详细阐述基于mousedown、mousemove和mouseup事件的拖拽原理,演示如何将基础逻辑封装为可复用的全局Drag方法。内容涵盖多元素管理、有效拖拽区域判定以及利用setTimeout提升onmousemove执行效率的性能优化技巧,并提供jQuery插件化思路。
本文深入解析JavaScript拖拽插件的开发流程,主要围绕六个核心环节展开详细论述。
JS拖拽插件原理与基础实现
核心内容涵盖:一、JS拖拽插件的运行原理;二、基于原理的基础效果实现;三、代码的抽象与优化处理;四、有效拖拽元素的扩展功能;五、性能优化措施及总结;六、jQuery插件化改造。JS拖拽是网页交互中的常见效果,本文将引导读者从零开始构建一个简单的JS插件。
一、JS拖拽插件的运行原理 典型的拖拽交互包含以下主要步骤:
1、用户鼠标点击需要拖拽的目标元素。
2、保持鼠标按键按下状态,并移动鼠标指针。
3、将元素移动至指定位置后,松开鼠标按键。
该过程涉及三个关键的DOM事件:onmousedown、onmousemove和onmouseup。因此,拖拽的基本实现逻辑如下:
1、点击目标元素触发onmousedown事件。
(1)将当前元素的可拖拽状态标记为true,标识该元素允许被拖拽。
(2)记录此时鼠标在页面上的坐标x和y。
(3)记录当前目标元素在页面上的坐标x和y。
2、移动鼠标触发onmousemove事件。
(1)判定元素是否具备拖拽属性,若满足条件则执行步骤2,否则直接终止流程
(2)若元素可拖拽,则更新元素的坐标位置。
元素的新x坐标 = 鼠标横向移动距离 + 元素原始x坐标 = 鼠标当前x坐标 - 鼠标初始x坐标 + 元素原始x坐标。
元素的新y坐标 = 鼠标纵向移动距离 + 元素原始y坐标 = 鼠标当前y坐标 - 鼠标初始y坐标 + 元素原始y坐标。
3、松开鼠标触发onmouseup事件。
(1)将可拖拽状态重置为false。
回到顶部 二、基于原理的基础效果实现 在编写基础代码前,需注意以下几点:
1、目标元素必须设置position属性为relative或absolute,否则无法通过改变left/top实现位移。
2、使用event.clientX和event.clientY属性获取鼠标相对于视口的坐标。
3、onmousemove事件应绑定在document对象上,而非拖拽元素本身,这样可以避免鼠标移动过快时脱离元素导致拖拽中断或延迟的问题。
具体实现代码如下:
var dragObj = document.getElementById("test");
dragObj.style.left = "px";
dragObj.style.top = "px";
var mouseX, mouseY, objX, objY;
var dragging = false;
dragObj.onmousedown = function (event) {
event = event || window.event;
dragging = true;
dragObj.style.position = "relative";
mouseX = event.clientX;
mouseY = event.clientY;
objX = parseInt(dragObj.style.left);
objY = parseInt(dragObj.style.top);
}
document.onmousemove = function (event) {
event = event || window.event;
if (dragging) {
dragObj.style.left = parseInt(event.clientX - mouseX + objX) + "px";
dragObj.style.top = parseInt(event.clientY - mouseY + objY) + "px";
}
}
document.onmouseup = function () {
dragging = false;
}
三、代码抽象与优化
代码抽象与全局封装
为了将上述代码封装为通用插件,需进行抽象处理,基本结构如下:
代码实现如下:
; (function (window, undefined) {
function Drag(ele) {}
window.Drag = Drag;
})(window, undefined);
通过自执行匿名函数包裹代码,内部定义Drag方法并将其暴露至全局作用域。使用时直接调用Drag,并传入需要拖拽的元素节点即可。
首先对常用方法进行简单封装:
; (function (window, undefined) {
var dom = {
//绑定事件
on: function (node, eventName, handler) {
if (node.addEventListener) {
node.addEventListener(eventName, handler);
}
else {
node.attachEvent("on" + eventName, handler);
}
},
//获取元素的样式
getStyle: function (node, styleName) {
var realStyle = null;
if (window.getComputedStyle) {
realStyle = window.getComputedStyle(node, null)[styleName];
}
else if (node.currentStyle) {
realStyle = node.currentStyle[styleName];
}
return realStyle;
},
//获取设置元素的样式
setCss: function (node, css) {
for (var key in css) {
node.style[key] = css[key];
}
}
};
window.Drag = Drag;
})(window, undefined);
在拖拽操作中,主要涉及两个对象:被拖拽的元素对象和鼠标对象。我们定义如下两个对象及其对应操作方法:
首先是拖拽元素对象,它包含一个DOM元素节点以及拖拽前的x和y坐标:
function DragElement(node) {
this.node = node;//被拖拽的元素节点
this.x = ;//拖拽之前的x坐标
this.y = ;//拖拽之前的y坐标
}
DragElement.prototype = {
constructor: DragElement,
init: function () {
this.setEleCss({
"left": dom.getStyle(node, "left"),
"top": dom.getStyle(node, "top")
})
.setXY(node.style.left, node.style.top);
},
//设置当前的坐标
setXY: function (x, y) {
this.x = parseInt(x) || ;
this.y = parseInt(y) || ;
return this;
},
//设置元素节点的样式
setEleCss: function (css) {
dom.setCss(this.node, css);
return this;
}
}
另一个对象是鼠标对象,主要包含x坐标和y坐标:
function Mouse() {
this.x = ;
this.y = ;
}
Mouse.prototype.setXY = function (x, y) {
this.x = parseInt(x);
this.y = parseInt(y);
}
以上是在拖拽操作中定义的两个核心对象。
若页面中存在多个可拖拽元素,需注意以下事项:
1、每个元素对应一个独立的拖拽对象实例。
2、同一时刻页面中只能有一个元素处于拖拽状态。
为此,我们定义一个全局配置对象来保存相关状态:
代码实现如下:
var draggableConfig = {
zIndex: ,
draggingObj: null,
mouse: new Mouse()
};
该对象包含三个属性:
(1)zIndex:用于赋值给拖拽对象的zIndex属性。当多个拖拽对象重叠时,通过设置zIndex确保当前拖拽对象显示在最顶层。
(2)draggingObj:用于保存当前正在拖拽的对象。此处移除了之前用于判断是否可拖拽的独立变量,改为通过draggingObj是否存在来判断当前是否允许拖拽,并获取相应的拖拽对象。
(3)mouse:唯一的鼠标对象,用于保存当前鼠标的坐标等信息。
最后绑定onmousedown、onmouseover和onmouseout事件,整合后的代码如下:
; (function (window, undefined) {
var dom = {
//绑定事件
on: function (node, eventName, handler) {
if (node.addEventListener) {
node.addEventListener(eventName, handler);
}
else {
node.attachEvent("on" + eventName, handler);
}
},
//获取元素的样式
getStyle: function (node, styleName) {
var realStyle = null;
if (window.getComputedStyle) {
realStyle = window.getComputedStyle(node, null)[styleName];
}
else if (node.currentStyle) {
realStyle = node.currentStyle[styleName];
}
return realStyle;
},
//获取设置元素的样式
setCss: function (node, css) {
for (var key in css) {
node.style[key] = css[key];
}
}
};
//#region 拖拽元素类
function DragElement(node) {
this.node = node;
this.x = ;
this.y = ;
}
DragElement.prototype = {
constructor: DragElement,
init: function () {
this.setEleCss({
"left": dom.getStyle(node, "left"),
"top": dom.getStyle(node, "top")
})
.setXY(node.style.left, node.style.top);
},
setXY: function (x, y) {
this.x = parseInt(x) || ;
this.y = parseInt(y) || ;
return this;
},
setEleCss: function (css) {
dom.setCss(this.node, css);
return this;
}
}
//#endregion
//#region 鼠标元素
function Mouse() {
this.x = ;
this.y = ;
}
Mouse.prototype.setXY = function (x, y) {
this.x = parseInt(x);
this.y = parseInt(y);
}
//#endregion
//拖拽配置
var draggableConfig = {
zIndex: ,
draggingObj: null,
mouse: new Mouse()
};
function Drag(ele) {
this.ele = ele;
function mouseDown(event) {
var ele = event.target || event.srcElement;
draggableConfig.mouse.setXY(event.clientX, event.clientY);
draggableConfig.draggingObj = new DragElement(ele);
draggableConfig.draggingObj
.setXY(ele.style.left, ele.style.top)
.setEleCss({
"zIndex": draggableConfig.zIndex++,
"position": "relative"
});
}
ele.onselectstart = function () {
//防止拖拽对象内的文字被选中
return false;
}
dom.on(ele, "mousedown", mouseDown);
}
dom.on(document, "mousemove", function (event) {
if (draggableConfig.draggingObj) {
var mouse = draggableConfig.mouse,
draggingObj = draggableConfig.draggingObj;
draggingObj.setEleCss({
"left": parseInt(event.clientX - mouse.x + draggingObj.x) + "px",
"top": parseInt(event.clientY - mouse.y + draggingObj.y) + "px"
});
}
})
dom.on(document, "mouseup", function (event) {
draggableConfig.draggingObj = null;
})
window.Drag = Drag;
})(window, undefined);
调用方法示例:Drag(document.getElementById("obj"));
需注意的一点是,为防止在拖拽过程中选中元素内的文字,可通过onselectstart事件处理程序返回false来解决此问题。
有效拖拽区域扩展
四、扩展:有效拖拽元素 常见的拖拽效果可能如下所示:

例如弹窗的顶部区域允许拖拽,而内容区域不可拖拽,如何实现此类效果?
首先优化拖拽元素对象,增加一个目标元素target属性,表示实际被拖拽的对象。在上述登录框示例中,target即为整个登录窗口。
实际记录坐标并进行位移操作的拖拽元素即为该目标元素,但它并非整个区域都支持拖拽。我们在HTML结构中为有效拖拽区域添加class="draggable"类名:
代码实现如下:
<div id="obj" class="dialog" style="position:relative;left:px">
<div class="header draggable">
拖拽的有效元素
</div>
<div class="content">
拖拽对象
</div>
</div>
随后修改Drag方法如下:
function drag(ele) {
var dragNode = (ele.querySelector(".draggable") || ele);
dom.on(dragNode, "mousedown", function (event) {
var dragElement = draggableConfig.dragElement = new DragElement(ele);
draggableConfig.mouse.setXY(event.clientX, event.clientY);
draggableConfig.dragElement
.setXY(dragElement.target.style.left, dragElement.target.style.top)
.setTargetCss({
"zIndex": draggableConfig.zIndex++,
"position": "relative"
});
}).on(dragNode, "mouseover", function () {
dom.setCss(this, draggableStyle.dragging);
}).on(dragNode, "mouseout", function () {
dom.setCss(this, draggableStyle.defaults);
});
}
主要修改点在于绑定mousedown事件的节点变为包含draggable类的有效元素。若该元素不包含draggable类,则整个元素均为有效拖拽区域。
性能优化与总结
五、性能优化和总结 由于onmousemove事件会频繁触发,可能引发性能问题。我们可以通过setTimeout来延迟绑定onmousemove事件,改进后的move函数如下:
function move(event) {
if (draggableConfig.dragElement) {
var mouse = draggableConfig.mouse,
dragElement = draggableConfig.dragElement;
dragElement.setTargetCss({
"left": parseInt(event.clientX - mouse.x + dragElement.x) + "px",
"top": parseInt(event.clientY - mouse.y + dragElement.y) + "px"
});
dom.off(document, "mousemove", move);
setTimeout(function () {
dom.on(document, "mousemove", move);
}, );
}
}
总结:
整个拖拽插件的实现相对简单,但需注意以下几点:
1、实现思路:元素拖拽位置的改变等于鼠标移动的距离,关键在于准确获取鼠标的变动量和元素的原始坐标。
2、通过setTimeout延迟加载onmousemove事件以提升性能。
jQuery插件化改造
六、jQuery插件化 将其简单封装为jQuery插件,主要涉及将相关DOM操作方法替换为jQuery方法进行操作。
; (function ($, window, undefined) {
//#region 拖拽元素类
function DragElement(node) {
this.target = node;
node.onselectstart = function () {
//防止拖拽对象内的文字被选中
return false;
}
}
DragElement.prototype = {
constructor: DragElement,
setXY: function (x, y) {
this.x = parseInt(x) || ;
this.y = parseInt(y) || ;
return this;
},
setTargetCss: function (css) {
$(this.target).css(css);
return this;
}
}
//#endregion
//#region 鼠标元素
function Mouse() {
this.x = ;
this.y = ;
}
Mouse.prototype.setXY = function (x, y) {
this.x = parseInt(x);
this.y = parseInt(y);
}
//#endregion
//拖拽配置
var draggableConfig = {
zIndex: ,
dragElement: null,
mouse: new Mouse()
};
var draggableStyle = {
dragging: {
cursor: "move"
},
defaults: {
cursor: "default"
}
}
var $document = $(document);
function drag($ele) {
var $dragNode = $ele.find(".draggable");
$dragNode = $dragNode.length > ? $dragNode : $ele;
$dragNode.on({
"mousedown": function (event) {
var dragElement = draggableConfig.dragElement = new DragElement($ele.get());
draggableConfig.mouse.setXY(event.clientX, event.clientY);
draggableConfig.dragElement
.setXY(dragElement.target.style.left, dragElement.target.style.top)
.setTargetCss({
"zIndex": draggableConfig.zIndex++,
"position": "relative"
});
},
"mouseover": function () {
$(this).css(draggableStyle.dragging);
},
"mouseout": function () {
$(this).css(draggableStyle.defaults);
}
})
}
function move(event) {
if (draggableConfig.dragElement) {
var mouse = draggableConfig.mouse,
dragElement = draggableConfig.dragElement;
dragElement.setTargetCss({
"left": parseInt(event.clientX - mouse.x + dragElement.x) + "px",
"top": parseInt(event.clientY - mouse.y + dragElement.y) + "px"
});
$document.off("mousemove", move);
setTimeout(function () {
$document.on("mousemove", move);
}, );
}
}
$document.on({
"mousemove": move,
"mouseup": function () {
draggableConfig.dragElement = null;
}
});
$.fn.drag = function (options) {
drag(this);
}
})(jQuery, window, undefined)
以上就是本文对JS拖拽插件实现步骤的详细介绍,希望对大家有所帮助。
为什么JS调用reload后页面没有立即更新
2026-11-05 13:38:29
crypto-js 加密结果与 Java/Python 后端不一致怎么解决
2026-11-05 12:09:48
js文件缓存导致代码不更新怎么强制刷新
2026-11-04 22:30:24
JS逆向调试技巧:编写Hook脚本监控加密函数输入输出
2026-11-04 10:42:46
Babylon.js 性能优化技巧:实例化网格与遮挡剔除
2026-11-03 23:43:22
