• react 实现拖动元素


    demo使用create-react-app脚手架创建
    删除一些文件,创建一些文件后
    结构目录如下截图
    
    • 1
    • 2
    • 3

    在这里插入图片描述

    com/index
    import Movable from './move'
    import { useMove } from './move.hook'
    import * as Operations from './move.op'
    
    
    Movable.useMove = useMove
    Movable.Operations = Operations
    
    export default Movable
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    com/move
    import React, {forwardRef, memo} from "react"
    import { noop } from "../utils/noop"
    import {mouseTracker, touchTracker, moveTracker} from './move.utils';
    // forwardRef 将允许组件使用ref,将dom暴露给父组件; 返回一个可以接受ref属性的组件
    export const Move = forwardRef(({onBeginMove, onMove, onEndMove, ...props}, ref) =>  {
      const tracker = moveTracker(onBeginMove, onMove, onEndMove);
      const handleOnMouseDown = mouseTracker(tracker);
      return <div 
        {...props} 
        ref={ref}
        onMouseDown={handleOnMouseDown}
        className={`movable ${props.className}`}
        />
    })
    
    export default memo(Move)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    com/move.utils
    
    export const moveTracker = (onBeginMove, onMove, onEndMove) => {
      let initial = {};
      let previous = {};
    
      const event = e => ({
          ...e,
          cx: e.x - previous.x,
          cy: e.y - previous.y,
          dx: e.x - initial.x,
          dy: e.y - initial.y,
      });
    
      return {
          start: e => {
              initial = {x: e.x, y: e.y};
              previous = {...initial};
              onBeginMove(event(e));
          },
          move: e => {
              onMove(event(e));
              previous = {x: e.x, y: e.y};
          },
          end: e => {
              onEndMove(event(e));
          },
      }
    };
    
    export const mouseTracker = tracker => {
    
      const event = e => ({
          x: e.clientX,
          y: e.clientY,
          target: e.target,
          stopPropagation: () => e.stopPropagation(),
          preventDefault: () => e.preventDefault(),
      });
    
      const onMouseDown = e => {
          document.addEventListener('mousemove', onMouseMove);
          document.addEventListener('mouseup', onMouseUp);
          tracker.start(event(e));
      };
    
      const onMouseMove = e => {
          tracker.move(event(e));
      };
    
      const onMouseUp = e => {
          document.removeEventListener('mousemove', onMouseMove);
          document.removeEventListener('mouseup', onMouseUp);
          tracker.end(event(e));
      };
    
      return onMouseDown;
    };
    
    
    
    • 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
    com/move.hook
    import { useRef, useCallback } from "react";
    
    export const useMove = ops => {
      const shared = useRef({})
    
      const onBeginMove = useCallback(e => {
        ops.forEach(({onBeginMove}) => onBeginMove(e, shared.current));
      }, [ops])
    
      const onMove = useCallback(e => {
        ops.forEach(({onMove}) => onMove(e, shared.current));
      }, [ops])
    
      const onEndMove = useCallback(e => {
        ops.forEach(({onEndMove}) => onEndMove(e, shared.current));
      }, [ops])
    
      return {onBeginMove, onMove, onEndMove}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    com/move.op
    import { clamp } from "../utils/number";
    import { noop } from "../utils/noop";
    import { isEqual } from "../utils/object";
    
    export const createOp = handlers => ({
      onBeginMove: noop,
      onMove: noop,
      onEndMove: noop,
      ...handlers
    })
    
    
    export const move = m => createOp({
      onBeginMove: (e, shared) => {
        // getBoundingClientRect返回一个 DOMRect 对象,其提供了元素的大小及其相对于视口的位置。
        const { top, left } = m.current.getBoundingClientRect()
        shared.next = {top, left}
        shared.initial = {top, left}
      },
      onMove: ({dx, dy}, shared) => {
        const {left, top} = shared.initial
        shared.next = {
          left: left + dx,
          top: top + dy
        }
      }
    })
    
    export const update = onUpdate => createOp({
      onBeginMove: _update(onUpdate),
      onMove: _update(onUpdate),
      onEndMove: _update(onUpdate),
    });
    const _update = onUpdate => (e, shared) => {
      if (!isEqual(shared.prev, shared.next)) {
          onUpdate(shared.next);
          shared.prev = shared.next;
      }
    };
    
    • 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
    utils/number
    export const clamp = (num, min, max) => {
      return Math.min(Math.max(num, min), max)
    }
    ================================
    utils/noop
    export const noop = () => null;
    ================================
    utils/object
    
    const Types = {
      NUMBER: 'number',
      OBJECT: 'object',
      NULL: 'null',
      ARRAY: 'array',
      UNDEFINED: 'undefined',
      BOOLEAN: 'boolean',
      STRING: 'string',
      DATE: 'date',
    };
    const getType = v => Object.prototype.toString.call(v).slice(8, -1).toLowerCase();
    const isType = (v, ...types) => types.includes(getType(v));
    const isObject = v => isType(v, Types.OBJECT);
    const isArray = v => isType(v, Types.ARRAY);
    
    export const EqualityIterators = {
      SHALLOW: (a, b) => a === b,
      DEEP: (a, b, visited = []) => {
          if (visited.includes(a)) {
              return true;
          }
          if (a instanceof Object) {
              visited.push(a);
          }
          return isEqual(a, b, (a, b) => EqualityIterators.DEEP(a, b, visited))
      },
    };
    
    export const isEqual = (a, b, iterator = EqualityIterators.DEEP) => {
      if (a === b) {
          return true;
      }
    
      if (getType(a) !== getType(b)) {
          return false;
      }
    
      if (isObject(a) && Object.keys(a).length === Object.keys(b).length) {
          return Object.keys(a).every(key => iterator(a[key], b[key]));
      }
    
      if (isArray(a) && a.length === b.length) {
          return a.every((item, i) => iterator(a[i], b[i]))
      }
    
      return false;
    };
    
    • 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
    App.js
    import { useMemo, useRef, useState } from "react";
    import Movable from "./com";
    
    const {move, update} = Movable.Operations
    
    function App() {
      const ref = useRef()
      const ref2 = useRef()
      const [p, setP] = useState({})
      const [p2, setP2] = useState({})
      const props = Movable.useMove(useMemo(() => [
        move(ref),
        update(setP)
      ], []))
      const props2 = Movable.useMove(useMemo(() => [
        move(ref2),
        update(setP2)
      ], []))
      return (
        <>
        <Movable {...props} ref={ref} style={p}>
              拖我
        </Movable>
        <Movable {...props2} ref={ref2} style={p2}>
              拖我2
        </Movable>
        </>
      );
    }
    
    export default App;
    
    
    • 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
    src/index
    import React from 'react';
    import ReactDOM from 'react-dom/client';
    import './index.css';
    import App from './App';
    import reportWebVitals from './reportWebVitals';
    
    const root = ReactDOM.createRoot(document.getElementById('root'));
    root.render(
      <React.StrictMode>
        <App />
      </React.StrictMode>
    );
    
    // If you want to start measuring performance in your app, pass a function
    // to log results (for example: reportWebVitals(console.log))
    // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
    reportWebVitals();
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    src/index.css
    .movable {
      user-select: none;
      width: 100px;
      height: 100px;
      cursor: move;
      position: absolute;
      padding: 10px;
      display: flex;
      align-items: center;
      justify-content: center;
      text-align: center;
      background-color: palegreen;
    } 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    效果截图如下
    在这里插入图片描述

  • 相关阅读:
    产品经理学习笔记
    JS提升:实现flat平铺的底层原理
    python的类属性和实例属性
    http缓存策略以及强缓存和协商缓存浅析
    【全网独家,收藏吧】10年全部《信息资源管理》真题整理,第2章 信息化规划与组织
    yolov7模型训练环境安装
    自动驾驶的未来展望和挑战
    81.(前端)分配权限实现——用路由来接收参数实现展开行中选数据功能
    nano gpt 中MLP的矩阵长度为什么是C*4的;MLP多层感知机:s x h;llama3 和chatGpt4的 MLP 隐藏层数量;
    电子制造行业的数字化转型突破点在哪?精益制造是关键
  • 原文地址:https://blog.csdn.net/kyyius/article/details/132901373