中文字幕av专区_日韩电影在线播放_精品国产精品久久一区免费式_av在线免费观看网站

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

怎么實現react拖拽hooks

發布時間:2021-03-22 09:24:10 來源:億速云 閱讀:367 作者:小新 欄目:開發技術

這篇文章主要介紹了怎么實現react拖拽hooks,具有一定借鑒價值,感興趣的朋友可以參考下,希望大家閱讀完這篇文章之后大有收獲,下面讓小編帶著大家一起了解一下。

前言

源碼總共也就一百多行,看完這個大致可以理解一些成熟的react拖拽庫的實現思路,比如react-dnd,然后你上手這些庫的時候就非常快了。

使用hooks實現的大致效果動圖如下:

怎么實現react拖拽hooks

我們的目標是實現一個useDrag和useDrop的hooks,類似以下用法就可以輕松讓元素可以拖拽,并且在拖拽的各個生命周期,如下,可以自定義傳遞消息(順便介紹幾個拖拽會觸發的事件)。

  • dragstart:用戶開始拖拉時,在被拖拉的節點上觸發,該事件的target屬性是被拖拉的節點。

  • dragenter:拖拉進入當前節點時,在當前節點上觸發一次,該事件的target屬性是當前節點。通常應該在這個事件的監聽函數中,指定是否允許在當前節點放下(drop)拖拉的數據。如果當前節點沒有該事件的監聽函數,或者監聽函數不執行任何操作,就意味著不允許在當前節點放下數據。在視覺上顯示拖拉進入當前節點,也是在這個事件的監聽函數中設置。

  • dragover:拖拉到當前節點上方時,在當前節點上持續觸發(相隔幾百毫秒),該事件的target屬性是當前節點。該事件與dragenter事件的區別是,dragenter事件在進入該節點時觸發,然后只要沒有離開這個節點,dragover事件會持續觸發。

  • dragleave:拖拉操作離開當前節點范圍時,在當前節點上觸發,該事件的target屬性是當前節點。如果要在視覺上顯示拖拉離開操作當前節點,就在這個事件的監聽函數中設置。

使用方法 + 源碼講解

class Hello extends React.Component<any, any> {
 constructor(props: any) {
  super(props)
  this.state = {}
 }

 render() {
  return (
   <DragAndDrop>
    <DragElement />
    <DropElement />
   </DragAndDrop>
  )
 }
}

ReactDOM.render(<Hello />, window.document.getElementById("root"))

如上,DragAndDrop組件的作用是給所有的使用useDrag和useDrop的組件傳遞消息,比如當前拖拽的元素是那個dom,或者你想要其他信息都可以往里面加,我們看看它的實現。

const DragAndDropContext = React.createContext({ DragAndDropManager: {} });
const DragAndDrop = ({ children }) => (
 <DragAndDropContext.Provider value={{ DragAndDropManager: new DragAndDropManager() }}>
  {children}
 </DragAndDropContext.Provider>
)

可以看到傳遞消息是用react的Context的api去實現的,重點就是這個DragAndDropManager,我們看下實現

export default class DragAndDropManager {

 constructor() {
  this.active = null
  this.subscriptions = []
  this.id = -1
 }

 setActive(activeProps) {
  this.active = activeProps
  this.subscriptions.forEach((subscription) => subscription.callback())
 }

 subscribe(callback) {
  this.id += 1
  this.subscriptions.push({
   callback,
   id: this.id,
  })

  return this.id
 }

 unsubscribe(id) {
  this.subscriptions = this.subscriptions.filter((sub) => sub.id !== id)
 }
}

setActive的作用是用來記錄當前drag的元素是哪個,useDrag里面會用到,我們在看useDrag的hooks實現的時候就會明白只要調用setActive方法把drag的dom元素傳進去,是不是就知道當前拖拽的元素是哪個了呢。

除此之外,我還增加了訂閱事件的api,subscribe,目前我并沒有使用它,本次示例里你可以忽略這部分,知道可以添加訂閱事件就行。

接著我們看看,useDrag的使用,DragElement的實現如下:

function DragElement() {
 const input = useRef(null)
 const hanleDrag = useDrag({
  ref: input,
  collection: {}, // 這里可以填寫任意你想傳遞給drop元素的消息,后面會通過參數的形式傳遞給drop元素
 })
 return (
  <div ref={input}>
   <h2 role="button" onClick={hanleDrag}>
    drag元素
   </h2>
  </div>
 )
}

我們就來看下useDrag的實現,非常簡單

export default function useDrag(props) {

 const { DragAndDropManager } = useContext(DragAndDropContext)
 
 const handleDragStart = (e) => {
  DragAndDropManager.setActive(props.collection)
  if (e.dataTransfer !== undefined) {
   e.dataTransfer.effectAllowed = "move"
   e.dataTransfer.dropEffect = "move"
   e.dataTransfer.setData("text/plain", "drag") // firefox fix
  }
  if (props.onDragStart) {
   props.onDragStart(DragAndDropManager.active)
  }
 }
 
 useEffect(() => {
  if (!props.ref) return () => {}
  const {
   ref: { current },
  } = props
  if (current) {
   current.setAttribute("draggable", true)
   current.addEventListener("dragstart", handleDragStart)
  }
  return () => {
   current.removeEventListener("dragstart", handleDragStart)
  }
 }, [props.ref.current])

 return handleDragStart
}

