欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

JavaScript 實(shí)現(xiàn)拖拽效果組件功能(兼容移動(dòng)端)

 更新時(shí)間:2020年11月11日 15:47:01   作者:奇思妙想趙老師  
這篇文章主要介紹了JavaScript 實(shí)現(xiàn)拖拽效果組件功能(兼容移動(dòng)端),本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下

頁(yè)面元素拖拽是一種非常實(shí)用的前端效果,基于元素拖拽可以實(shí)現(xiàn)很多不同的功能,增加客戶(hù)端許多操作的便捷性,大大提高用戶(hù)體驗(yàn)。日常生活中大家多多少少都見(jiàn)過(guò)這種效果,所以就不廢話(huà)了,直接開(kāi)干吧。

預(yù)期目標(biāo)

實(shí)現(xiàn)一個(gè) Class 類(lèi),通過(guò)該 Class,可以將任意 DOM 元素(比如 div)一鍵變?yōu)榭赏献顟B(tài),也可以恢復(fù)成原來(lái)的狀態(tài),例如這樣:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <style>
    #box1 {
      height: 50px;
      width: 50px;
      background-color: cadetblue;
    }

    #box2 {
      height: 50px;
      width: 50px;
      background-color: blue;
    }

    #box3 {
      height: 50px;
      width: 50px;
      background-color: red;
    }
  </style>
</head>

<body>
  <div id="box1">1</div>
  <a id="box2">2</a>
  <div id="box3">3</div>
</body>
<script type="module">
	// 我們要完成的目標(biāo) Class
  import DragElement from './DragElement.js'
  // 使 3 個(gè)元素可拖拽
  let box1 = new DragElement(document.querySelector("#box1"))
  let box2 = new DragElement(document.querySelector("#box2"))
  let box3 = new DragElement(document.querySelector("#box3"))
  // box2 解除拖拽效果,恢復(fù)為原來(lái)的樣子
  // box2.dragRelease()
</script>
</html>

原本的樣子

在這里插入圖片描述

隨意拖放

在這里插入圖片描述

一、算法思路

1.1 拖拽的行為描述

我們先思考如何描述拖拽這一行為。我的思路是這樣的:

  • 先對(duì)拖拽這一行為進(jìn)行定義:在指定的元素上,若保持鼠標(biāo)按下?tīng)顟B(tài),則該元素將會(huì)跟隨鼠標(biāo)移動(dòng)。當(dāng)鼠標(biāo)松開(kāi),該元素將不再跟隨鼠標(biāo)移動(dòng)。如果是移動(dòng)端的話(huà),鼠標(biāo)的角色改為觸摸(touch)即可。

根據(jù)定義,我們可以確定幾個(gè)關(guān)鍵信息:

  • 鼠標(biāo)移動(dòng),是拖拽算法本身的作用范圍。
  • 鼠標(biāo)按下,開(kāi)啟拖拽
  • 鼠標(biāo)松開(kāi),關(guān)閉拖拽

可以看到,完整的拖拽功能分為 3 個(gè)部分,分別是開(kāi)啟、運(yùn)行與關(guān)閉。分別對(duì)應(yīng)鼠標(biāo)的按下、運(yùn)行、松開(kāi)事件。 因此我們至少需要設(shè)計(jì)相應(yīng)的 3 個(gè)函數(shù),作為事件的回調(diào)。在這里我分別命名為 dragStart()、dragMoving()、dragEnd()。

這里就出現(xiàn)了第一個(gè)重點(diǎn):如何描述拖拽功能的狀態(tài)變化?

顯然,鼠標(biāo)的按下與松開(kāi),將會(huì)決定DOM 元素是否能夠被拖拽,這是一種 “狀態(tài)” 的變化。這種狀態(tài)的變化,在編碼上,可以通過(guò)一個(gè)變量來(lái)實(shí)現(xiàn),也可以通過(guò)不斷地添加 or 移除回調(diào)函數(shù)來(lái)實(shí)現(xiàn)。如果通過(guò)變量的話(huà),在鼠標(biāo)沒(méi)有按下時(shí),鼠標(biāo)移動(dòng)事件也會(huì)觸發(fā)進(jìn)行狀態(tài)判斷,這其實(shí)是沒(méi)有必要的,因此方案上我們選擇后者,鼠標(biāo)按下與松開(kāi)時(shí),分別添加和移除實(shí)現(xiàn)拖拽的函數(shù)。

