激情久久久_欧美视频区_成人av免费_不卡视频一二三区_欧美精品在欧美一区二区少妇_欧美一区二区三区的

服務(wù)器之家:專注于服務(wù)器技術(shù)及軟件下載分享
分類導(dǎo)航

node.js|vue.js|jquery|angularjs|React|json|js教程|

香港云服务器
服務(wù)器之家 - 編程語(yǔ)言 - JavaScript - React - 一百多行代碼實(shí)現(xiàn)react拖拽hooks

一百多行代碼實(shí)現(xiàn)react拖拽hooks

2022-02-20 17:28孟祥_成都 React

這篇文章主要介紹了一百多行代碼實(shí)現(xiàn)react拖拽hooks,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧

前言

源碼總共也就一百多行,看完這個(gè)大致可以理解一些成熟的react拖拽庫(kù)的實(shí)現(xiàn)思路,比如react-dnd,然后你上手這些庫(kù)的時(shí)候就非??炝?。

使用hooks實(shí)現(xiàn)的大致效果動(dòng)圖如下:

一百多行代碼實(shí)現(xiàn)react拖拽hooks

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

  • dragstart:用戶開始拖拉時(shí),在被拖拉的節(jié)點(diǎn)上觸發(fā),該事件的target屬性是被拖拉的節(jié)點(diǎn)。
  • dragenter:拖拉進(jìn)入當(dāng)前節(jié)點(diǎn)時(shí),在當(dāng)前節(jié)點(diǎn)上觸發(fā)一次,該事件的target屬性是當(dāng)前節(jié)點(diǎn)。通常應(yīng)該在這個(gè)事件的監(jiān)聽函數(shù)中,指定是否允許在當(dāng)前節(jié)點(diǎn)放下(drop)拖拉的數(shù)據(jù)。如果當(dāng)前節(jié)點(diǎn)沒有該事件的監(jiān)聽函數(shù),或者監(jiān)聽函數(shù)不執(zhí)行任何操作,就意味著不允許在當(dāng)前節(jié)點(diǎn)放下數(shù)據(jù)。在視覺上顯示拖拉進(jìn)入當(dāng)前節(jié)點(diǎn),也是在這個(gè)事件的監(jiān)聽函數(shù)中設(shè)置。
  • dragover:拖拉到當(dāng)前節(jié)點(diǎn)上方時(shí),在當(dāng)前節(jié)點(diǎn)上持續(xù)觸發(fā)(相隔幾百毫秒),該事件的target屬性是當(dāng)前節(jié)點(diǎn)。該事件與dragenter事件的區(qū)別是,dragenter事件在進(jìn)入該節(jié)點(diǎn)時(shí)觸發(fā),然后只要沒有離開這個(gè)節(jié)點(diǎn),dragover事件會(huì)持續(xù)觸發(fā)。
  • dragleave:拖拉操作離開當(dāng)前節(jié)點(diǎn)范圍時(shí),在當(dāng)前節(jié)點(diǎn)上觸發(fā),該事件的target屬性是當(dāng)前節(jié)點(diǎn)。如果要在視覺上顯示拖拉離開操作當(dāng)前節(jié)點(diǎn),就在這個(gè)事件的監(jiān)聽函數(shù)中設(shè)置。

使用方法 + 源碼講解

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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的組件傳遞消息,比如當(dāng)前拖拽的元素是那個(gè)dom,或者你想要其他信息都可以往里面加,我們看看它的實(shí)現(xiàn)。

?
1
2
3
4
5
6
const DragAndDropContext = React.createContext({ DragAndDropManager: {} });
const DragAndDrop = ({ children }) => (
 <DragAndDropContext.Provider value={{ DragAndDropManager: new DragAndDropManager() }}>
  {children}
 </DragAndDropContext.Provider>
)

可以看到傳遞消息是用react的Context的api去實(shí)現(xiàn)的,重點(diǎn)就是這個(gè)DragAndDropManager,我們看下實(shí)現(xiàn)

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
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的作用是用來記錄當(dāng)前drag的元素是哪個(gè),useDrag里面會(huì)用到,我們?cè)诳磚seDrag的hooks實(shí)現(xiàn)的時(shí)候就會(huì)明白只要調(diào)用setActive方法把drag的dom元素傳進(jìn)去,是不是就知道當(dāng)前拖拽的元素是哪個(gè)了呢。

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

接著我們看看,useDrag的使用,DragElement的實(shí)現(xiàn)如下:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
function DragElement() {
 const input = useRef(null)
 const hanleDrag = useDrag({
  ref: input,
  collection: {}, // 這里可以填寫任意你想傳遞給drop元素的消息,后面會(huì)通過參數(shù)的形式傳遞給drop元素
 })
 return (
  <div ref={input}>
   <h1 role="button" onClick={hanleDrag}>
    drag元素
   </h1>
  </div>
 )
}

