import { useRef, useEffect, useState } from 'react' /** * 可拖拽组件 */ export const Draggable = (props) => { const { children } = props 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) { // 兼容IE浏览器 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 // 计算鼠标指针的x轴距离 event.my = event.pageY || event.clientY + document.body.scrollTop // 计算鼠标指针的y轴距离 return event // 返回标准化的事件对象 } // 定义鼠标事件处理函数 // document.pointerdown = start 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) 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' checkInWindow() } } /** * 鼠标是否在可拖拽区域内 * @param {*} event * @returns */ 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 if (horizontal && vertical) { return true } return false } /** * 若超出窗口则吸附。 */ 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 = 0 } if (offsetTop > (clientHeight - offsetHeight)) { drag.firstElementChild.style.top = clientHeight - offsetHeight + 'px' } if (offsetLeft < 0) { drag.firstElementChild.style.left = 0 } if (offsetLeft > (clientWidth - offsetWidth)) { drag.firstElementChild.style.left = clientWidth - offsetWidth + 'px' } } } window.addEventListener('resize', checkInWindow) return () => { return () => { window.removeEventListener('resize', checkInWindow) cancelAnimationFrame(rafRef.current) } } }, []) return
{children}
} Draggable.defaultProps = { left: 0, top: 0 }