当然我们可以研究js库的源码, 也可以自己去发明轮子试试看, 其过程还是挺有趣的...下面我就来实现下页面元素的拖拽功能

现在就开始着手实现, 让我们从最顶层的方法讲起, 它用于初始化一个drag object, 方法的声明如下

function DragObject(cfg)

这里的cfg我们用一个对象来传入, 有点像Extjs里配置属性

var dragObj = new DragObject({

el: 'exampleB',

attachEl: 'exampleBHandle',

lowerBound: new Position(0, 0), //position代表一个点,有属性x,y下面会详细讲到

upperBound: new Position(500, 500),

startCallback: ..., // 开始拖拽时触发的回调 这里均省略了

moveCallback: ..., // 拖拽过程中触发的回调

endCallback: ..., // 拖拽结束触发的回调

attachLater: ... // 是否立刻启动拖拽事件的监听

});

配置参数中el可以是具体元素的id, 也可以直接是个dom对象 attachEl就是例子中的handle元素, 通过拖拽它来拖拽元素, lowerBound和upperBound是用于限定拖拽范围的, 都是Position对象, 关于这个对象的封装和作用我们下面会分析到,不急哈: ), 如果没有传入的话, 拖拽的范围就没有限制. startCallback, moveCallback, endCallback都是些回调函数, attachLater为true或者false. 如果不是很明白看了下面的分析, 我想你肯定很快会清楚的..

下面就来写Position, 代码如下:

function Position(x, y) {

this.X = x;

thix.Y = y;

}

Position.prototype = {

constructor: Position,

add : function(val) {

var newPos = new Position(this.X, this.Y);

if (val) {

newPos.X += val.X;

newPos.Y += val.Y;

}

return newPos;

},

subtract : function(val) {

var newPos = new Position(this.X, this.Y);

if (val) {

newPos.X -= val.X;

newPos.Y -= val.Y;

}

return newPos;

},

min : function(val) {

var newPos = new Position(this.X, this.Y);

if (val) {

newPos.X = this.X > val.X ? val.X : this.X;

newPos.Y = this.Y > val.Y ? val.Y : this.Y;

return newPos;

}

return newPos;

},

max : function(val) {

var newPos = new Position(this.X, this.Y);

if (val) {

newPos.X = this.X < val.X ? val.X : this.X;

newPos.Y = this.Y < val.Y ? val.Y : this.Y;

return newPos;

}

return newPos;

},

bound : function(lower, upper) {

var newPos = this.max(lower);

return newPos.min(upper);

},

check : function() {

var newPos = new Position(this.X, this.Y);

if (isNaN(newPos.X))

newPos.X = 0;

if (isNaN(newPos.Y))

newPos.Y = 0;

return newPos;

},

apply : function(el) {

if(typeof el == 'string')

el = document.getElementById(el);

if(!el) return;

el.style.left = this.X + 'px';

el.style.top = this.Y + 'px';

}

};

一个坐标点的简单封装, 它保存两个值: x, y坐标. 我们能够通过add和substract方法跟别的坐标点进行+运算和-运算, 返回一个计算过的新坐标点. min和max函数顾名思义用于跟其他坐标点进行比较,并返回其中较小和教大的值.bound方法返回一个在限定范围内的坐标点. check方法用于确保属性x, y的值是数字类型的, 否则会置0. 最后apply方法就是把属性x,y作用于元素style.left和top上. 接着我把剩下的大部分代码拿出来, 再一点一点看:

