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 相关文章推荐
jquery DOM操作 基于命令改变页面
May 06 Javascript
jQuery控制网页打印指定区域的方法
Apr 07 Javascript
详解javascript传统方法实现异步校验
Jan 22 Javascript
javascript原生ajax写法分享
Apr 10 Javascript
浅谈JavaScript中的apply/call/bind和this的使用
Feb 26 Javascript
从零开始学习Node.js系列教程三:图片上传和显示方法示例
Apr 13 Javascript
深入理解Vue 的条件渲染和列表渲染
Sep 01 Javascript
利用Decorator如何控制Koa路由详解
Jun 26 Javascript
在微信小程序中渲染HTML内容的方法示例
Sep 28 Javascript
vue-cli中使用高德地图的方法示例
Mar 28 Javascript
javascript利用键盘控制小方块的移动
Apr 20 Javascript
JS实现拖拽元素时与另一元素碰撞检测
Aug 27 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
Terran剧情介绍
2020/03/14 星际争霸
php防止伪造的数据从URL提交方法
2014/06/27 PHP
PHP中的日期加减方法示例
2014/08/21 PHP
学习ExtJS Column布局
2009/10/08 Javascript
实现placeholder效果的方案汇总
2015/06/11 Javascript
使用JS实现图片展示瀑布流效果的实例代码
2016/09/12 Javascript
用js制作淘宝放大镜效果
2020/10/28 Javascript
微信小程序使用wxParse解析html的方法教程
2018/07/06 Javascript
vue语法自动转typescript(解放双手)
2019/09/18 Javascript
微信小程序登陆注册功能的实现代码
2019/12/10 Javascript
使用npm命令提示: 'npm' 不是内部或外部命令,也不是可运行的程序的处理方法
2020/05/14 Javascript
python实现简单的计时器功能函数
2015/03/14 Python
Python中输出ASCII大文字、艺术字、字符字小技巧
2015/04/28 Python
Python中用于检查英文字母大写的isupper()方法
2015/05/19 Python
详解Django中的权限和组以及消息
2015/07/23 Python
Django中celery执行任务结果的保存方法
2019/07/12 Python
详解在python操作数据库中游标的使用方法
2019/11/12 Python
pytorch的batch normalize使用详解
2020/01/15 Python
Django生成数据库及添加用户报错解决方案
2020/10/09 Python
AmazeUI图片轮播效果的示例代码
2020/08/20 HTML / CSS
德国高端单身人士交友网站:ElitePartner
2018/12/02 全球购物
美国室内和室外装饰花盆购物网站:ePlanters
2019/03/22 全球购物
Tessabit美国:集世界奢侈品和设计师品牌的意大利精品买手店
2020/06/29 全球购物
初中物理教学反思
2014/01/14 职场文书
校运会入场式解说词
2014/02/10 职场文书
乳制品整治工作方案
2014/05/29 职场文书
分公司负责人任命书
2014/06/04 职场文书
节约用水的口号
2014/06/20 职场文书
2014国庆节主题活动方案:快乐的国庆节
2014/09/16 职场文书
第二批党的群众路线教育实践活动总结报告
2014/10/30 职场文书
结婚保证书(三从四德)
2015/02/26 职场文书
事业单位聘任报告
2015/03/02 职场文书
vue实现无缝轮播效果(跑马灯)
2021/05/14 Vue.js
mysql配置SSL证书登录的实现
2021/09/04 MySQL
Python 居然可以在 Excel 中画画你知道吗
2022/02/15 Python
聊聊mysql都有哪几种分区方式
2022/04/13 MySQL