我們就來看下useDrag的實(shí)現(xiàn),非常簡(jiǎn)單

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
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做的事情非常簡(jiǎn)單,

  • 首先通過useContext,來把獲取最外層store的數(shù)據(jù),也就是上面代碼的DragAndDropManager
  • 在useEffect里面,如果外界傳入了ref,就將這個(gè)dom元素的屬性draggable設(shè)為true,也就是可拖拽狀態(tài)
  • 然后給這個(gè)元素綁定dragstart事件,注意了,銷毀組件的時(shí)候我們要移除事件,以防內(nèi)存泄漏
  • handleDragStart事件首先把外界傳的props.collection更新到我們的外界倉(cāng)庫(kù)里,這樣每一個(gè)要drag,也就是拖拽的元素都可以將我們useDrag中傳是入的useDrag({collection: {}})信息,通過DragAndDropManager.setActive(props.collection)的方式,傳入到外界的store
  • 接著我們dataTransder屬性上做一些事,目的是設(shè)置元素的拖拽屬性為move,并且為了兼容firefox做了處理。
  • 最后每當(dāng)出發(fā)drag事件的時(shí)候,外界傳入的onDragStart事件也會(huì)觸發(fā),并且我們將store里的數(shù)據(jù)傳入進(jìn)去

其中,useDrop的使用,DropElement的實(shí)現(xiàn)如下:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
function DropElement(props: any): any {
 const input = useRef(null)
 useDrop({
  ref: input,
  // e代表dragOver事件發(fā)生時(shí),正在被over的元素的event對(duì)象
  // collection是store存儲(chǔ)的數(shù)據(jù)
  // showAfter是表示,是否鼠標(biāo)拖拽元素時(shí),鼠標(biāo)經(jīng)過drop元素的上方(上方就是上半邊,下方就是下半邊)
  onDragOver: (e, collection, showAfter) => {
  // 如果經(jīng)過上半邊,drop元素的上邊框就是紅色
   if (!showAfter) {
    input.current.style = "border-bottom: none;border-top: 1px solid red"
   } else {
    // 如果經(jīng)過下半邊,drop元素的上邊框就是紅色
    input.current.style = "border-top: none;border-bottom: 1px solid red"
   }
  },
  // 如果在drop元素上放開鼠標(biāo),則樣式清空
  onDrop: () => {
   input.current.style = ""
  },
  // 如果在離開drop元素,則樣式清空
  onDragLeave: () => {
   input.current.style = ""
  },
 })
 return (
  <div>
   <h1 ref={input}>drop元素</h1>
  </div>
 )
}

最后,我們來看看useDrop的實(shí)現(xiàn)

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
export default function useDrop(props) {
// 獲取最外層store里的數(shù)據(jù)
 const { DragAndDropManager } = useContext(DragAndDropContext)
 const handleDragOver = (e) => {
 // e就是拖拽的event對(duì)象
  e.preventDefault()
  // getBoundingClientRect的圖請(qǐng)看下面
  const overElementHeight = e.currentTarget.getBoundingClientRect().height / 2
  const overElementTopOffset = e.currentTarget.getBoundingClientRect().top
  // clientY就是鼠標(biāo)到瀏覽器頁(yè)面可視區(qū)域的最頂端的距離
  const mousePositionY = e.clientY
  // mousePositionY - overElementTopOffset就是鼠標(biāo)在元素內(nèi)部到元素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)
  }
 }
  // 注冊(cè)事件,注意銷毀組件時(shí)要注銷事件,避免內(nèi)存泄露
 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:元素左邊到視窗左邊的距離;

一百多行代碼實(shí)現(xiàn)react拖拽hooks

到此這篇關(guān)于一百多行代碼實(shí)現(xiàn)react拖拽hooks的文章就介紹到這了,更多相關(guān)react拖拽hooks內(nèi)容請(qǐng)搜索服務(wù)器之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持服務(wù)器之家!

原文鏈接:https://juejin.cn/post/6941786077237624840

延伸 · 閱讀

精彩推薦
760
主站蜘蛛池模板: 国产91精品久久久 | xxxxhd86日本护士hd | 欧美a区 | 黄色18网站| 曰批全过程40分钟免费视频多人 | 精品二区在线观看 | 久久精品一区二区三区不卡牛牛 | 成人精品一区二区 | 精品国产一区二区三 | 91网址在线播放 | 亚洲精品成人18久久久久 | 国产一极毛片 | 免费一级特黄欧美大片勹久久网 | 夜班护士在线观看 | 日本一区二区三区精品 | 毛片在线视频免费观看 | 成人国产精品色哟哟 | 成人资源在线 | 日本羞羞影院 | 国产精品久久久久影院老司 | 黄色一级毛片免费看 | 国产女同疯狂激烈互摸 | 黄色毛片免费视频 | 亚洲成人综合网站 | 精品一区二区三区在线观看视频 | 国产老师做www爽爽爽视频 | 一日本道久久久精品国产 | 亚洲小视频在线观看,com | 男女羞羞视频在线免费观看 | 黄色免费不卡视频 | 久久久久中精品中文字幕19 | 奇米888一区二区三区 | 黄色片免费在线 | 国产一区二区高清在线 | 欧美又黄又嫩大片a级 | 精品一区二区久久久久久久网精 | 国产午夜小视频 | 大胆在线日本aⅴ免费视频 永久免费毛片 | 欧美精品一区二区免费 | 亚洲网在线观看 | 极品大长腿啪啪高潮露脸 |