基本思路

  1. 如果要设置物体拖拽,那么必须使用三个事件,并且这三个事件的使用顺序不能颠倒。
  2. 1.onmousedown:鼠标按下事件
  3. 2.onmousemove:鼠标移动事件
  4. 3.onmouseup:鼠标抬起事件

重点:

1、一定要绝对定位,脱离文档流才可以移动。

2、绑定拖拽的元素,移动和鼠标松开后是对document的绑定,因为移动的是整个div。

3、点击:a= 获取当前鼠标坐标、b =div距浏览器距离、c = 鼠标在div内部距离=a-b。

  1. 移动:通过 a - c 建立鼠标与div的关系,防止鼠标超出div

基本思路:

拖拽状态 = 0鼠标在元素上按下的时候{
拖拽状态 = 1
记录下鼠标的x和y坐标
记录下元素的x和y坐标
}

鼠标在元素上移动的时候{
如果拖拽状态是0就什么也不做。
如果拖拽状态是1,那么
元素y = 现在鼠标y - 原来鼠标y + 原来元素y
元素x = 现在鼠标x - 原来鼠标x + 原来元素x }

鼠标在任何时候放开的时候{
拖拽状态 = 0
}

  1. //draggable.jsx
  2. import React, {useState, useCallback, useMemo, useEffect} from 'react';
  3. const POSITION = {x: 0, y: 0};
  4. const Draggable = ({children, id, onDrag, onDragEnd}) => {
  5. const [state, setState] = useState({
  6. //是否平移,保存拖动状态
  7. isDragging: false,
  8. //之前的位置
  9. origin: POSITION,
  10. // 平移的位置
  11. translation: POSITION
  12. });
  13. //鼠标按下时
  14. const handleMouseDown = useCallback(({clientX, clientY}) => {
  15. //计算出鼠标当前的坐标,并保存记录下来
  16. setState(state => ({
  17. ...state,
  18. isDragging: true,
  19. origin: {x: clientX, y: clientY}
  20. }));
  21. }, []);
  22. //鼠标移动时
  23. const handleMouseMove = useCallback(({clientX, clientY}) => {
  24. // 算出平移量,平移的距离x,y坐标,当前的x,y轴坐标减去初始的x,y轴的坐标
  25. const translation = {x: clientX - state.origin.x, y: clientY - state.origin.y};
  26. //将现在的平移量设置进state
  27. setState(state => ({
  28. ...state,
  29. translation
  30. }));
  31. // 触发拖动事件
  32. onDrag({translation, id});
  33. }, [state.origin, onDrag, id]);
  34. //鼠标抬起,结束拖动状态
  35. const handleMouseUp = useCallback(() => {
  36. setState(state => ({
  37. ...state,
  38. isDragging: false
  39. }));
  40. onDragEnd();
  41. }, [onDragEnd]);
  42. useEffect(() => {
  43. //调用原生事件
  44. if (state.isDragging) {
  45. window.addEventListener('mousemove', handleMouseMove);
  46. window.addEventListener('mouseup', handleMouseUp);
  47. } else {
  48. window.removeEventListener('mousemove', handleMouseMove);
  49. window.removeEventListener('mouseup', handleMouseUp);
  50. setState(state => ({...state, translation: {x: 0, y: 0}}));
  51. }
  52. }, [state.isDragging, handleMouseMove, handleMouseUp]);
  53. //设置属性
  54. const styles = useMemo(() => ({
  55. cursor: state.isDragging ? '-webkit-grabbing' : '-webkit-grab',
  56. transform: `translate(${state.translation.x}px, ${state.translation.y}px)`,
  57. transition: state.isDragging ? 'none' : 'transform 500ms',
  58. zIndex: state.isDragging ? 2 : 1,
  59. position: state.isDragging ? 'absolute' : 'relative'
  60. }), [state.isDragging, state.translation]);
  61. return (
  62. <div style={styles} onMouseDown={handleMouseDown}>
  63. {children}
  64. </div>
  65. );
  66. };
  67. export default Draggable
  1. //about.jsx
  2. import React, {useState, useCallback} from 'react';
  3. import styled from 'styled-components';
  4. import {range, inRange} from 'lodash';
  5. import Draggable from '../Draggable';
  6. const MAX = 5;
  7. const HEIGHT = 100;
  8. const App = () => {
  9. const items = range(MAX);
  10. //设置state保存三个状态
  11. const [state, setState] = useState({
  12. //初始数组
  13. order: items,
  14. //拖动状态下的数组
  15. dragOrder: items, // items order while dragging
  16. draggedIndex: null
  17. });
  18. const [imgUrl]=useState([
  19. 'https://ss0.bdstatic.com/70cFvHSh_Q1YnxGkpoWK1HF6hhy/it/u=3388269861,2982194587&fm=26&gp=0.jpg',
  20. 'https://ss1.bdstatic.com/70cFuXSh_Q1YnxGkpoWK1HF6hhy/it/u=2357545491,277280726&fm=15&gp=0.jpg',
  21. 'https://ss0.bdstatic.com/70cFvHSh_Q1YnxGkpoWK1HF6hhy/it/u=1167992334,1034210916&fm=26&gp=0.jpg',
  22. 'https://ss1.bdstatic.com/70cFuXSh_Q1YnxGkpoWK1HF6hhy/it/u=2850066628,804265687&fm=26&gp=0.jpg',
  23. 'https://ss0.bdstatic.com/70cFvHSh_Q1YnxGkpoWK1HF6hhy/it/u=1332205634,147092206&fm=26&gp=0.jpg'
  24. ])
  25. const handleDrag = useCallback(({translation, id}) => {
  26. //平移的距离除以高度得到平移了多少个滑块
  27. const delta = Math.round(translation.y / HEIGHT);
  28. //拿到当前所拖动砖块的索引
  29. const index = state.order.indexOf(id);
  30. //移出当前所在的滑块,根据移动的id不等于数组中的index,得到新的数组不包含当前所在滑块
  31. const dragOrder = state.order.filter(index => index !== id);
  32. //如果拖拽的滑块数加当前滑块的索引不在总滑块数的范围内,结束任务
  33. if (!inRange(index + delta, 0, items.length)) {
  34. return;
  35. }
  36. //将改拖拽的滑块插入到当前改变的总滑块中
  37. dragOrder.splice(index + delta, 0, id);
  38. //将修改后得数组索引以及索引保存到当前的state状态中
  39. setState(state => ({
  40. ...state,
  41. draggedIndex: id,
  42. dragOrder
  43. }));
  44. }, [state.order, items.length]);
  45. // 结束拖动状态
  46. const handleDragEnd = useCallback(() => {
  47. setState(state => ({
  48. ...state,
  49. order: state.dragOrder,
  50. draggedIndex: null
  51. }));
  52. }, []);
  53. return (
  54. <Container>
  55. {items.map(index => {
  56. //这里的状态当前拖动的索引等于数组的索引
  57. const isDragging = state.draggedIndex === index;
  58. //这里的高度加10是加上了外边距
  59. //初始高度
  60. const top = state.dragOrder.indexOf(index) * (HEIGHT + 10);
  61. //拖拽后的高度
  62. const draggedTop = state.order.indexOf(index) * (HEIGHT + 10);
  63. return (
  64. <Draggable
  65. key={index}
  66. id={index}
  67. onDrag={handleDrag}
  68. onDragEnd={handleDragEnd}
  69. >
  70. <Rect
  71. isDragging={isDragging}
  72. top={isDragging ? draggedTop : top}
  73. index={index}
  74. imgUrl={imgUrl}
  75. >
  76. </Rect>
  77. </Draggable>
  78. );
  79. })}
  80. </Container>
  81. );
  82. };
  83. export default App;
  84. const Container = styled.div`
  85. width: 100vw;
  86. min-height: 100vh;
  87. `;
  88. const Rect = styled.div.attrs(props => ({
  89. style: {
  90. transition: props.isDragging ? 'none' : 'all 500ms'
  91. }
  92. }))`
  93. width: 300px;
  94. user-select: none;
  95. height: ${HEIGHT}px;
  96. background: #88D0FF;
  97. box-shadow: 0 5px 10px rgba(0, 0, 0, 0.5);
  98. display: flex;
  99. align-items: center;
  100. justify-content: center;
  101. position: absolute;
  102. top: ${({top}) => 100 + top}px;
  103. left: calc(50vw - 150px);
  104. font-size: 20px;
  105. color: #fff;
  106. border-radius:10px;
  107. background:url(${props=>props.imgUrl[props.index]});
  108. background-repeat:no-repeat;
  109. background-size: cover;
  110. position: absolute;
  111. overflow: hidden;
  112. `
  1. app.js
  2. import Draggable from './components/About';
  3. function App() {
  4. return (
  5. <div className="App">
  6. <Draggable onDrag={console.log} id="uniqueId">
  7. </Draggable>
  8. </div>
  9. );
  10. }
  11. export default App;

下载

  1. yarn add lodash
  2. yarn add styled-components

效果演示

20210422_121012.mp4