雙端通用型JS拖拽插件的封裝與應用

最近工作中遇到一個需求,需要將一個元素從某位置拖動到另一固定位置後執行某一交互行爲,具體效果如下:

這個看似簡單的需求,然而實現起來卻並不那麼順利。我首先想到的是如何通過哪個現有的插件來快速解決這個問題,然而找了半天,並未找到合適的原生js插件,總會在實際使用當中出現一些莫名其妙的問題。所以,一不做二不休,乾脆自己封裝一個得了!一方面以後可能還會遇到類似的需求,另一方面自己寫的總歸更加熟悉,日後也更好維護和拓展。

閒言少敘,接下來就讓我們一步步用原生js來實現這個簡單的拖拽插件。


一、“類”的構建

插件,當然得有插件的樣子。這裏我用的是ES6中的class語法糖來實現“類”的封裝,這樣我們之後在使用插件時只需new一個對象就可以了。

//  定義拖拽插件
class Drag{
  constructor(selector, options){
  }
}
//  使用拖拽插件
new Drag('.box');  // 該box元素可拖拽

二、獲取元素

既然是拖拽插件,當然首先得獲取需要被拖拽的元素,這裏我們可以讓插件使用者直接把已經獲取到的元素對象傳進來,也可以以CSS選擇器的方式傳進來,在插件內部進行元素獲取。

具體可以通過類型判斷來實現,代碼如下:

getElement(selector){
  if(typeof selector === 'string'){  // 傳入css選擇器
    return document.querySelector(selector);
  } else if(typeof selector === 'object'){  // 傳入DOM對象
    return selector;
  } else {
    throw '請傳入正確的元素';
  }
}

然後我們可以在constructor中調用它:

constructor(selector, options){
  this.el = this.getElement(selector);
  if(!this.el){
    throw `未找到移動元素`;
  }
}

三、通過事件對象獲取鼠標(或手指)位置

在處理拖拽事件之前,我們得先知道拖拽的基本原理是什麼。

拖拽,本質上是鼠標(或手指)在元素上按下後,移動鼠標(或手指)時元素跟隨鼠標指針(或手指)位置移動,最後當鼠標(或手指)鬆開時元素停止移動。

其中最關鍵的部分就是鼠標(或手指)移動時,鼠標(或手指)位置的獲取,這時我們就要用到 事件對象 了。

1. PC端獲取鼠標位置

e.clientX   // 橫座標
e.clientY   // 縱座標

2. 移動端獲取手指位置

這裏又得分兩種情況,一種是手指移動時,一種是手指鬆開時。

① 手指移動時,也就是touchmove事件

e.touches[0].clientX   // 橫座標
e.touches[0].clientY   // 縱座標

爲什麼是touches[0]呢?因爲我們只用到了一根手指呀!

② 手指鬆開時,也就是touchend事件

e.changedTouches[0].clientX   // 橫座標
e.changedTouches[0].clientY   // 縱座標

看到了嗎?無論是哪種方式,我們獲取鼠標(或手指)的位置都是clientXclientY,只不過前面的那個對象不一樣而已。這時爲了代碼良好的複用性,我們可以對前面的那個對象進行簡單的封裝。

eventObj(event,isEnd = false){  // isEnd代表是否是手指鬆開時
  return isMobile() ? (isEnd ? event.changedTouches[0] : event.touches[0]) : event;
}
// 判斷是否是移動端,因爲移動端纔會有ontouchstart
function isMobile() {
  return document.body.ontouchstart;
}

四、獲取事件名稱

在整個拖拽過程中,我們無非就用到三種事件:開始、移動、結束。而在PC端和移動端分別對應一組事件名稱,我們將其分別用數組進行存儲。

eventName(){
  if(isMobile()){
    return ['touchstart','touchmove','touchend'];
  } else {
    return ['mousedown','mousemove','mouseup'];
  }
}

五、實現拖拽

前面準備工作做了這麼多,就是爲了實現這最最關鍵的一步:拖拽,也就是這三種事件(開始、移動、結束)的實現。

initData(){
  // 父元素的位置
  this.parentPos = {
    x: this.el.parentNode.getBoundingClientRect().left,
    y: this.el.parentNode.getBoundingClientRect().top,
    w: this.el.parentNode.getBoundingClientRect().width,
    h: this.el.parentNode.getBoundingClientRect().height,
  };
  // 移動元素的初始位置和大小
  this.elemPos = {
    x: this.el.offsetLeft,
    y: this.el.offsetTop,
    w: this.el.offsetWidth,
    h: this.el.offsetHeight,
  };
}
bindEvent(){
  let eventName = this.eventName(),
      status = false;

  //  初始化數據
  this.initData(); 

  // 開始
  this.el.addEventListener(eventName[0], e => {
    status = true;
  });

  // 移動
  document.addEventListener(eventName[1], e => {
    if(status){
      e = this.eventObj(e);
      let left = e.clientX - this.elemPos.w / 2 - this.parentPos.x,
          top = e.clientY - this.elemPos.h / 2 - this.parentPos.y;
      this.el.style.cssText = `position: absolute; left: ${ left }px; top: ${ top }px;`;
    }
  });

  // 結束
  document.addEventListener(eventName[2], e => {
    e = this.eventObj(e,true);
    status = false;
  });
}

結束語

寫到這裏,一個簡單的PC端和移動端雙端通用性JS拖拽插件就已經完成了。當然,我在此基礎上還加了拖拽目標和鬆開反彈等功能,完整代碼可在我的 Github 上預覽。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章