function DragObject(cfg) {

var el = cfg.el,

attachEl = cfg.attachEl,

lowerBound = cfg.lowerBound,

upperBound = cfg.upperBound,

startCallback = cfg.startCallback,

moveCallback = cfg.moveCallback,

endCallback = cfg.endCallback,

attachLater = cfg.attachLater;

if(typeof el == 'string')

el = document.getElementById(el);

if(!el) return;

if(lowerBound != undefined && upperBound != undefined) {

var tempPos = lowerBound.min(upperBound);

upperBound = lowerBound.max(upperBound);

lowerBound = tempPos;

}

var cursorStartPos,

elementStartPos,

dragging = false,

listening = false,

disposed = false;

function dragStart(eventObj) {

if(dragging || !listening || disposed) return;

dragging = true;

if(startCallback)

startCallback(eventObj, el);

cursorStartPos = absoluteCursorPosition(eventObj);

elementStartPos = new Position(parseInt(getStyle(el, 'left')), parseInt(getStyle(el, 'top')));

elementStartPos = elementStartPos.check();

hookEvent(document, 'mousemove', dragGo);

hookEvent(document, 'mouseup', dragStopHook);

return cancelEvent(eventObj);

}

function dragGo(e) {

if(!dragging || disposed) return;

var newPos = absoluteCursorPosition(e);

newPos = newPos.add(elementStartPos)

.subtract(cursorStartPos)

.bound(lowerBound, upperBound);

newPos.apply(el);

if(moveCallback)

moveCallback(newPos, el);

return cancelEvent(e);

}

function dragStopHook(e) {

dragStop();

return cancelEvent(e);

}

function dragStop() {

if(!dragging || disposed) return;

unhookEvent(document, 'mousemove', dragGo);

unhookEvent(document, 'mouseup', dragStopHook);

cursorStartPos = null;

elementStartPos = null;

if(endCallback)

endCallback(el);

dragging = false;

}

this.startListening = function() {

if(listening || disposed) return;

listening = true;

hookEvent(attachEl, 'mousedown', dragStart);

};

this.stopListening = function(stopCurrentDragging) {

if(!listening || disposed)

return;

unhookEvent(attachEl, 'mousedown', dragStart);

listening = false;

if(stopCurrentDragging && dragging)

dragStop();

};

this.dispose = function() {

if(disposed) return;

this.stopListening(true);

el = null;

attachEl = null;

lowerBound = null;

upperBound = null;

startCallback = null;

moveCallback = null;

endCallback = null;

disposed = true;

};

this.isDragging = function() {

return dragging;

};

this.isListening = function() {

return listening;

};

this.isDisposed = function() {

return disposed;

};

if(typeof attachEl == 'string')

attachEl = document.getElementById(attachEl);

// 如果没有配置, 或者没找到该Dom对象, 则用el

if(!attachEl) attachEl = el;

if(!attachLater)

this.startListening();

}

其中一些未给出方法, 在往下分析的过程中, 会一一给出....

我们先通过cfg来使el和attachEl指向实际的Dom对象, 如果attachEl没配置或者没找到对应元素则用el替代. 我们同时设置了一些在拖拽中要用到的变量. cursorStartPos用于保存鼠标按下开始拖拽时鼠标的坐标点. elementStartPos用于保存元素开始拖拽时的起始点. dragging, listening, disposed是一些状态变量. listening: 指drag object是否正在监听拖拽开始事件. dragging: 元素是否正在被拖拽. disposed: drag object被清理, 不能再被拖拽了.

在代码的最后, 我们看到如果attachLater不为true, 那么就调用startListening, 这是一个 public方法定义在drag object中, 让我们看下它的实现

this.startListening = function() {

if(listening || disposed) return;

listening = true;

hookEvent(attachEl, 'mousedown', dragStart);

};

前两行就是做个判断, 如果已经开始对拖拽事件进行监听或者清理过了, 就什么都不做直接return. 否则把listening状态设为true, 表示我们开始监听啦, 把dragStart函数关联到attachEl的mousedown事件上. 这里碰到个hookEvent函数, 我们来看看它的样子:

function hookEvent(el, eventName, callback) {

if(typeof el == 'string')

el = document.getElementById(el);

if(!el) return;

if(el.addEventListener)

el.addEventListener(eventName, callback, false);

else if (el.attachEvent)

el.attachEvent('on' + eventName, callback);

}

其实也没什么, 就是对元素事件的监听做了个跨浏览器的封装, 同样的unhookEvent方法如下

