JS拖拽插件实现步骤


Posted in Javascript onAugust 03, 2015

这篇文章详细介绍了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、元素想要被拖动,它的postion属性一定要是relative或absolute

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);

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

首先的拖拽对象,它包含一个元素节点和拖拽之前的坐标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事件处理程序return false来处理这个问题。

四、扩展:有效的拖拽元素
我们常见的一些拖拽效果很有可能是这样的:

JS拖拽插件实现步骤

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

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

被记录和设置坐标的拖拽元素就是这个目标元素,但是它并不是整个部分都是拖拽的有效部分。我们在html结构中为拖拽的有效区域添加类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在一直调用,会造成一些性能问题,我们可以通过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);
    }, );
   }
  }

总结:

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

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) || ;
    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拖拽插件实现步骤的详细介绍,希望对大家有所帮助。

Javascript 相关文章推荐
js导出格式化的excel 实例方法
Jul 17 Javascript
深入理解javascript中return的作用
Dec 30 Javascript
jquery的trigger和triggerHandler的区别示例介绍
Apr 20 Javascript
使用Raygun对Node.js应用进行错误处理的方法
Jun 23 Javascript
jquery实现倒计时效果
Dec 14 Javascript
vue2.0开发实践总结之入门篇
Dec 06 Javascript
Javascript实现倒计时(防页面刷新)实例
Dec 13 Javascript
完美解决jQuery的hover事件在IE中不停闪动的问题
Feb 10 Javascript
基于Vue制作组织架构树组件
Dec 06 Javascript
Angular路由ui-router配置详解
Aug 01 Javascript
vue的全局变量和全局拦截请求器的示例代码
Sep 13 Javascript
JavaScript数据结构与算法之二叉树实现查找最小值、最大值、给定值算法示例
Mar 01 Javascript
javascript实现的淘宝旅行通用日历组件用法实例
Aug 03 #Javascript
javascript简单实现类似QQ头像弹出效果的方法
Aug 03 #Javascript
比例尺、缩略图、平移缩放之百度地图添加控件方法
Aug 03 #Javascript
浅谈JavaScript中setInterval和setTimeout的使用问题
Aug 01 #Javascript
JavaScript图片轮播代码分享
Jul 31 #Javascript
简单实现异步编程promise模式
Jul 31 #Javascript
JavaScript数据类型判定的总结笔记
Jul 31 #Javascript
You might like
PHP中的类型提示(type hinting)功能介绍
2015/07/01 PHP
PHP实现页面静态化的超简单方法
2016/09/06 PHP
php7 新增功能实例总结
2020/05/25 PHP
pjblog中的UBBCode.js
2007/04/25 Javascript
精通JavaScript 纠正 cleanWhitespace函数
2010/03/11 Javascript
js中parseFloat(参数1,参数2)定义和用法及注意事项
2013/01/27 Javascript
jquery时间下拉框小例子
2013/04/15 Javascript
jQuery实现在最后一个元素之前插入新元素的方法
2015/07/18 Javascript
javascript的变量、传值、传址、参数之间关系
2015/07/26 Javascript
使用Browserify配合jQuery进行编程的超级指南
2015/07/28 Javascript
Node.js与Sails ~项目结构与Mvc实现及日志机制
2015/10/14 Javascript
jQuery中数据缓存$.data的用法及源码完全解析
2016/04/29 Javascript
Knockout结合Bootstrap创建动态UI实现产品列表管理
2016/09/14 Javascript
在element-ui的el-tree组件中用render函数生成el-button的实例代码
2018/11/05 Javascript
对 Vue-Router 进行单元测试的方法
2018/11/05 Javascript
微信小程序实现授权登录
2019/05/15 Javascript
LayUi使用switch开关,动态的去控制它是否被启用的方法
2019/09/21 Javascript
解决vue-cli@3.xx安装不成功的问题及搭建ts-vue项目
2020/02/09 Javascript
vue 使用插槽分发内容操作示例【单个插槽、具名插槽、作用域插槽】
2020/03/06 Javascript
微信小程序基于ColorUI构建皮皮虾短视频去水印组件
2020/11/04 Javascript
vue 插槽简介及使用示例
2020/11/19 Vue.js
Python实现监控程序执行时间并将其写入日志的方法
2015/06/30 Python
Python打印斐波拉契数列实例
2015/07/07 Python
python中 logging的使用详解
2017/10/25 Python
django 发送邮件和缓存的实现代码
2018/07/18 Python
python获取当前文件路径以及父文件路径的方法
2019/07/10 Python
对django 模型 unique together的示例讲解
2019/08/06 Python
通过 Django Pagination 实现简单分页功能
2019/11/11 Python
Pytorch提取模型特征向量保存至csv的例子
2020/01/03 Python
Django form表单与请求的生命周期步骤详解
2020/06/07 Python
pytho matplotlib工具栏源码探析一之禁用工具栏、默认工具栏和工具栏管理器三种模式的差异
2021/02/25 Python
找工作最新求职信
2013/12/22 职场文书
党员承诺书怎么写
2014/05/20 职场文书
高考百日冲刺决心书
2015/09/23 职场文书
python 实现德洛内三角剖分的操作
2021/04/22 Python