对页面元素的拖拽变形

最常见的对元素进行拖拽变形,主要是在需要变形的元素的四角上添加四个白色方块,通过监听这四个白色方块的移动事件,实现对元素的变形,主要是修改元素的widthheight

过程拆解

  • 为元素添加鼠标移动监听事件,监听鼠标相对于元素的移动距离
  • 根据移动位置的不同修改元素的大小与定位
    • 左上角移动:修改元素widthheightwidthtop
    • 右上角移动:修改元素widthheighttop
    • 左下角移动:修改元素widthheightleft
    • 右下角移动:修改元素widthheight

以上只是大概的思路,代码层面还需进行完善,比如说边界处理,即如果左上角拖拽越过右上角如何处理等。

代码实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class DragControll {
// 变形元素
dragEl = null;
// 变形元素的位置与大小
_style = {
top: 0,
left: 0,
width: 0,
height: 0,
};
elBox = null;
tl = null;
tr = null;
bl = null;
br = null;
}

1.设置四角的拖拽元素

画板中的每一个元素都可以进行变形,如果为每个元素添加变形组件,回造成大量代码冗余,如果页面中存在元素过多也会造成页面卡顿,因此在当前画板中只生成一个变形框,拖过点击需要变形的元素获取此元素的位置与宽高,然后修改变形框的位置与宽高,通过监听变形框、位置与宽高的变化来对变形元素进行变形。