以上是拖拽本身的行為,此外,由于我們需要 DOM 元素能夠在原本的狀態(tài)和可拖拽狀態(tài)之間進(jìn)行轉(zhuǎn)換,因此我們還需要 2 個(gè)函數(shù),一個(gè)用于將 DOM 元素變?yōu)榭赏献顟B(tài),另一個(gè)用于卸載這些狀態(tài)。前者我稱(chēng)為 dragActive(),后者我稱(chēng)為 dragRelease()。它們做的事情,就是添加和解除事件監(jiān)聽(tīng)。

現(xiàn)在第一個(gè)問(wèn)題解決了,我們來(lái)解決第二個(gè)問(wèn)題,那就是:拖拽函數(shù)怎么實(shí)現(xiàn)?

1.2 拖拽的實(shí)現(xiàn)

首先看核心的,拖拽本身應(yīng)該怎么計(jì)算,如何讓元素跟著鼠標(biāo)走。

同樣的,我們繼續(xù)想象實(shí)際的場(chǎng)景。鼠標(biāo)按下時(shí),我們假設(shè)鼠標(biāo)的坐標(biāo)處于(x0, y0) 點(diǎn),鼠標(biāo)移動(dòng),假設(shè)移動(dòng)到了(x1, y1) 點(diǎn)。那么該元素,相對(duì)自身初始位置便移動(dòng)了(x1-x0, y1-y0) 的距離。這種相對(duì)于自身移動(dòng)的,在 CSS 上可以通過(guò)相對(duì)定位,也可以通過(guò) transform: translate 或 translate3d 來(lái)實(shí)現(xiàn),由于定位在布局中很常用,我們也不知道指定的 DOM 元素到底是什么樣式,為了盡量不影響原來(lái)的布局,所以我們采用 transform。

再回到具體計(jì)算上,鼠標(biāo)的位置 x 和 y,可以通過(guò)事件回調(diào)函數(shù)傳入的參數(shù) event 得到,在 PC 端是 event.clientX 和 event.clientY,移動(dòng)端是 event.changedTouches[0].pageX 和 event.changedTouches[0].pageY。而 mousemove 事件是連續(xù)觸發(fā)的,我們的拖動(dòng)也要讓元素跟著鼠標(biāo)連續(xù)運(yùn)動(dòng),因此需要不停更新 (x0, y0),(x1, y1) 的值,在每個(gè)細(xì)小的運(yùn)動(dòng)中都進(jìn)行差值計(jì)算,就像微積分一樣。為了方便記錄和更新,我們不妨把拖動(dòng)中需要的變量用一個(gè)對(duì)象表示,稱(chēng)為 dragInfo,掛載到 document 元素上,這樣在不同的函數(shù)、對(duì)象之間都可以訪問(wèn)。

class DragElement {
  constructor(element) {
    this.element = element
    document.dragInfo = {
      element: this.element,
      x0: 0,
      y0: 0,
      x1: 0,
      y1: 0
    }
  }
}

element 表示拖拽的元素,x 和 y 分別為計(jì)算所需的變量。

獲取鼠標(biāo)位置的函數(shù):

updateDragPosition = (event) => {
	return {
		x: event.clientX || (event.changedTouches ? event.changedTouches[0].pageX : document.dragInfo.x0),
		y: event.clientY || (event.changedTouches ? event.changedTouches[0].pageY : document.dragInfo.y0)
	}
}

或許有人會(huì)有疑問(wèn),為啥不直接 event.clientX || event.changedTouches[0].pageX,而是要用三元表達(dá)式。這是因?yàn)橛行┣闆r下,上述兩者可能都不存在,比如當(dāng)鼠標(biāo)移到瀏覽器左邊緣的時(shí)候,就無(wú)法獲得位置:

在這里插入圖片描述

獲取鼠標(biāo)位置的函數(shù)寫(xiě)完后,就可以寫(xiě)拖拽的函數(shù)了:

dragMoving = (event) => {
	document.dragInfo.x1 = this.updateDragPosition(event).x - document.dragInfo.x0 + document.dragInfo.x1
	document.dragInfo.y1 = this.updateDragPosition(event).y - document.dragInfo.y0 + document.dragInfo.y1
	document.dragInfo.x0 = this.updateDragPosition(event).x
	document.dragInfo.y0 = this.updateDragPosition(event).y
	document.dragInfo.element.style.transform = 'translate3d(' + document.dragInfo.x1 + 'px, ' + document.dragInfo.y1 + 'px, 0)';
}

