forked from tangly1024/NotionNext
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathDraggable.js
146 lines (128 loc) · 4.19 KB
/
Draggable.js
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
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
import { useEffect, useRef, useState } from 'react'
/**
* 可拖拽组件
* @param {children} 渲染的子元素
* @param {stick} 是否要吸附
* @returns
*/
export const Draggable = ({ children, stick }) => {
const draggableRef = useRef(null)
const rafRef = useRef(null)
const [moving, setMoving] = useState(false)
let currentObj, offsetX, offsetY
useEffect(() => {
const draggableElements = document.getElementsByClassName('draggable')
function e(event) {
if (!event) {
event = window.event
event.target = event.srcElement
event.layerX = event.offsetX
event.layerY = event.offsetY
}
if (event.type === 'touchstart' || event.type === 'touchmove') {
event.clientX = event.touches[0].clientX
event.clientY = event.touches[0].clientY
}
event.mx = event.pageX || event.clientX + document.body.scrollLeft
event.my = event.pageY || event.clientY + document.body.scrollTop
return event
}
document.onmousedown = start
document.ontouchstart = start
function start(event) {
if (!draggableElements) return
event = e(event)
for (const drag of draggableElements) {
if (inDragBox(event, drag)) {
currentObj = drag.firstElementChild
}
}
if (currentObj) {
if (event.type === 'touchstart') {
event.preventDefault()
document.documentElement.style.overflow = 'hidden'
}
setMoving(true)
offsetX = event.mx - currentObj.offsetLeft
offsetY = event.my - currentObj.offsetTop
document.onmousemove = move
document.ontouchmove = move
document.onmouseup = stop
document.ontouchend = stop
}
}
function move(event) {
event = e(event)
rafRef.current = requestAnimationFrame(() => updatePosition(event))
}
const stop = event => {
event = e(event)
document.documentElement.style.overflow = 'auto'
cancelAnimationFrame(rafRef.current)
setMoving(false)
if (stick) {
checkInWindow() // 吸附逻辑
}
currentObj =
document.ontouchmove =
document.ontouchend =
document.onmousemove =
document.onmouseup =
null
}
const updatePosition = event => {
if (currentObj) {
const left = event.mx - offsetX
const top = event.my - offsetY
currentObj.style.left = left + 'px'
currentObj.style.top = top + 'px'
}
}
function inDragBox(event, drag) {
const { clientX, clientY } = event
const { offsetHeight, offsetWidth, offsetTop, offsetLeft } =
drag.firstElementChild
const horizontal =
clientX > offsetLeft && clientX < offsetLeft + offsetWidth
const vertical = clientY > offsetTop && clientY < offsetTop + offsetHeight
return horizontal && vertical
}
function checkInWindow() {
for (const drag of draggableElements) {
const { offsetHeight, offsetWidth, offsetTop, offsetLeft } =
drag.firstElementChild
const { clientHeight, clientWidth } = document.documentElement
if (offsetTop < 0) {
drag.firstElementChild.style.top = '0px'
}
if (offsetTop > clientHeight - offsetHeight) {
drag.firstElementChild.style.top = clientHeight - offsetHeight + 'px'
}
if (offsetLeft < 0) {
drag.firstElementChild.style.left = '0px'
}
if (offsetLeft > clientWidth - offsetWidth) {
drag.firstElementChild.style.left = clientWidth - offsetWidth + 'px'
}
if (stick === 'left') {
drag.firstElementChild.style.left = '0px'
} else if (stick === 'right') {
drag.firstElementChild.style.left = clientWidth - offsetWidth + 'px'
}
}
}
window.addEventListener('resize', checkInWindow)
return () => {
window.removeEventListener('resize', checkInWindow)
cancelAnimationFrame(rafRef.current)
}
}, [stick])
return (
<div
className={`draggable ${moving ? 'cursor-grabbing' : 'cursor-grab'} select-none`}
ref={draggableRef}>
{children}
</div>
)
}
Draggable.defaultProps = { left: 0, top: 0 }