Vue插件开发3-简单拖动窗口的优化

思路

思路一

中心思想:首先算出首次拖动窗口距视窗的上面和左面的距离,然后计算出每次鼠标的偏移量(可正可负)。然后将其相加,就可得到新的偏移量。

代码思路:

  1. 设置一个变量,用于标记鼠标是否按下。
  2. mousedown钩子函数中记录下此时的clientX1,clientY1,offsetX,offsetY
  3. mousemove钩子函数中记录下次此时的clientX2,clientY2,然后计算两组量:
    1. 拖动之前拖动框距文档边框的距离left1 = clientX1 - offsetXtop1 = clientY1 - offsetY
    2. 拖动的距离disX = clientX2 - clientX1disY = clientY2 - clientY1
  4. 此时新的left2 = left + disXtop2 = 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 - offsetXtop = clientY2 - offsetY

代码思路:

  1. 设置一个变量,用于标志鼠标是否按下。
  2. mousedown钩子函数中记录下此时的clientX1,clientY1,offsetX,offsetY
  3. mousemove钩子函数中记录下次此时的clientX2,clientY2,然后计算拖动出偏移量dOffsetXdOffsetY,具体
    1. left = clientX2 - offsetX
    2. top = clientY2 - offsetY
  4. 将拖动窗口的样式:
    1. style.left设置为left
    2. 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";
}
},
}

思路三

接下来我发现了另外一个apimovementXmovementY,这个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 - leftclientY - 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函数。具体细节可以查看函数的节流与防抖

Powered by Hexo and Hexo-theme-hiker

Copyright © 2019 - 2024 My Wonderland All Rights Reserved.

UV : | PV :