function unhookEvent(el, eventName, callback) {

if(typeof el == 'string')

el = document.getElementById(el);

if(!el) return;

if(el.removeEventListener)

el.removeEventListener(eventName, callback, false);

else if(el.detachEvent)

el.detachEvent('on' + eventName, callback);

}

接着我们来看看dragStart函数的实现, 它是drag object的一个私有函数

function dragStart(eventObj) {

if(dragging || !listening || disposed) return;

dragging = true;

if(startCallback)

startCallback(eventObj, el);

cursorStartPos = absoluteCursorPosition(eventObj);

elementStartPos = new Position(parseInt(getStyle(el, 'left')), parseInt(getStyle(el, 'top')));

elementStartPos = elementStartPos.check();

hookEvent(document, 'mousemove', dragGo);

hookEvent(document, 'mouseup', dragStopHook);

return cancelEvent(eventObj);

}

attachEl所指的dom对象捕获到mousedown事件后调用此函数. 首先我们先确定drag object在一个适合拖拽的状态, 如果拖拽正在进行, 或者没有在监听拖拽事件, 再或者已经处理完"后事"了, 那就什么都不做. 如果一切ok, 我们把 dragging状态设为true, 然后"开工了", 如果startCallback定义了, 那我们就调用下它, 以mousedown event和el为参数. 接着我们定位鼠标的绝对位置, 保存到cursorStartPos中. 然后拿到拖拽元素当前的top, left,封装成Position对象保存到elementStartPos中. 保险起见我们检查下elementStartPos中属性是否合法. 再看两个hookEvent的调用, 一个是mousemove事件, 表示正在dragging,调用dragGo函数. 一个是mouseup事件, 代表拖拽的结束, 调用dragStopHook函数.可能你会问,为什么事件绑定在document上, 而不是要拖拽的元素上,比如我们这里的el或者attachEl.因为考虑到直接将事件绑定到元素上,可能由于浏览器的一些延时会影响效果,所以直接把事件绑定到document上. 如果实在不是很理解, 或许影响也不大: P.... 看最后一句话中的cancelEvent(eventObj)

function cancelEvent(e) {

e = e ? e : window.event;

if(e.stopPropagation)

e.stopPropagation();

if(e.preventDefault)

e.preventDefault();

e.cancelBubble = true;

e.returnValue = false;

return false;

}

用于停止冒泡, 阻止默认事件, 可以理解为安全考虑....在dragStart中有些方法需要介绍下,先来 看看absoluteCursorPosition, 再看下getStyle

function absoluteCursorPosition(e) {

e = e ? e : window.event;

var x = e.clientX + (document.documentElement || document.body).scrollLeft;

var y = e.clientY + (document.documentElement || document.body).scrollTop;

return new Position(x, y);

}

此方法就只是用于获得鼠标在浏览器中的绝对位置, 把滚动条考虑进去就行了

function getStyle(el, property) {

if(typeof el == 'string')

el = document.getElementById(el);

if(!el || !property) return;

var value = el.style[property];

if(!value) {

if(document.defaultView && document.defaultView.getComputedStyle) {

var css = document.defaultView.getComputedStyle(el, null);

value = css ? css.getPropertyValue(property) : null;

} else if (el.currentStyle) {

value = el.currentStyle[property];

}

}

return value == 'auto' ? '' : value;

}

getStyle方法用于获取元素的css属性值, 这样不管你样式是写成内联形式还是定义在css中, 我们都能拿到正确的值, 当然我们这里只要获取元素的top, left属性即可..下面真正处理拖拽工作的方法dragGo

function dragGo(e) {

if(!dragging || disposed) return;

var newPos = absoluteCursorPosition(e);

newPos = newPos.add(elementStartPos)

.subtract(cursorStartPos)

.bound(lowerBound, upperBound);

newPos.apply(el);

if(moveCallback)

moveCallback(newPos, el);

return cancelEvent(e);

}

