思路
思路一
中心思想:首先算出首次拖动窗口距视窗的上面和左面的距离,然后计算出每次鼠标的偏移量(可正可负)。然后将其相加,就可得到新的偏移量。
代码思路:
- 设置一个变量,用于标记鼠标是否按下。
mousedown
钩子函数中记录下此时的clientX1
,clientY1
,offsetX
,offsetY
。
mousemove
钩子函数中记录下次此时的clientX2
,clientY2
,然后计算两组量:
- 拖动之前拖动框距文档边框的距离
left1 = clientX1 - offsetX
,top1 = clientY1 - offsetY
- 拖动的距离
disX = clientX2 - clientX1
,disY = clientY2 - clientY1
- 此时新的
left2 = left + disX
,top2 = top + disY
代码实现:
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
| banMouseDown(e) { console.log("鼠标按下"); this.isDown = true; let dw = this.$refs[this.wid]; let banner = this.$refs["banner"]; banner.style.cursor = "move"; let RectData = dw.getBoundingClientRect(); Vue.set(this.currenctPositon, "x", e.clientX); Vue.set(this.currenctPositon, "y", e.clientY); Vue.set(this.currenctPositon, "offX", e.offsetX); Vue.set(this.currenctPositon, "offY", e.offsetY); },
banMouseUp() { this.isDown = false; let banner = this.$refs["banner"]; banner.style.cursor = "default"; },
banMouseMove(e) { debounce(this, this.handle, 20)(e); },
handle(e) { if (this.isDown) { console.log(e); let dw = this.$refs[this.wid]; let banner = this.$refs["banner"]; banner.style.cursor = "move"; let RectData = dw.getBoundingClientRect(); let cx = e.clientX; let cy = e.clientY; let moveX = (this.currenctPositon.x - this.currenctPositon.offX) + (cx - this.currenctPositon.x); let moveY = (this.currenctPositon.y - this.currenctPositon.offY) + (cy - this.currenctPositon.y); Vue.set(this.currenctPositon, "x", cx); Vue.set(this.currenctPositon, "y", cy); dw.style.left = moveX + "px"; dw.style.top = moveY + "px"; } },
|
思路二
然后又发现另一个思路(似乎更简单):
中心思想:要想达到拖动效果,即要保持鼠标的位置相对于拖动框是相对静止的。而鼠标的位置相对于拖动框就是offset
的值,是不会变化的。所以当鼠标发生移动时,拖动框的位置也要发生变化,才能保证offset
的值是不变的。所以我们在新的一次鼠标移动(mousemove
)后,新的偏移量应当赋予拖动框的left
,right
值。即left = clientX2 - offsetX
,top = clientY2 - offsetY
代码思路:
- 设置一个变量,用于标志鼠标是否按下。
mousedown
钩子函数中记录下此时的clientX1
,clientY1
,offsetX
,offsetY
。
mousemove
钩子函数中记录下次此时的clientX2
,clientY2
,然后计算拖动出偏移量dOffsetX
,dOffsetY
,具体
left = clientX2 - offsetX
top = clientY2 - offsetY
- 将拖动窗口的样式:
style.left
设置为left
style.top
设置为top
实测是可以使用的。
代码实现:
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
| banMouseDown(e) { console.log("鼠标按下"); this.isDown = true; let dw = this.$refs[this.wid]; let banner = this.$refs["banner"]; banner.style.cursor = "move"; let RectData = dw.getBoundingClientRect(); Vue.set(this.currenctPositon, "x", e.clientX); Vue.set(this.currenctPositon, "y", e.clientY); Vue.set(this.currenctPositon, "offX", e.offsetX); Vue.set(this.currenctPositon, "offY", e.offsetY); },
banMouseUp() { this.isDown = false; let banner = this.$refs["banner"]; banner.style.cursor = "default"; },
banMouseMove(e) { debounce(this, this.handle, 20)(e); },
handle(e) { if (this.isDown) { let dw = this.$refs[this.wid]; let banner = this.$refs["banner"]; banner.style.cursor = "move"; let RectData = dw.getBoundingClientRect(); let cx = e.clientX; let cy = e.clientY; Vue.set(this.currenctPositon, "x", cx); Vue.set(this.currenctPositon, "y", cy); let moveX = this.currenctPositon.x - this.currenctPositon.offX; let moveY = this.currenctPositon.y - this.currenctPositon.offY; dw.style.left = moveX + "px"; dw.style.top = moveY + "px"; } }, }
|
思路三
接下来我发现了另外一个api
:movementX
,movementY
,这个api
会在mousemove
时记录与上一次移动的距离,所以就在思路一的基础上可以省略求disX
,disY
的过程,所以有了以下代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
|
handle(e) { if (this.isDown) { let dw = this.$refs[this.wid]; let banner = this.$refs["banner"]; banner.style.cursor = "move"; let RectData = dw.getBoundingClientRect(); let cx = e.clientX; let cy = e.clientY; Vue.set(this.currenctPositon, "x", cx); Vue.set(this.currenctPositon, "y", cy); let moveX = this.currenctPositon.x -this.currenctPositon.offX + e.movementX; let moveY = this.currenctPositon.y -this.currenctPositon.offY + e.movementY; dw.style.left = moveX + "px"; dw.style.top = moveY + "px"; } },
|
但是由于movementX
,movementY
返回的是int
,精度不够,所以会出现不跟手的情况;而且IE均不支持该属性,所以不是最优方案。
思路四
最后我去参考了layui
的方案。
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
| moveElem.on('mousedown', function(e){ e.preventDefault(); if(config.move){ dict.moveStart = true; dict.offset = [ e.clientX - parseFloat(layero.css('left')) ,e.clientY - parseFloat(layero.css('top')) ]; ready.moveElem.css('cursor', 'move').show(); } });
_DOC.on('mousemove', function(e){
if(dict.moveStart){ var X = e.clientX - dict.offset[0] ,Y = e.clientY - dict.offset[1] ,fixed = layero.css('position') === 'fixed'; e.preventDefault(); dict.stX = fixed ? 0 : win.scrollLeft(); dict.stY = fixed ? 0 : win.scrollTop();
if(!config.moveOut){ var setRig = win.width() - layero.outerWidth() + dict.stX ,setBot = win.height() - layero.outerHeight() + dict.stY; X < dict.stX && (X = dict.stX); X > setRig && (X = setRig); Y < dict.stY && (Y = dict.stY); Y > setBot && (Y = setBot); } layero.css({ left: X ,top: Y }); }
|
layui
采用的是一种更为常见的方式。
其关键在于其并没有直接拿offsetX
,而是通过clientX - left
,clientY - top
来计算offset
。其原因在于可能是offset
属性的兼容性问题。
Feature |
Chrome |
Edge |
Firefox (Gecko) |
Internet Explorer |
Opera |
Safari |
Basic support |
(Yes) |
(Yes) |
39.0 (39.0) |
6 |
(Yes) |
(Yes) |
Redefined from long to double |
56 |
? |
? |
? |
? |
? |
offsetX
在早期返回一个int
,这对于拖动窗口有很大的影响,比如上面的movement
属性,所以layui
选择了更加稳定的直接获取其style.left
属性。
函数节流
这一部分,我觉得可加可不加,因为虽然DOM消耗很大,但是目前计算机的性能是完全足够承担一秒几十次到上百次的DOM重绘的。额可以在后期加入检测机制,如果机器性能较差,则可以节流mousemove
函数。具体细节可以查看函数的节流与防抖