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. Django实现cookie&session以及认证系统

    COOKIE&SESSION 知识储备 由于http协议无法保持状态,但实际情况,我们却又需要“保持状态”,因此cookie就是在这样一个场景下诞生. cookie的工作原理是:由服务器产生内 ...

  2. MQTT协议学习研究 & Mosquitto简要教程(安装和使用)

    若初次接触MQTT协议,可先理解以下概念: [MQTT协议特点]——相比于RESTful架构的物联网系统,MQTT协议借助消息推送功能,可以更好地实现远程控制. [MQTT协议角色]——在RESTfu ...

  3. 转载自(http://snailz.diandian.com/post/2012-10-24/40041265730)

    PHP 5.4.8 添加系统服务命令 之前没注意,PHP 5.4.8 的安装包有自带的系统服务注册文件的 打开编译安装包,换成你自己的路径 cd /mydata/soft/php-5.4.8/ cp ...

  4. 原生http模块与使用express框架对比

    node的http创建服务与利用Express框架有何不同 原生http模块与使用express框架对比: const http = require("http"); let se ...

  5. 《Convolutional Neural Networks for Sentence Classification》 文本分类

    文本分类任务中可以利用CNN来提取句子中类似 n-gram 的关键信息. TextCNN的详细过程原理图见下: keras 代码: def convs_block(data, convs=[3, 3, ...

  6. MYSQL的存储过程和函数简单写法

    存储过程 MySQL中,创建存储过程的基本形式如下: CREATE PROCEDURE sp_name ([proc_parameter[,...]]) [characteristic ...] ro ...

  7. Linux基础命令---fold

    fold 按照指定的宽度显示文件. 此命令的适用范围:RedHat.RHEL.Ubuntu.CentOS.SUSE.openSUSE.Fedora. 1.语法       fold [选项]  fil ...

  8. 远程登录 dos命令

    1.桌面连接命令 mstsc /v: 192.168.1.250 /console 2.若需要远程启动所有Internet服务,可以使用iisreset命令来实现. 进入“命令提示符”窗口.在提示符后 ...

  9. OpenCV中HSV颜色模型及颜色分量范围

    HSV颜色模型 HSV(Hue, Saturation, Value)是根据颜色的直观特性由A. R. Smith在1978年创建的一种颜色空间, 也称六角锥体模型(Hexcone Model)..这 ...

  10. SQL学习之Can't connect to MySQL server on localhost (10061)

    最近升级 了系统,开机后连接MySQL报错,Can't connect to MySQL server on localhost (10061): 估计是升级系统清除了以前的缓存设置,网上很多方法是命 ...