这个方法并不复杂, 像其他的方法一样, 我们先查看下状态如何, 如果没有在拖拽中或者已经清理了, 那么什么都不做. 如果一切顺利, 我们利用鼠标当前位置, 元素初始位置, 鼠标初始位置, 和限定范围(如果配置upperBound, lowerBound的话)来计算出一个结果点, 通过apply方法我们把计算的坐标赋给元素style.top和style.left, 让拖拽元素确定其位置. 如果配置了moveCallback, 那么就调用下, 最后来个cancelEvent...这里的新坐标运算,类似于jquery的操作, 因为Position对象的每个方法都返回了一个Position对像...dragStart里还有个方法dragStopHook

function dragStopHook(e) {

dragStop();

return cancelEvent(e);

}

function dragStop() {

if(!dragging || disposed) return;

unhookEvent(document, 'mousemove', dragGo);

unhookEvent(document, 'mouseup', dragStopHook);

cursorStartPos = null;

elementStartPos = null;

if(endCallback)

endCallback(el);

dragging = false;

}

关键看下dragStop方法, 同样先判断下状态, 一切ok的话, 我们移除事件的绑定mousemove和mouseup, 并把 cursorStartPos和elementStartPos的值释放掉, 一次拖拽结束啦..如果配置了endCallback那就调用下, 最后把dragging状态设置为false......最后给出会用到的public方法

this.stopListening = function(stopCurrentDragging) {

if(!listening || disposed)

return;

unhookEvent(attachEl, 'mousedown', dragStart);

listening = false;

if(stopCurrentDragging && dragging)

dragStop();

};

this.dispose = function() {

if(disposed) return;

this.stopListening(true);

el = null;

attachEl = null;

lowerBound = null;

upperBound = null;

startCallback = null;

moveCallback = null;

endCallback = null;

disposed = true;

};

this.isDragging = function() {

return dragging;

};

this.isListening = function() {

return listening;

};

this.isDisposed = function() {

return disposed;

};

stopListening移除监听拖拽的mousedown事件, 把监听状态listening设置为false, 这里有个参数stopCurrentDragging见名知意. dispose方法用于些处理工作, 如果你不想让drag object能被拖拽,那么调用一下dispose就可以了, 至于下面的三个小方法isDragging, isListening, isDisposed一看便知, 返回相关的状态. 最后给个源码的下拉链接 下载点我 欢迎园友留言, 交流!

本文原创发布php中文网,转载请注明出处,感谢您的尊重!

php 元素 拖拉,Draggable Elements 元素拖拽功能实现代码_javascript技巧相关推荐

  1. java swing 控件拖动_java swing中实现拖拽功能示例

    java实现拖拽示例 Swing中实现拖拽功能,代码很简单,都有注释,自己看,运行效果如下图: package com; import java.awt.*;import java.awt.datat ...

  2. draggable禁止拖动_通过 JS 实现简单的拖拽功能并且可以在特定元素上禁止拖拽...

    前言 关于讲解 JS 的拖拽功能的文章数不胜数,我确实没有必要大费周章再写一篇重复的文章来吸引眼球.本文的重点是讲解如何在某些特定的元素上禁止拖拽.这是我在编写插件时遇到的问题,其实很多插件的拖拽功能 ...

  3. css元素可拖动,使用css-transform实现更好的拖拽功能

    拖拽功能是目前网页上一种非常常见的功能,例如"登录弹窗"的拖拽.本文将使用transform来实现这一功能. 一.拖拽的用户行为分析与原理解析 二.代码实现 三.总结 本文所涉及的 ...

  4. vue 鼠标拖动画矩形_vue中拖动元素效果实现,以及拖拽`缩放后的元素`效果实现...

    START 番茄我又又又来写点啥了,最近被需求折磨的不要不要的,要做一个在线PPT做的网站.元素拖动算是其中一小部分的功能吧,但是还是出了很多的bug,这篇文章算是我对元素拖拽相关的记录吧. 仅以此文 ...

  5. Vue.Draggable 实现组件拖拽

    Vue.Draggable 实现组件拖拽 特性 支持触摸设备 支持拖拽和选择文本 支持智能滚动 支持不同列表之间的拖拽 不以jQuery为基础 和视图模型同步刷新 和vue2的国度动画兼容 支持撤销操 ...

  6. 如何玩转sortablejs-vuedraggable实现表单嵌套拖拽功能

    最近几天在研究有关vue实现拖拽的功能,不过跟一般的拖拽排序有点不同,这个需求可能出现多行多列嵌套的表单元素,数据也是递归形式的出现.我也是在vuedraggable的基础上扩展实现的,如何想了解更多 ...

  7. vuedraggable能实现自由拖拽功能吗?_基于 vue.js 仿禅道主页拖拽效果

    今天给大家分享一个超不错的Vue仿禅道首页拖拽布局VueDndKon. vue-dnd-kon 基于vuedraggable实现的仿禅道首页拖拽项目.支持模块上下及左右自由拖动布局. 主页分为左右两栏 ...

  8. jQuery UI 拖拽功能

    原文地址:http://www.cnblogs.com/holbrook/archive/2012/03/13/2394111.html JQuery UI是JQuery官方支持的WebUI 代码库, ...

  9. JQuery UI的拖拽功能

    JQuery UI是JQuery官方支持的WebUI 代码库,包含底层交互.动画.特效等API,并且封装了一些Web小部件(Widget).同时,JQuery UI继承了jquery的插件支持,有大量 ...

