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、元素想要被拖动,它的postion属性一定要是relative或absolute

  2、通过event.clientX和event.clientY获取鼠标的坐标

  3、onmousemove是绑定在document元素上而不是拖拽元素本身,这样能解决快速拖动造成的延迟或停止移动的问题

代码如下:

 var dragObj = document.getElementById("test");
dragObj.style.left = "0px";
dragObj.style.top = "0px"; 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);

在一个拖拽操作中,存在着两个对象:被拖拽的对象和鼠标对象,我们定义了下面的两个对象以及它们对应的操作:

首先的拖拽对象,它包含一个元素节点和拖拽之前的坐标x和y:

             function DragElement(node) {
this.node = node;//被拖拽的元素节点
this.x = 0;//拖拽之前的x坐标
this.y = 0;//拖拽之前的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) || 0;
this.y = parseInt(y) || 0;
return this;
},
//设置元素节点的样式
setEleCss: function (css) {
dom.setCss(this.node, css);
return this;
}
}

还有一个对象是鼠标,它主要包含x坐标和y坐标:

             function Mouse() {
this.x = 0;
this.y = 0;
}
Mouse.prototype.setXY = function (x, y) {
this.x = parseInt(x);
this.y = parseInt(y);
}

这是在拖拽操作中定义的两个对象。

如果一个页面可以有多个拖拽元素,那应该注意什么:

1、每个元素对应一个拖拽对象实例

2、每个页面只能有一个正在拖拽中的元素

为此,我们定义了唯一一个对象用来保存相关的配置:

             var draggableConfig = {
zIndex: 1,
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 = 0;
this.y = 0;
}
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) || 0;
this.y = parseInt(y) || 0;
return this;
},
setEleCss: function (css) {
dom.setCss(this.node, css);
return this;
}
}
//#endregion //#region 鼠标元素
function Mouse() {
this.x = 0;
this.y = 0;
}
Mouse.prototype.setXY = function (x, y) {
this.x = parseInt(x);
this.y = parseInt(y);
}
//#endregion //拖拽配置
var draggableConfig = {
zIndex: 1,
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事件处理程序return false来处理这个问题。

四、扩展:有效的拖拽元素

我们常见的一些拖拽效果很有可能是这样的:

弹框的顶部是可以进行拖拽操作的,内容区域是不可拖拽的,怎么实现这样的效果呢:

首先优化拖拽元素对象如下,增加一个目标元素target,表示被拖拽对象,在上图的登录框中,就是整个登录窗口。

被记录和设置坐标的拖拽元素就是这个目标元素,但是它并不是整个部分都是拖拽的有效部分。我们在html结构中为拖拽的有效区域添加类draggable表示有效拖拽区域:

     <div id="obj1" class="dialog" style="position:relative;left:50px">
<div class="header draggable">
拖拽的有效元素
</div>
<div class="content">
拖拽对象1
</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在一直调用,会造成一些性能问题,我们可以通过setTimout来延迟绑定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);
}, 25);
}
}

总结:

整个拖拽插件的实现其实很简单,主要是要注意几点

  1、实现思路:元素拖拽位置的改变就等于鼠标改变的距离,关键在于获取鼠标的变动和元素原本的坐标

2、通过setTimeout来延迟加载onmousemove事件来提供性能

六、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) || 0;
this.y = parseInt(y) || 0;
return this;
},
setTargetCss: function (css) {
$(this.target).css(css);
return this;
}
}
//#endregion //#region 鼠标元素
function Mouse() {
this.x = 0;
this.y = 0;
}
Mouse.prototype.setXY = function (x, y) {
this.x = parseInt(x);
this.y = parseInt(y);
}
//#endregion //拖拽配置
var draggableConfig = {
zIndex: 1,
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 > 0 ? $dragNode : $ele; $dragNode.on({
"mousedown": function (event) {
var dragElement = draggableConfig.dragElement = new DragElement($ele.get(0)); 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);
}, 25);
}
} $document.on({
"mousemove": move,
"mouseup": function () {
draggableConfig.dragElement = null;
}
}); $.fn.drag = function (options) {
drag(this);
} })(jQuery, window, undefined)

点击下载DEMO