1
2
3
4
5
6
7
8
9
.drag-box i {
width: 4px;
height: 4px;
border: 1px solid rgb(6, 123, 239);
background-color: #ffffff;
}
.drag-box i:hover {
background-color: blue;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class DragControll {
init() {
this.elBox = document.createElement("div");
this.elBox.className = "drag-box";
this.elBox.style.display = "none";
this.tl = this._initDragIcon("i", "tl");
this.tr = this._initDragIcon("i", "tr");
this.bl = this._initDragIcon("i", "bl");
this.br = this._initDragIcon("i", "br");
document.body.appendChild(this.elBox);
}

// 创建四角的拖拽元素
_initDragIcon(elName, className) {
const el = document.createElement(elName);
el.className = className;
el.style.position = 'absolute';
this.elBox.appendChild(el);
return el;
}
}

2.监听四角拖拽元素的移动

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
class DragControll {
// ...

// 创建四角的拖拽元素
_initDragIcon(elName, className) {
// ...
const _this = this;
el.addEventListener('mousedown', function(e) {
e.stopPropagation();
function drag(e) {
_this._dragFn(className, e);
}
function stopDrag() {
document.removeEventListener('mousemove', drag);
document.removeEventListener('mouseup', stopDrag);
}

document.addEventListener('mousemove', drag);
document.addEventListener('mouseup', stopDrag);
});
this.elBox.appendChild(el);
return el;
}

// 四角拖拽后修改_style
_dragFn(type, e) {}
}

3.设置变形框的位置与宽高
3-1.获取需变形元素的位置与宽高

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
class DragControll {
// ...
_dragFn(type, e) {
const { clientX, clientY } = e;
const { left, top, width, height } = this._style;
let obj = {};
switch(type) {
case "tl":
if (clientX > left + width || clientY > top + height) return;
obj = {
left: clientX,
top: clientY,
width: width + left - clientX,
height: height + top - clientY,
};
break;
case "tr":
if (clientX < left || clientY > top + height) return;
obj = {
top: clientY,
width: clientX - left,
height: height + top - clientY,
};
break;
case "bl":
if (clientX > left + width || clientY < top) return;
obj = {
left: clientX,
width: width - clientX + left,
height: clientY - top,
};
break;
case "br":
if (clientX < left || clientY < top) return;
obj = {
width: clientX - left,
height: clientY - top,
};
break;
}

this.setStyle(obj)
}

setStyle(obj) {
// ...
}
}

3-2.根据位置与宽高设置四角拖拽元素的位置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class DragControll {
setStyle(obj) {
Object.keys(obj).forEach((k) => {
if (this._style[k] != undefined || this._style[k] != null) {
this._style[k] = obj[k];
}
});
const { width, height, top, left } = this._style;
const iconRect = this.tl.getBoundingClientRect();
this.elBox.style.display = "inline-block";
this._setIconPostion(this.tl, top - iconRect.height / 2, left - iconRect.width / 2)
this._setIconPostion(this.tr, top - iconRect.height / 2, left + width - iconRect.width / 2)
this._setIconPostion(this.bl, top + height - iconRect.height / 2, left - iconRect.width / 2)
this._setIconPostion(this.br, top + height - iconRect.height / 2, left + width - iconRect.width / 2)
// 设置变形元素的位置与宽高
// setDragItemStyle
}

_setIconPostion(el, top, left) {
el.style.top = top + "px";
el.style.left = left + "px";
}
}

4.为需要变形的元素添加点击事件

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
<style>
.square {
width: 40px;
height: 40px;
}
</style>
<div class="square" style="position:absolute; top: 600px;left: 600px;"></div>

<script>
class DragControll {
// ...
// 点击变形元素初始化变形框
handleClick(dragEl) {
this.elBox.style.display = "inline-block";
const { top, left, width, height } = dragEl.getBoundingClientRect();
this.setStyle({
top,
left,
width,
height,
});
}
}
const dragControll = new DragControll();
const el = document.querySelector(".square");
el.addEventListener("click", function(e) {
dragControll.handleClick(this);
})
</script>

源码

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
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
<style>
.square {
width: 50px;
height: 50px;
background-color: blue;
}
.controll-box i {
width: 4px;
height: 4px;
border: 1px solid rgb(6, 123, 239);
background-color: #ffffff;
}
.controll-box i:hover {
background-color: blue;
}
</style>
<body>
<div class="square" style="position: absolute; left: 500px; top: 500px"></div>
<script>
class DragControll {
dragEl = null;
_style = {
top: 0,
left: 0,
width: 0,
height: 0,
};

elBox = null;
tl = null;
tr = null;
bl = null;
br = null;

constructor() {
this.init();
}

init() {
this.elBox = document.createElement("div");
this.elBox.className = "controll-box";
this.elBox.style.display = "none";
this.tl = this._initDragIcon("i", "tl");
this.tr = this._initDragIcon("i", "tr");
this.bl = this._initDragIcon("i", "bl");
this.br = this._initDragIcon("i", "br");
document.body.appendChild(this.elBox);

const _this = this;
document.addEventListener("click", function (e) {
if (!_this.isActive(e)) {
_this.hide();
}
});
}

isActive(e) {
const { clientX, clientY } = e;
const { top, left, width, height } = this._style;
return (
clientX >= left &&
clientX <= left + width &&
clientY >= top &&
clientY <= top + height
);
}

handleClick(dragEl) {
this.dragEl = dragEl;
this.elBox.style.display = "inline-block";
const { top, left, width, height } = dragEl.getBoundingClientRect();
console.log(top, left, width, height);
this.setStyle({
top,
left,
width,
height,
});
}

setStyle(obj) {
console.log(obj);
Object.keys(obj).forEach((k) => {
if (this._style[k] != undefined || this._style[k] != null) {
this._style[k] = obj[k];
}
});
const { width, height, top, left } = this._style;
const iconRect = this.tl.getBoundingClientRect();
console.log(iconRect);
this.elBox.style.display = "inline-block";
this._setIconPostion(
this.tl,
top - iconRect.height / 2,
left - iconRect.width / 2
);
this._setIconPostion(
this.tr,
top - iconRect.height / 2,
left + width - iconRect.width / 2
);
this._setIconPostion(
this.bl,
top + height - iconRect.height / 2,
left - iconRect.width / 2
);
this._setIconPostion(
this.br,
top + height - iconRect.height / 2,
left + width - iconRect.width / 2
);

this._setDragItemStyle({ width, height, top, left });
}

_setIconPostion(el, top, left) {
el.style.top = top + "px";
el.style.left = left + "px";
}

_setDragItemStyle(obj) {
const { width, height, top, left } = obj;
this.dragEl.style.top = top + "px";
this.dragEl.style.left = left + "px";
this.dragEl.style.width = width + "px";
this.dragEl.style.height = height + "px";
}

hide() {
this.elBox.style.display = "none";
}

_initDragIcon(elName, className) {
const el = document.createElement(elName);
el.className = className;
el.style.position = "absolute";
const _this = this;
el.addEventListener("mousedown", function (e) {
e.stopPropagation();
// const RectParent = el.offsetParent.getBoundingClientRect();
function drag(e) {
_this._dragFn(className, e);
}
function stopDrag() {
document.removeEventListener("mousemove", drag);
document.removeEventListener("mouseup", stopDrag);
}

document.addEventListener("mousemove", drag);
document.addEventListener("mouseup", stopDrag);
});
this.elBox.appendChild(el);
return el;
}

_dragFn(type, e) {
const { clientX, clientY } = e;
const { left, top, width, height } = this._style;
let obj = {};
switch (type) {
case "tl":
if (clientX > left + width || clientY > top + height) return;
obj = {
left: clientX,
top: clientY,
width: width + left - clientX,
height: height + top - clientY,
};
break;
case "tr":
if (clientX < left || clientY > top + height) return;
obj = {
top: clientY,
width: clientX - left,
height: height + top - clientY,
};
break;
case "bl":
if (clientX > left + width || clientY < top) return;
obj = {
left: clientX,
width: width - clientX + left,
height: clientY - top,
};
break;
case "br":
if (clientX < left || clientY < top) return;
obj = {
width: clientX - left,
height: clientY - top,
};
break;
}

this.setStyle(obj);
}
}

const dragControll = new DragControll();
const el = document.querySelector(".square");
el.addEventListener("click", function (e) {
dragControll.handleClick(this);
});
</script>
</body>

扩展

等比例放大、缩小

最后更新: 2024年04月07日 22:20