最新文章

  1. JavaScript初学者编程题(18)
  2. Python使用matplotlib可视化斜率图、对比同一数据对象前后(before、after)两个状态的差异(Slope Chart)
  3. 不懂编程可以自学python吗-为什么我建议你一定要学Python?
  4. 【集合论】Stirling 子集数 ( 斯特林子集数概念 | 放球模型 | Stirling 子集数递推公式 | 划分的二元关系 加细关系 )
  5. bzoj3224: Tyvj 1728 普通平衡树(打个splay暖暖手)
  6. 【数据结构与算法】【字符串匹配】Trie树
  7. 基于JAVA+Spring+MYSQL的物流运输管理系统
  8. 吴恩达深度学习作业L1W2:ValueError: cannot reshape array of size 12288 into shape (50,1)
  9. 数值溢出(arithmetic overflow)问题与解决方案
  10. 无主之地kill ajax,阿克斯顿 - 无主之地中文维基 - 灰机wiki
  11. mysql读写分离代码层实现_Mysql主从配置,实现读写分离
  12. [jzoj 4528] [GDOI2019模拟2019.3.26] 要换换名字 (最大权闭合子图)
  13. 管理感悟:如何处理不干活的主管
  14. 高通8996启动流程-2.总体启动流程
  15. 介绍两种提取视频语音变成文字的方式
  16. 宁德时代与蔚来签署全面战略合作协议;中国通信服务委任闫栋为公司总裁 | 美通企业日报...
  17. python把字典保存到文件_将Python字典保存到文件中,并定期更新它
  18. 【大数据】服务器硬盘基础知识
  19. OmniPeek11安装 (驱动安装)
  20. pvid与access的关系_交换机应用之端口模式(access、trunk和hybird)、是否标记(tag、untag)、端口缺省vlan(pvid、native id)...

热门文章

  1. java 微信获取用户地理位置_Java微信公众平台之获取地理位置
  2. ubuntu 安装java7_ubuntu 安装jdk7
  3. 效果:mask-image 哔哩哔哩弹幕不遮挡人物
  4. springboot高校大学校园论坛系统
  5. Brother打印机的安装
  6. 今年的 WWDC 2017 为我们带来了什么?
  7. 利用摄像机如何获取物体的深度信息
  8. 小程序毕业设计 基于微信医院预约挂号小程序毕业设计开题报告功能参考
  9. 枚举类型enum用法赋值
  10. 【异常】SSL 接收到一个超出最大准许长度的记录。