一步一步实现JS拖拽插件的更多相关文章

  1. 好用的JS拖拽插件

    下载artDialog插件的时候发现它把拖拽单独封装成了一个方法,挺好用的,使用方法如下... 第一种拖拽方式-点击容器指定区域进行拖拽 $('.ui-dialog').on(DragEvent.ty ...

  2. 浅谈js拖拽

    本文来自网易云社区 作者:刘凌阳 前言 本文依据半年前本人的分享<浅谈js拖拽>撰写,算是一篇迟到的文章. 基本思路 虽然现在关于拖拽的组件库到处都是,HTML5也把拖放纳入了标准.但考虑 ...

  3. 原生js拖拽功能制作滑动条实例教程

    拖拽属于前端常见的功能,很多效果都会用到js的拖拽功能.滑动条的核心功能也就是使用js拖拽滑块来修改位置.一个完整的滑动条包括 滑动条.滑动痕迹.滑块.文本 等元素,先把html代码写出来,如下所示: ...

  4. 再谈React.js实现原生js拖拽效果

    前几天写的那个拖拽,自己留下的疑问...这次在热心博友的提示下又修正了一些小小的bug,也加了拖拽的边缘检测部分...就再聊聊拖拽吧 一.不要直接操作dom元素 react中使用了虚拟dom的概念,目 ...

  5. React.js实现原生js拖拽效果及思考

    一.起因&思路 不知不觉,已经好几天没写博客了...近来除了研究React,还做了公司官网... 一直想写一个原生js拖拽效果,又加上近来学react学得比较嗨.所以就用react来实现这个拖 ...

  6. js拖拽效果

    <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...

  7. 关于 JS 拖拽功能的冲突问题及解决方法

    前言 我在之前写过关于 JS 拖拽的文章,实现方式和网上能搜到的方法大致相同,别无二致,但是在一次偶然的测试中发现,这种绑定事件的方式可能会和其它的拖拽事件产生冲突,由此产生了对于事件绑定的思考.本文 ...

  8. js拖拽分析

    js拖拽分析 思路 1.三个鼠标事件,mousedown,mousemove,mouseup 2.可移动性absolute 3.边界限制 得到鼠标点击处和div边界的距离,然后得出top 和 left ...

  9. JS拖拽div(移动)

    <!doctype html><html><head> <meta charset="utf-8"> <title>JS ...

随机推荐

  1. #C++初学记录(sort函数)

    sort函数 前言:当进行贪心算法的学习时,需要用到sort函数,因为初学c++汇编语言,sort的具体用法没有深入学习,所以这里进行sort学习记录并只有基础用法并借用贪心算法题目的代码. 百度百科 ...

  2. cocos2dx-环境搭建和创建项目(mac用)

    学习一门语言最快的方法就是制作一个简单的项目. 而要制作一个项目必须先搭好环境,即使花上2小时去熟悉环境创建也不算浪费.由于之后的项目会用到cocos2d-x 3.0,这里以3.0为例子(不同的版本写 ...

  3. Oracle性能优化之HINT的用法

    1. /*+ALL_ROWS*/ 表明对语句块选择基于开销的优化方法,并获得最佳吞吐量,使资源消耗最小化. 例如: SELECT /*+ALL+_ROWS*/ EMP_NO,EMP_NAM,DAT_I ...

  4. 最近整理出了有关大数据,微服务,分布式,Java,Python,Web前端,产品运营,交互等1.7G的学习资料,有视频教程,源码,课件,工具,面试题等等。这里将珍藏多年的资源免费分享给各位小伙伴们

    大数据,微服务,分布式,Java,Python,Web前端,产品运营,交互 领取方式在篇尾!!! 基础篇.互联网架构,高级程序员必备视频,Linux系统.JVM.大型分布式电商项目实战视频...... ...

  5. linux常用命令:at 命令

    在windows系统中,windows提供了计划任务这一功能,在控制面板 -> 性能与维护 -> 任务计划, 它的功能就是安排自动运行的任务. 通过'添加任务计划'的一步步引导,则可建立一 ...

  6. linux常用命令:ln 命令

    ln是linux中又一个非常重要命令,它的功能是为某一个文件在另外一个位置建立一个同步的链接.当我们需要在不同的目录,用到相同的文件时,我们不需要在每一个需要的目录下都放一个必须相同的文件,我们只要在 ...

  7. SNMP学习笔记之Linux服务器SNMP常用OID

    收集整理一些Linux下snmp常用的OID,用做服务器监控很不错. 应用示例 查看服务器1分钟平均负载: snmpwalk -v1 -c public 127.0.0.1 .1.3.6.1.4.1. ...

  8. Tomcat分windows版和linux版

    Tomcat分windows版和linux版 官方下载链接:http://archive.apache.org/dist/tomcat/tomcat-6/v6.0.37/bin/ windows下载e ...

  9. Github使用教程(一)------ 初识Github

    上一节我们解决了Github网站响应慢,加载不完全的情况,接下来我们就要正式开始使用Github了. :好,那我先安装Git,稍后就上传项目. :......你Github网站都看懂了? :还需要看懂 ...

  10. 20145321 《网络对抗技术》 Web安全基础实践

    20145321<网络对抗技术> Web安全基础实践 基础问题回答 1.SQL注入攻击原理,如何防御? SQL注入就是通过把SQL命令插入到“Web表单递交”或“输入域名”或“页面请求”的 ...