vue(js) 拖拽改變排序(陣列)位置(原理及程式碼)

發表於2019-02-28

vue(js) 拖拽改變排序(陣列)位置(原理及程式碼)

先說實現的邏輯(針對操作邏輯對應的事件講解):

onmousedown(滑鼠按下事件):

重新獲取操作盒子的大小,以及座標(考慮到客戶有可能變更瀏覽器大小的情況)。

  • 獲取操作盒子頂部距離
  • 獲取操作盒子左邊距離
  • 獲取操作盒子的高度
  • 獲取操作盒子的寬度

滑鼠按下獲取到按下的item資料,所在item的座標,(讓隨滑鼠移動的臨時dom所在滑鼠的位置完全對應上按下時的位置,使用者體驗像真複製出來的一樣)

  • 獲取按下時陣列的index,要刪除的位置
  • 滑鼠點選時的頂部距離 = 滑鼠距離瀏覽器的頂部的高度event.clientY + 瀏覽器滾動條隱藏的高度document.documentElement.scrollTop
  • 滑鼠點選時的左邊距離 = 滑鼠距離瀏覽器的左邊的高度event.clientX + 瀏覽器滾動條隱藏的距離document.documentElement.scrollLeft
  • item的左邊距離 = 滑鼠點選時的左邊距離 - 獲取操作盒子左邊距離
  • item的頂部距離 = (滑鼠點選時的頂部距離 - 獲取操作盒子頂部距離)% (每個item的高度+邊距(我固定了位置這裡設定為88,不固定的話可另外計算))

賦值給一個隨滑鼠移動的臨時dom上,先不顯示(因為點選是可開啟編輯的,移動時再顯示)

onmousermove(滑鼠移動時的事件):

  • 判斷臨時的dom是否有資料,有資料的時候進行移動,顯示出來
  • 設定臨時dom的top = 滑鼠頂部位置event.clientY - 按下時獲取到的item的頂部距離 + ‘px’(因為顯示的就是距離瀏覽器視窗座標,所以不用加上滾動條隱藏的位置)
  • 設定臨時dom的left = 滑鼠左邊位置event.clientX - 按下時獲取到的item的左邊距離 + ‘px’
  • 獲取滑鼠移動到的index位置 = ((滑鼠獲取到的頂部距離+滾動條隱藏的距離) - 獲取操作盒子頂部距離)/ (每個item的高度+邊距(我固定了位置這裡設定為88,不固定的話可另外計算))四捨五入取整

onmouserup(滑鼠釋放時的事件):

我們獲取到了要刪除的index,獲取到了滑鼠釋放前停留的index。只要做陣列插入,再重置獲取到的資料就可以了。可以做一些細小的調整,比如滑鼠移動到的位置進行高亮顯示,要插入資料的準確性等等


然後,普及一下需要用到的座標獲取方法

獲取操作盒子的寬度offsetWidth及高度offsetHeight

給盒子一個ref,不是vue就操作dom

this.offset_width = this.$refs.componentBox.offsetWidth
this.offset_height = this.$refs.componentBox.offsetHeight
複製程式碼

獲取操作盒子左上角的座標

offsetLeftoffsetTop這兩個方法是距離父元素文件左邊距離,距離父元素文件頂部距離,可通過offsetParent做遍歷相加,拿到準確座標

// 獲取盒子左上角座標
      const componentBox = this.$refs.componentBox
      let top = componentBox.offsetTop
      let left = componentBox.offsetLeft
      let parent = componentBox.offsetParent
     //每一層父元素相加一次距離
      while (parent !== null){
        left += parent.offsetLeft;
        top += parent.offsetTop;
        parent = parent.offsetParent;
   }
複製程式碼

獲取滑鼠距離瀏覽器頂部座標event.clientY,但是瀏覽器可能有滾動條下拉的情況,所以要得到頁面的上的準確位置 = 滑鼠座標 + 瀏覽器被隱藏距(document.documentElement.scrollTop)


最後貼上部分主要實現程式碼

<template>
       <div ref="componentBox" @mousemove="onmousermoves">
            <div>
                <div
                    v-for="(component,componentIndex) in currentCustomPage.components"
                    :key="componentIndex"
                    :class="insertIndex === componentIndex?'move-active':''||(insertIndex === -1&&componentIndex===0)?'move-active-top':''"
                    class="component-item"
                    @mousedown="onmousedowns($event,componentIndex)"
                >
                   <div class="name" @click="handleEditComponent(componentIndex)">{{ component.type_data.data.name }}</div>
                     <div class="component-item_delete" @click="handleDeleteComponent(componentIndex)">刪除</div>
                    </div>
                </div>
              <!-- 滑鼠按下臨時dom -->
              <div v-show="mousedownInfo.status" @mouseup="onmouseups" ref="moveItem" class="mousedownInfo-item">
                <div class="name" >{{ mousedownInfo.name }}</div>
            </div>

        </div>
