react 实现拖动元素

news/2024/7/15 18:03:59 标签: react.js, javascript, 前端
demo使用create-react-app脚手架创建
删除一些文件,创建一些文件后
结构目录如下截图

在这里插入图片描述

javascript">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
javascript">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)
javascript">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;
};


javascript">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}
}
javascript">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;
  }
};
javascript">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;
};
javascript">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;

javascript">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();

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;
} 

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


http://www.niftyadmin.cn/n/5027172.html

相关文章

Java-day14(多线程)

多线程 0.基本概念 程序&#xff1a;为完成特定任务&#xff0c;用某种编程语言编写的一组指令的集合&#xff08;静态&#xff09; 进程&#xff1a;程序的一次执行过程&#xff0c;或正在执行的一个程序&#xff08;动态过程&#xff09; 线程&#xff1a;程序内部的一条…

英语简单句

简单句分类 陈述句疑问句感叹句祈使句 一、陈述句 陈述句就是描述一个东西或者一个事实&#xff0c;把这个东西给陈述出来。 陈述句的两种形式&#xff1a; 肯定式&#xff1a;I have money 肯定变否定的方式&#xff1a;助动词后面加否定词 否定式&#xff1a;I don’t&…

classnames合并class名

classnames拼接 class 的名称 作用:为了方便给组件动态添加与删除class名称 安装 pnpm i classnames -S使用 当值为 undefined, null, false, 0, ‘’ 时,对应的class名称将不会显示 import classNames from classnames;let buttonType = primary;let btnClassName=btn-pr…

伦敦银时走势与获利机会

交易时间灵活、资金杠杆充沛是伦敦银交易的主要优势&#xff0c;投资者应该充分利用这个品种的制度优势&#xff0c;结合自己个人的作息时间&#xff0c;在工作、投资与生活三者之间取得平衡的前提下&#xff0c;借助国际白银市场的波动&#xff0c;通过交易逐步实现自己的财富…

【JUC系列-06】深入理解Semaphore底层原理和基本使用

JUC系列整体栏目 内容链接地址【一】深入理解JMM内存模型的底层实现原理https://zhenghuisheng.blog.csdn.net/article/details/132400429【二】深入理解CAS底层原理和基本使用https://blog.csdn.net/zhenghuishengq/article/details/132478786【三】熟练掌握Atomic原子系列基本…

05目标检测-区域推荐(Anchor机制详解)

目录 一、问题的引入 二、解决方案-设定的anchor boxes 1.高宽比&#xff08;aspect ratio&#xff09;的确定 2.尺度(scale)的确定 3.anchor boxes数量的确定 三、Anchor 的在目标检测中是怎么用的 1、anchor boxes对真值bounding box编码的步骤 2、为什么要回归偏移量…

mysql 插入sql语句,把当前时间格式话到时分秒 yyyy-MM-dd

要将当前时间格式化为 yyyy-MM-dd HH:mm:ss 格式&#xff0c;并插入 MySQL 数据库&#xff0c;您可以使用以下 SQL 插入语句&#xff1a; INSERT INTO your_table_name (your_column_name) VALUES (DATE_FORMAT(NOW(), %Y-%m-%d %H:%i:%s)); 上面的语句中&#xff0c;your_t…

程序单实例运行的一种实现

技术背景知识 来自《Windows核心编程》 创建自定义段 Section 来自《Windows核心编程》 举例&#xff08;获取当前总共运行的实例数&#xff09; 创建自定义段并设置属性 #include "stdafx.h" #include "MFCApplication1.h" #include "MFC…