基本思路
如果要设置物体拖拽,那么必须使用三个事件,并且这三个事件的使用顺序不能颠倒。
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.jsx
import 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};
//将现在的平移量设置进state
setState(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.jsx
import 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 dragging
draggedIndex: 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 (
<Draggable
key={index}
id={index}
onDrag={handleDrag}
onDragEnd={handleDragEnd}
>
<Rect
isDragging={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.js
import Draggable from './components/About';
function App() {
return (
<div className="App">
<Draggable onDrag={console.log} id="uniqueId">
</Draggable>
</div>
);
}
export default App;
下载
yarn add lodash
yarn add styled-components