</template>
<style scoped>
  .move-active{
  border-bottom:2px #409EFF solid!important;
  box-shadow:0 1px 2px #409EFF!important;;
}
.move-active-top{
  border-top:2px #409EFF solid!important;
  box-shadow:1px 0 2px #409EFF!important;;
}
</style>
<script>
  export default{
    data(){
       return {
          // 拖拽
      offset_top: null,
      offset_left: null,
      offset_width: null,
      offset_height: null,
      // 臨時儲存資訊,隨滑鼠移動
      mousedownInfo: {
        name: '',
        index: null,
        status:false,
        width:0,
        height:0
      },
      // 要插入的index
      insertIndex:null,
       }
    },
   methods:{
      // 獲取移動父盒子的座標,大小
    handMove() {
      const componentBox = this.$refs.componentBox
      let top = componentBox.offsetTop
      let left = componentBox.offsetLeft
      let parent = componentBox.offsetParent
      // 獲取盒子左上角座標
      //每一層父元素相加一次距離
      while (parent !== null){
        left += parent.offsetLeft;
        top += parent.offsetTop;
        parent = parent.offsetParent;
&emsp;&emsp;&emsp;&emsp;}
      this.offset_top = top
      this.offset_left = left
      this.offset_width = componentBox.offsetWidth
      this.offset_height = componentBox.offsetHeight
      // 初始化移動item的寬度
      this.$refs.moveItem.style.width = componentBox.offsetWidth + 'px'
    },
    // 滑鼠按下臨時儲存
    onmousedowns(event, index) {
      // 獲取移動父盒子的座標,大小
      this.handMove()

      const e= event || window.event
      this.mousedownInfo.name = this.currentCustomPage.components[index].type_data.data.name
      // 儲存要刪除的index
      this.mousedownInfo.index = index
      // 滑鼠距離點選元素的寬高
      this.mousedownInfo.width = (e.clientX+document.documentElement.scrollLeft) - this.offset_left
      // (滑鼠高度+超出瀏覽器高度) - 父盒子高度 / 每個item的高度88,取模% =  滑鼠所在item的高度
      this.mousedownInfo.height = ((e.clientY+document.documentElement.scrollTop) - this.offset_top) % 88
      // 按下先初始化位置
    /*   this.$refs.moveItem.style.top = (e.clientY - this.mousedownInfo.height) + 'px'
      this.$refs.moveItem.style.left = (e.clientX - this.mousedownInfo.width) + 'px' */
    },
    // 滑鼠移動時
    onmousermoves(event) {
      if(this.mousedownInfo.name){
          const e = event || window.event
        // 判斷滑鼠是否移出了盒子
        if( (e.clientX-this.offset_left)<0 ||(e.clientX-this.offset_left)>this.offset_width
          || ((e.clientY+document.documentElement.scrollTop)- this.offset_top)<-88
          || ((e.clientY+document.documentElement.scrollTop)- this.offset_top)>Number(this.offset_height)+88
          )
         {
          this.resetMove()
          return false
        }
          // 按下移動時展示,不拖拽可以進入編輯
          this.$set(this.mousedownInfo,'status',true)

          const stateIndex = ((e.clientY+document.documentElement.scrollTop)- this.offset_top) / 88
          this.insertIndex = (stateIndex.toFixed(0))-1
          // 根據滑鼠位置計算移動item的位置
          this.$refs.moveItem.style.top = (e.clientY - this.mousedownInfo.height) + 'px'
          this.$refs.moveItem.style.left = (e.clientX - this.mousedownInfo.width) + 'px'
        
      }
    },
    // 滑鼠釋放時,
    // 進行元素的位置更改
    onmouseups() {
      const item = this.currentCustomPage.components.splice(this.mousedownInfo.index,1)
      this.currentCustomPage.components.splice(this.insertIndex+1,0,item[0])
      this.resetMove()
    },
    // 重置拖拽資料
    resetMove(){
      this.mousedownInfo={
        name:null,
        index:null,
        width:0,
        height:0,
      }
      this.insertIndex = null
      this.mousedownInfo.status = false;
    },

   }
  }
</script>
複製程式碼

相關文章