但此時(shí)問(wèn)題就來(lái)了,由于 document 上只有一個(gè) dragInfo,不同的組件之間坐標(biāo)沖突如何解決?其實(shí)這個(gè)簡(jiǎn)單,只需要在 this.element 上添加一個(gè)對(duì)象記錄每次拖拽后的位置即可,每當(dāng)點(diǎn)擊一個(gè)拖拽元素時(shí),就將該元素的信息注入 document.dragInfo。

this.element.dragPostion = {
	x: 0,
	y: 0
}

綜上,我們已經(jīng)解決了最核心的流程描述與算法部分,接下來(lái)只要編碼就可以了。

二、編碼實(shí)現(xiàn)

請(qǐng)根據(jù)之前說(shuō)的思路,自行閱讀代碼,整體邏輯還是非常清晰的,如果有一些細(xì)節(jié)不懂,可以在評(píng)論區(qū)提出,或者我有空了再補(bǔ)充。

class DragElement {
  constructor(element) {
    this.element = element
    document.dragInfo = {
      element: this.element,
      x0: 0,
      y0: 0,
      x1: 0,
      y1: 0
    }
    document.updateDragPosition = this.updateDragPosition
    this.dragActive()
  }

  // 更新鼠標(biāo)位置
  updateDragPosition = (event) => {
    return {
      x: event.clientX || (event.changedTouches ? event.changedTouches[0].pageX : document.dragInfo.x0),
      y: event.clientY || (event.changedTouches ? event.changedTouches[0].pageY : document.dragInfo.y0)
    }
  }

  // 為元素配置相應(yīng)的拖拽控制函數(shù)
  dragActive = () => {
    if (!this.element) return
    this.element.style.display = "block" 
    this.element.addEventListener('mousedown', this.dragStart, false)
    this.element.addEventListener('touchstart', this.dragStart, false)
    this.element.addEventListener('mouseup', this.dragEnd, false) // 釋放
    this.element.addEventListener('touchend', this.dragEnd, false)
    this.element.addEventListener('touchcancel', this.dragEnd, false)
    // 為該元素添加一個(gè)對(duì)象,保存當(dāng)前位置
    this.element.dragPostion = {
      x: 0,
      y: 0
    }
  }

  // 釋放配置
  dragRelease = () => {
    this.element.removeEventListener('mousedown', this.dragStart)
    this.element.removeEventListener('touchstart', this.dragStart)
    this.element.removeEventListener('mouseup', this.dragEnd) // 釋放
    this.element.removeEventListener('touchend', this.dragEnd)
    this.element.removeEventListener('touchcancel', this.dragEnd)
    this.element.style.display = ""
    return this.element
  }

  // 點(diǎn)擊捕獲拖拽元素,初始化相應(yīng)信息
  dragStart = (event) => {
    document.dragInfo.element = this.element
    document.dragInfo.x0 = this.updateDragPosition(event).x
    document.dragInfo.y0 = this.updateDragPosition(event).y
    document.dragInfo.x1 = this.element.dragPostion.x
    document.dragInfo.y1 = this.element.dragPostion.y
    // 屏蔽默認(rèn)行為
    event.preventDefault();

    // mousemove 綁定在 document 上,防止鼠標(biāo)過(guò)快可能導(dǎo)致的元素跟丟
    document.addEventListener('mousemove', this.dragMoving, false)
    document.addEventListener('touchmove', this.dragMoving, false)
  }

  // 實(shí)時(shí)計(jì)算、更新相對(duì)位置變化
  dragMoving = (event) => {
    document.dragInfo.x1 = this.updateDragPosition(event).x - document.dragInfo.x0 + document.dragInfo.x1
    document.dragInfo.y1 = this.updateDragPosition(event).y - document.dragInfo.y0 + document.dragInfo.y1
    document.dragInfo.x0 = this.updateDragPosition(event).x
    document.dragInfo.y0 = this.updateDragPosition(event).y
    document.dragInfo.element.style.transform = 'translate3d(' + document.dragInfo.x1 + 'px, ' + document.dragInfo.y1 + 'px, 0)';
  }

  // 關(guān)閉拖拽
  dragEnd = () => {
    // 保存當(dāng)前位置
    this.element.dragPostion.x = document.dragInfo.x1
    this.element.dragPostion.y = document.dragInfo.y1
    // 解綁
    document.removeEventListener('touchmove', this.dragMoving)
    document.removeEventListener('mousemove', this.dragMoving)
  }
}

export default DragElement

到此這篇關(guān)于JavaScript 實(shí)現(xiàn)拖拽效果組件功能(兼容移動(dòng)端)的文章就介紹到這了,更多相關(guān)JavaScript 拖拽效果組件內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

最新評(píng)論