useDrag做的事情非常簡單,

  • 首先通過useContext,來把獲取最外層store的數據,也就是上面代碼的DragAndDropManager

  • 在useEffect里面,如果外界傳入了ref,就將這個dom元素的屬性draggable設為true,也就是可拖拽狀態

  • 然后給這個元素綁定dragstart事件,注意了,銷毀組件的時候我們要移除事件,以防內存泄漏

  • handleDragStart事件首先把外界傳的props.collection更新到我們的外界倉庫里,這樣每一個要drag,也就是拖拽的元素都可以將我們useDrag中傳是入的useDrag({collection: {}})信息,通過DragAndDropManager.setActive(props.collection)的方式,傳入到外界的store

  • 接著我們dataTransder屬性上做一些事,目的是設置元素的拖拽屬性為move,并且為了兼容firefox做了處理。

  • 最后每當出發drag事件的時候,外界傳入的onDragStart事件也會觸發,并且我們將store里的數據傳入進去

其中,useDrop的使用,DropElement的實現如下:

function DropElement(props: any): any {
 const input = useRef(null)
 useDrop({
  ref: input,
  // e代表dragOver事件發生時,正在被over的元素的event對象
  // collection是store存儲的數據
  // showAfter是表示,是否鼠標拖拽元素時,鼠標經過drop元素的上方(上方就是上半邊,下方就是下半邊)
  onDragOver: (e, collection, showAfter) => {
  // 如果經過上半邊,drop元素的上邊框就是紅色
   if (!showAfter) {
    input.current.style = "border-bottom: none;border-top: 1px solid red"
   } else {
    // 如果經過下半邊,drop元素的上邊框就是紅色
    input.current.style = "border-top: none;border-bottom: 1px solid red"
   }
  },
  // 如果在drop元素上放開鼠標,則樣式清空
  onDrop: () => {
   input.current.style = ""
  },
  // 如果在離開drop元素,則樣式清空
  onDragLeave: () => {
   input.current.style = ""
  },
 })
 return (
  <div>
   <h2 ref={input}>drop元素</h2>
  </div>
 )
}

最后,我們來看看useDrop的實現

export default function useDrop(props) {
// 獲取最外層store里的數據
 const { DragAndDropManager } = useContext(DragAndDropContext)
 const handleDragOver = (e) => {
 // e就是拖拽的event對象
  e.preventDefault()
  // getBoundingClientRect的圖請看下面
  const overElementHeight = e.currentTarget.getBoundingClientRect().height / 2
  const overElementTopOffset = e.currentTarget.getBoundingClientRect().top
  // clientY就是鼠標到瀏覽器頁面可視區域的最頂端的距離
  const mousePositionY = e.clientY
  // mousePositionY - overElementTopOffset就是鼠標在元素內部到元素border-top的距離
  const showAfter = mousePositionY - overElementTopOffset > overElementHeight
  if (props.onDragOver) {
   props.onDragOver(e, DragAndDropManager.active, showAfter)
  }
 }
 // drop事件
 const handledDop = (e: React.DragEvent) => {
  e.preventDefault()

  if (props.onDrop) {
   props.onDrop(DragAndDropManager.active)
  }
 }
 // dragLeave事件
 const handledragLeave = (e: React.DragEvent) => {
  e.preventDefault()

  if (props.onDragLeave) {
   props.onDragLeave(DragAndDropManager.active)
  }
 }
  // 注冊事件,注意銷毀組件時要注銷事件,避免內存泄露
 useEffect(() => {
  if (!props.ref) return () => {}
  const {
   ref: { current },
  } = props
  if (current) {
   current.addEventListener("dragover", handleDragOver)
   current.addEventListener("drop", handledDop)
   current.addEventListener("dragleave", handledragLeave)
  }
  return () => {
   current.removeEventListener("dragover", handleDragOver)
   current.removeEventListener("drop", handledDop)
   current.removeEventListener("dragleave", handledragLeave)
  }
 }, [props.ref.current])
}

getBoundingClientRect的api圖解:

rectObject = object.getBoundingClientRect();

rectObject.top:元素上邊到視窗上邊的距離;

rectObject.right:元素右邊到視窗左邊的距離;

rectObject.bottom:元素下邊到視窗上邊的距離;

rectObject.left:元素左邊到視窗左邊的距離;

怎么實現react拖拽hooks

感謝你能夠認真閱讀完這篇文章,希望小編分享的“怎么實現react拖拽hooks”這篇文章對大家有幫助,同時也希望大家多多支持億速云,關注億速云行業資訊頻道,更多相關知識等著你來學習!

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

科尔| 雅安市| 龙山县| 繁昌县| 马山县| 鄂温| 昭苏县| 应城市| 日喀则市| 大足县| 高安市| 乌兰察布市| 承德县| 独山县| 永春县| 剑阁县| 宿迁市| 黎平县| 保德县| 仙桃市| 临潭县| 通州区| 武城县| 鹤峰县| 丰顺县| 景洪市| 应用必备| 揭东县| 成武县| 城固县| 兴义市| 江川县| 枞阳县| 公主岭市| 通州市| 浦东新区| 诸城市| 望谟县| 将乐县| 和田市| 克东县|