1. 程式人生 > 實用技巧 >移動端拖拽 - 固定定位 fixed

移動端拖拽 - 固定定位 fixed

移動端的專案經常會引入手勢庫來實現拖拽,不過如果只是一兩個頁面用到拖拽,再引入一個手勢庫就很不划算。最近的專案中就有這麼一個需求:

因為就這一個地方需要拖拽,所以我就沒有引入第三方庫

移動端的拖拽有兩種主流的實現方案:

1. 將元素設定為固定定位,然後在拖拽的時候修改其定位,實現拖拽的效果;

這種方案的優點是對佈局沒有要求,容易理解,缺點是效能低的時候會卡頓

2. 使用 transform 中的平移translate 屬性實現拖拽。

這種方案的優點是拖拽效果很平滑,還可以設定過渡的時間函式,缺點是隻能使用定位佈局

這篇部落格將介紹使用固定定位的方式實現拖拽,以後再寫為平移方案寫一篇部落格

一、開發思路

頁面採用了 flex 佈局,所以在拖動的時候,原本的元素不能脫離文件流

因此在開始拖拽 (觸發touchstart事件) 的時候,需要將原本的元素 A 拷貝一份 (cloneNode())

然後通過getBoundingClientRect()方法獲取到元素 A 的座標資訊,並對新元素 A2 定位

同時給原本的元素 A 設定visibility:hidden; 使之隱藏並佔位

拖拽的過程中(touchmove事件) ,即時更新 A2 的定位

為防止拖拽的時候誤觸,可以建立一個不可見的遮罩,在拖拽結束的時候關掉

在拖拽結束(touchend事件)的時候,記錄終點位置,刪除 A2 並顯示元素 A

資源網站大全 https://55wd.com 我的007辦公資源網站 https://www.wode007.com

二、開始拖拽

首先封裝一個建立遮罩的方法,用於放置拷貝出來的元素,並防止誤觸

createModal (id) {
  let modal = document.getElementById(id)
  if (!modal) { // 在沒有遮罩的時候建立遮罩
    modal = document.createElement('div')
    modal.id = id
    modal.style.cssText = `position: fixed; left: 0; top: 0; right: 0; bottom: 0; z-index: 999;`
    document.body.appendChild(modal)
  }
},

  

然後在觸發touchstart事件的時候,建立遮罩,並記錄起點資訊

為了記錄起點資訊,需要 data 中建立一個物件source,用於記錄點選的位置 client,和初始定位座標 start

handleTouchstart (e) { // 開始拖拽
  // 建立遮罩層
  this.createModal(this.modalID) // modalID 遮罩層的id,由外部定義

  let element = e.targetTouches[0]
  let target = e.target.cloneNode(true) // 拷貝目標元素
  target.id = this.copyID // copyID 拷貝元素的id,由外部定義

  // 記錄初始點選位置 client,用於計算移動距離
  this.source.client = {
    x: element.clientX,
    y: element.clientY
  }

  // 算出目標元素的固定位置
  let disX = this.source.start.left = element.target.getBoundingClientRect().left
  let disY = this.source.start.top = element.target.getBoundingClientRect().top
  target.style.cssText = `position: fixed; left: ${disX}px; top: ${disY}px;`

  // 將拷貝的元素放到遮罩中
  document.getElementById(this.modalID).appendChild(target)
},

  

三、處理拖拽

拖拽的時候,監聽touchmove事件

用【當前滑鼠點位置】減去【初始點選位置】得到移動的距離

再結合初始座標資訊,更新拖拽元素的座標

handleTouchmove (e) { // 拖拽中
  let element = e.targetTouches[0]
  let target = document.getElementById(this.copyID)

  // 根據初始點選位置 client 計算移動距離
  let left = this.source.start.left + element.clientX - this.source.client.x
  let top = this.source.start.top + element.clientY - this.source.client.y

  // 移動當前元素
  target.style.left = `${left}px`
  target.style.top = `${top}px`
},

  

拖拽結束的時候,記錄終點位置,刪除遮罩

handleTouchend (e) { // 拖拽結束
  let end = {
    x: e.changedTouches[0].clientX,
    y: e.changedTouches[0].clientY
  }
  // 刪除遮罩層
  let modal = document.getElementById(this.modalID)
  document.body.removeChild(modal)
  // 處理結果
  this.doingSth(end)
},

  

四、小結

上面的程式碼只實現了拖拽的功能,沒有對目標元素 A 進行顯示/隱藏的操作

獲取目標元素的座標資訊的時候使用了 getBoundingClientRect() 方法,但這個方法比較耗效能,應當少用

我的專案使用了vue.js,但拖拽功能用到vue的地方不多,將幾個用於記錄的物件提出來,就能複用於其他框架

手機效能差的時候,真的會卡...