背景:
前两天面试的时候被问到,元素的拖放应该怎么实现,同时元素的自动合并应该怎么实现(当两个元素的XY轴差距比较小的时候,忽略差距,两元素自动合并);自己之前只写过元素的拖动,并没有写过元素的自动合并,所以就尝试写了一下这个局部。(后话:但是回答不怎么样,只答道了使用CSS属性去判断,并没有将具体思路说明出来)
原理:
元素拖动:元素拖动的原理其实比较简单,只需要在元素被触发 mousemove 事件时,持续捕获鼠标的位置,然后计算赋值给元素即可
元素合并:元素合并其实本质上就是抓住合并的条件,然后对所有元素(并不是页面上所有元素,一般能合并的元素都有特定的空间,我们只需要在判断特定空间中的元素是否符合合并条件即可)进行判断。
具体的实现思路可参照下面代码。
实现代码:
1 <!DOCTYPE html>
2 <html>
3
4 <head>
5 <meta charset="utf-8">
6 <title>移动元素</title>
7 <style>
8 .box {
9 width: 900px;
10 height: 700px;
11 margin: 0 auto;
12 position: relative;
13 border: black solid 1px;
14 }
15 .box > div {
16 width: 90px;
17 height: 60px;
18 border: black solid 1px;
19 position: absolute;
20 text-align: center;
21 line-height: 60px;
22 box-sizing: border-box;
23 cursor: pointer;
24 }
25 .element-1 {
26 background: yellow;
27 z-index: 100;
28 }
29 .element-2 {
30 background: blue;
31 z-index: 100;
32 }
33 .element-3 {
34 background: chartreuse;
35 z-index: 100;
36 }
37 .element-4 {
38 background: red;
39 z-index: 100;
40 }
41
42
43 </style>
44 </head>
45
46 <body>
47 <div class="box">
48 <div class="element-1">1</div>
49 <div class="element-2">2</div>
50 <div class="element-3">3</div>
51 <div class="element-4">4</div>
52 </div>
53 <script>
54 const returnNumber = (num) => {
55 let x = num.indexOf('p')
56 return Number(num.slice(0, x))
57 }
58 // 给temp元素绑定事件,使该元素能够被鼠标拖动
59 const bindEventForMovingElement = (temp) => {
60 let ele = temp
61 let element = null
62 let differX = 0
63 let differY = 0
64 let width = returnNumber(window.getComputedStyle(ele).getPropertyValue('width'))
65 let height = returnNumber(window.getComputedStyle(ele).getPropertyValue('height'))
66 let controlX = 10
67 let controlY = 20
68
69 // 在元素被拖动期间,分为三个阶段(本质是触发的三个类型事件)
70 // mousedown:按下鼠标时触发
71 // mousemove:拖动鼠标时触发
72 // mouseup:松开鼠标时触发
73
74 // 在 mousedown 事件中,主要是完成一些前期准备工作
75 ele.addEventListener('mousedown', function (event) {
76 let self = event.target
77 // event 可以理解为发生某一事件的位置,event.target表示发生某一事件的元素
78 // (differX, differY) 在这里该坐标表示的是鼠标的坐标
79 // 这里之所以要记录是因为希望在拖动的过程中,鼠标相对于拖动元素的位置不发生改变
80 differX = event.clientX - self.offsetLeft
81 differY = event.clientY - self.offsetTop
82 // 给element元素赋值,该值为被拖动的元素
83 element = event.target
84 })
85
86 // 在 mousemove 事件中,主要是完成元素移动的事件,即鼠标移动,元素也跟着移动
87 // 需要注意的是,该功能实现的前提是 CSS 属性必须满足要求,必须是绝对定位以及有对照布局的元素
88 ele.addEventListener('mousemove', function (event) {
89 // 注意,这里是给元素绑定了一个 mousemove 事件,如果不使用 element控制赋值,那么每次鼠标移入该元素均会触发
90 if (element !== null) {
91 // 下面的 event.clientX, event.clientY 保证了鼠标相对于元素的位置不发生改变
92 // 可以去除 differX, differY 两个属性看一下效果
93 element.style.left = (event.clientX - differX) + 'px'
94 element.style.top = (event.clientY - differY) + 'px'
95
96 // element.style.left = event.clientX + 'px'
97 // element.style.top = event.clientY + 'px'
98 }
99 })
100
101 // 在 mouseup 事件中主要是完成 element 元素的释放内存,表示移动元素事件结束
102 // 同时在该事件中也可以给元素绑定事件,使元素能够与其他元素合并
103 ele.addEventListener('mouseup', function (event) {
104 element = null
105 let self = event.target
106 // 表示移动元素的 clientX
107 let eleLeft = event.target.offsetLeft
108 // 表示移动元素的 clientX
109 let eleTop = event.target.offsetTop
110
111 // 合并一般有一些要求,比如两个元素相差距离为 10px
112 // 因此我们在完成拖动之后,只需要对于元素周围是否存在相关元素进行判断即可
113 // 因为拖动元素模块一般会有一个 box 元素,将所有所有可移动的元素包裹在一起
114 // 因此判断是否需要合并,只需要判断 box 元素内的所有元素是否满足合并要求即可
115 let otherEle = document.querySelector('.box').children
116 for (let i = 0, len = otherEle.length; i < len; i++) {
117 if (otherEle[i] != self) {
118 let centerX = otherEle[i].offsetLeft
119 let centerY = otherEle[i].offsetTop
120
121 let differX = eleLeft - centerX
122 let differY = eleTop - centerY
123
124 // 横向合并,合并分为两种情况
125 if (Math.abs(differX) > width && Math.abs(differX) < (width + controlX) && Math.abs(differY) < controlY) {
126 let x = differX > 0 ? (width - differX) : ( - differX - width)
127 self.style.left = eleLeft + x + 'px'
128 self.style.top = eleTop - differY + 'px'
129 }
130 // 纵向合并,合并分为两种情况
131 if (Math.abs(differY) > height && Math.abs(differY) < (height + controlX) && Math.abs(differX) < controlY) {
132 let y = differY > 0 ? (height - differY) : (- differY - height)
133 self.style.top = eleTop + y + 'px'
134 self.style.left = eleLeft - differX + 'px'
135 }
136 }
137 }
138 })
139 }
140
141 let ele = document.querySelectorAll('.box > div')
142
143 for (let i = 0, len = ele.length; i < len; i++) {
144 bindEventForMovingElement(ele[i])
145 }
146 </script>
147 </body>
148
149 </html>
执行效果:
代码执行效果图
参考资料:
1. JavaScript高级程序设计(第三版)321页
2. JavaScript高级程序设计(第三版)618页 ~ 622页