基本思路
如果要设置物体拖拽,那么必须使用三个事件,并且这三个事件的使用顺序不能颠倒。1.onmousedown:鼠标按下事件2.onmousemove:鼠标移动事件3.onmouseup:鼠标抬起事件
重点:
1、一定要绝对定位,脱离文档流才可以移动。
2、绑定拖拽的元素,移动和鼠标松开后是对document的绑定,因为移动的是整个div。
3、点击:a= 获取当前鼠标坐标、b =div距浏览器距离、c = 鼠标在div内部距离=a-b。
移动:通过 a - c 建立鼠标与div的关系,防止鼠标超出div。基本思路:
拖拽状态 = 0鼠标在元素上按下的时候{
拖拽状态 = 1
记录下鼠标的x和y坐标
记录下元素的x和y坐标
}鼠标在元素上移动的时候{
如果拖拽状态是0就什么也不做。
如果拖拽状态是1,那么
元素y = 现在鼠标y - 原来鼠标y + 原来元素y
元素x = 现在鼠标x - 原来鼠标x + 原来元素x }鼠标在任何时候放开的时候{
拖拽状态 = 0
}
//draggable.jsximport React, {useState, useCallback, useMemo, useEffect} from 'react';const POSITION = {x: 0, y: 0};const Draggable = ({children, id, onDrag, onDragEnd}) => {const [state, setState] = useState({//是否平移,保存拖动状态isDragging: false,//之前的位置origin: POSITION,// 平移的位置translation: POSITION});//鼠标按下时const handleMouseDown = useCallback(({clientX, clientY}) => {//计算出鼠标当前的坐标,并保存记录下来setState(state => ({...state,isDragging: true,origin: {x: clientX, y: clientY}}));}, []);//鼠标移动时const handleMouseMove = useCallback(({clientX, clientY}) => {// 算出平移量,平移的距离x,y坐标,当前的x,y轴坐标减去初始的x,y轴的坐标const translation = {x: clientX - state.origin.x, y: clientY - state.origin.y};//将现在的平移量设置进statesetState(state => ({...state,translation}));// 触发拖动事件onDrag({translation, id});}, [state.origin, onDrag, id]);//鼠标抬起,结束拖动状态const handleMouseUp = useCallback(() => {setState(state => ({...state,isDragging: false}));onDragEnd();}, [onDragEnd]);useEffect(() => {//调用原生事件if (state.isDragging) {window.addEventListener('mousemove', handleMouseMove);window.addEventListener('mouseup', handleMouseUp);} else {window.removeEventListener('mousemove', handleMouseMove);window.removeEventListener('mouseup', handleMouseUp);setState(state => ({...state, translation: {x: 0, y: 0}}));}}, [state.isDragging, handleMouseMove, handleMouseUp]);//设置属性const styles = useMemo(() => ({cursor: state.isDragging ? '-webkit-grabbing' : '-webkit-grab',transform: `translate(${state.translation.x}px, ${state.translation.y}px)`,transition: state.isDragging ? 'none' : 'transform 500ms',zIndex: state.isDragging ? 2 : 1,position: state.isDragging ? 'absolute' : 'relative'}), [state.isDragging, state.translation]);return (<div style={styles} onMouseDown={handleMouseDown}>{children}</div>);};export default Draggable
//about.jsximport React, {useState, useCallback} from 'react';import styled from 'styled-components';import {range, inRange} from 'lodash';import Draggable from '../Draggable';const MAX = 5;const HEIGHT = 100;const App = () => {const items = range(MAX);//设置state保存三个状态const [state, setState] = useState({//初始数组order: items,//拖动状态下的数组dragOrder: items, // items order while draggingdraggedIndex: null});const [imgUrl]=useState(['https://ss0.bdstatic.com/70cFvHSh_Q1YnxGkpoWK1HF6hhy/it/u=3388269861,2982194587&fm=26&gp=0.jpg','https://ss1.bdstatic.com/70cFuXSh_Q1YnxGkpoWK1HF6hhy/it/u=2357545491,277280726&fm=15&gp=0.jpg','https://ss0.bdstatic.com/70cFvHSh_Q1YnxGkpoWK1HF6hhy/it/u=1167992334,1034210916&fm=26&gp=0.jpg','https://ss1.bdstatic.com/70cFuXSh_Q1YnxGkpoWK1HF6hhy/it/u=2850066628,804265687&fm=26&gp=0.jpg','https://ss0.bdstatic.com/70cFvHSh_Q1YnxGkpoWK1HF6hhy/it/u=1332205634,147092206&fm=26&gp=0.jpg'])const handleDrag = useCallback(({translation, id}) => {//平移的距离除以高度得到平移了多少个滑块const delta = Math.round(translation.y / HEIGHT);//拿到当前所拖动砖块的索引const index = state.order.indexOf(id);//移出当前所在的滑块,根据移动的id不等于数组中的index,得到新的数组不包含当前所在滑块const dragOrder = state.order.filter(index => index !== id);//如果拖拽的滑块数加当前滑块的索引不在总滑块数的范围内,结束任务if (!inRange(index + delta, 0, items.length)) {return;}//将改拖拽的滑块插入到当前改变的总滑块中dragOrder.splice(index + delta, 0, id);//将修改后得数组索引以及索引保存到当前的state状态中setState(state => ({...state,draggedIndex: id,dragOrder}));}, [state.order, items.length]);// 结束拖动状态const handleDragEnd = useCallback(() => {setState(state => ({...state,order: state.dragOrder,draggedIndex: null}));}, []);return (<Container>{items.map(index => {//这里的状态当前拖动的索引等于数组的索引const isDragging = state.draggedIndex === index;//这里的高度加10是加上了外边距//初始高度const top = state.dragOrder.indexOf(index) * (HEIGHT + 10);//拖拽后的高度const draggedTop = state.order.indexOf(index) * (HEIGHT + 10);return (<Draggablekey={index}id={index}onDrag={handleDrag}onDragEnd={handleDragEnd}><RectisDragging={isDragging}top={isDragging ? draggedTop : top}index={index}imgUrl={imgUrl}></Rect></Draggable>);})}</Container>);};export default App;const Container = styled.div`width: 100vw;min-height: 100vh;`;const Rect = styled.div.attrs(props => ({style: {transition: props.isDragging ? 'none' : 'all 500ms'}}))`width: 300px;user-select: none;height: ${HEIGHT}px;background: #88D0FF;box-shadow: 0 5px 10px rgba(0, 0, 0, 0.5);display: flex;align-items: center;justify-content: center;position: absolute;top: ${({top}) => 100 + top}px;left: calc(50vw - 150px);font-size: 20px;color: #fff;border-radius:10px;background:url(${props=>props.imgUrl[props.index]});background-repeat:no-repeat;background-size: cover;position: absolute;overflow: hidden;`
app.jsimport Draggable from './components/About';function App() {return (<div className="App"><Draggable onDrag={console.log} id="uniqueId"></Draggable></div>);}export default App;
下载
yarn add lodashyarn add styled-components

