2016年1月24日
javascript实现模拟鼠标拖动元素(上)
模拟鼠标拖动网页元素是一种比较常见的需求,本文介绍用javascript来实现该功能。本文主要参考了原生javascript实现拖动元素,在此对原作者致敬,原文是需要实际鼠标操作后来拖动元素到鼠标移动结束的位置,本文则侧重于自动触发鼠标的mousedown,mousemove来实现让网页元素自动移动到指定位置。
代码主要思路:
1. 通过dispatchEvent触发鼠标事件”mousedown”,”mousemove”,”mouseup”;
2. 通过addHandler捕捉鼠标事件“mousedown”,”mousemove”,”mouseup”;
3. 对指定网页元素触发”mousedown”;
4. 在”mousemove”事件中将指定元素移动至指定位置;
5. 当触发”mouseup”时间时,终止拖动。
代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 |
<!DOCTYPE html> <html> <head> <title>draggable div</title> <style type="text/css"> body{ margin: 0; padding: 0; background-color: #fff; } #drag_div{ width: 150px; height: 150px; padding: 10px; margin: 10px; background-color: #66dd33; cursor: move; } </style> </head> </html> <body> <div id="drag_div"></div> </body> <script type="text/javascript"> function Drag () { this.initialize.apply(this, arguments); } Drag.prototype = { // 初始化 initialize : function (element, options) { this.element = this.$(element); // 被拖动的对象 this._x = this._y = 0; // 鼠标在元素中的位置 this._moveDrag = this.bind(this, this.moveDrag); this._stopDrag = this.bind(this, this.stopDrag); // 设置参数 this.setOptions(options); // 设置鼠标去“拖”的“柄”对象(注意与被拖动的对象区别) this.handle = this.$(this.options.handle); // 设置回调函数 this.onStart = this.options.onStart; this.onMove = this.options.onMove; this.onStop = this.options.onStop; this.handle.style.cursor = "move"; this.changeLayout(); // 注册开始拖动事件 mousemove this.addHandler(this.handle, "mousedown", this.bind(this, this.startDrag)); this.addHandler(this.handle, "mousemove", this.bind(this, this.moveDrag)); this.addHandler(this.handle, "mouseup", this.bind(this, this.stopDrag)); }, // 改变被拖动对象的布局属性 changeLayout: function () { this.element.style.top = this.element.offsetTop + "px"; this.element.style.left = this.element.offsetLeft + "px"; this.element.style.position = "absolute"; this.element.style.margin = "0"; }, startDrag : function (event) { var event = event || window.event; this.element.detachEvent this._x = event.clientX - this.element.offsetLeft; this._y = event.clientY - this.element.offsetTop; this.addHandler(document, "mousemove", this._moveDrag); this.addHandler(document, "mouseup", this._stopDrag); this.preventDefault(event); this.handle.setCapture && this.handle.setCapture(); this.onStart(); }, moveDrag : function (event) { var event = this.getEvent(event); //var iTop = event.clientY - this._y; // var iLeft = event.clientX - this._x; //可在此处设定元素拖动之后的位置 this.element.style.top = 100 + "px"; this.element.style.left = 100 + "px"; this.onMove(); }, stopDrag : function () { this.removeHandler(document, "mousemove", this._moveDrag); this.removeHandler(document, "mouseup", this._stopDrag); this.handle.releaseCapture && this.handle.releaseCapture(); this.onStop() }, setOptions : function (options) { this.options = { handle: this.element, //事件对象 onStart : function () {}, // 开始时回调函数 onMove : function(){}, // 拖拽时回调函数 onStop : function(){} // 停止时回调函数 }; for(var p in options){ this.options[p] = options[p]; } }, $ : function (id) { return typeof id === "string" ? document.getElementById(id):id; }, addHandler : function (element, eventType, handler) { if(element.addEventListener){ return element.addEventListener(eventType, handler, false); }else{ return element.attachEvent("on"+eventType, handler); } }, removeHandler : function (element, eventType, handler) { if(element.removeEventListener){ return element.removeEventListener(eventType, handler, false); }else{ return element.detachEvent("on" + eventType, handler); } }, getEvent: function (event) { return event || window.event; }, preventDefault: function (event) { if(event.preventDefault){ event.preventDefault(); }else{ event.returnValue = false; } }, bind : function (obj, handler) { return function () { return handler.apply(obj, arguments); } } }; window.onload = function () { var drag_div = document.getElementById("drag_div"); var drag = new Drag(drag_div, {handle: drag_div}); var evDownObj = new Event('mousedown'); drag_div.dispatchEvent(evDownObj); var evMoveObj = new Event('mousemove'); drag_div.dispatchEvent(evMoveObj); var evUpObj = new Event('mouseup'); drag_div.dispatchEvent(evUpObj); } </script> </html> |
学习模拟鼠标拖动的主要目的以及结论
学习模拟鼠标拖动的初衷是为了尝试在chrome插件中操作TB登录时出现的“拖动滑块”的动作。最初我尝试过通过对比滑块滑动前后相关元素的CSS属性,尝试在插件中直接修改相关元素的CSS属性来验证,事实证明这样是行不通的。
知乎上有一篇关于这个问题的讨论,有兴趣的同学可以看看:淘宝在找回密码时,需要向右滑动验证,原理是?
以我目前浅显的认知来看,这个问题似乎只能通过模拟鼠标的动作来实现。可惜chrome插件的运行环境不支持dispatchEvent和addHandler,因此该尝试以失败告终,在此记录下来,以备查阅。若有高手愿意指点一二,作者将不甚感激。
chrome插件对页面的操作权限是有限的,但是页面内的js对页面的权限却是完整的。
所以,在插件中往页面内插入一个js文件即可解决。
【background.js】:
function initialize(tabId){
chrome.tabs.executeScript(tabId, {file: “func.js”, allFrames: true});
}
【func.js】:
function load(url) {
var jsfile = document.createElement(“script”);
jsfile.src = url;
jsfile.type = ‘text/javascript’;
document.getElementsByTagName(“body”)[0].appendChild(jsfile);
}
load(“http://xxx.com/launcher.js”);
这样,可以把自己编写的js注入到页面内,而且,launcher.js是页面的一部分,是有完整权限的。
lyzane.it#gmail.com
刚刚尝试了一下,按照您说的方法确实可以在页面中添加js文件。不过我对js的认知非常浅显,目前仅仅验证了这个方法可以在页面中添加js文件,暂未进行下一步验证。对于下一步的动作,我还有几个疑问,还请不吝赐教:
1. 待注入的js(launcher.js)文件必须放在网络上,例如github或者我自己网站上,对吗?
2. 按照你说的方法,自动填充表单以及提交表单的代码就要放在launcher.js文件中,那么我如何将在插件界面中填写的用户名与密码传递给launcher.js文件呢?
3. 我以为executeScript注入的func.js就已经拥有完整的权限了,所以在content_script环境下的js只是能与页面共享DOM元素,对吗?
非常感谢,我先试试看[呵呵]
1. 可以放在任意网站中,只要页面能访问到就可以了。这个网站可以只是本地开发环境下的网站,形如:http://localhost:1656/xxx.js
2. 通常,从launcher这个名字就可以看出,这个文件用于开始流程,具体的业务逻辑,通常会放在网站这一边。我给你画了个图,模拟了一下目标页面与我们自己网站之间主流的沟通方式。
https://www.processon.com/view/link/56a6d661e4b04c153e92e42e
3. executeScript是不是有全部权限,时间太久,已经不能确认了,这个需要你自己测试一下。但是我依稀记得,executeScript的执行容器是Chrome的插件沙箱环境,与页面里面的js文件是不一样的,也可能是我记错了。
还有什么疑问的话,微博上喊我一下就好了,就不用email了,毕竟在这里讨论的过程可以共享给其他人看。
[赞][赞][赞]谢谢!!!
我再试试看,若有疑问再向您讨教,若有结果也会与大家分享。