背景:

  前两天面试的时候被问到,元素的拖放应该怎么实现,同时元素的自动合并应该怎么实现(当两个元素的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页