react 实现拖动元素

news2024/11/15 4:05:08
demo使用create-react-app脚手架创建
删除一些文件,创建一些文件后
结构目录如下截图

在这里插入图片描述

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


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

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.coloradmin.cn/o/1013254.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

ABB 1TGE120010R... Rev控制模块

ABB 1TGE120010R... Rev 控制器模块是一种高性能控制器&#xff0c;可用于工业自动化和过程控制应用。它具有以下主要特点&#xff1a; 多功能性&#xff1a;该控制器模块可用于多种应用&#xff0c;包括机器控制、过程控制和自动化系统等。 高性能&#xff1a;该控制器模块具…

竞赛选题 基于机器视觉的火车票识别系统

文章目录 0 前言1 课题意义课题难点&#xff1a; 2 实现方法2.1 图像预处理2.2 字符分割2.3 字符识别部分实现代码 3 实现效果最后 0 前言 &#x1f525; 优质竞赛项目系列&#xff0c;今天要分享的是 基于机器视觉的火车票识别系统 该项目较为新颖&#xff0c;适合作为竞赛…

408强化(番外)文件管理

有点看不下去书&#xff0c;408&#xff0c;哎好久没看了&#xff0c;死磕数学时完全不想看其他科目&#xff0c;数学分数也尚未质变。 突然想到一个好点子&#xff0c;只看大纲尝试回忆一下这章的内容。 文件就是为了方便用户使用&#xff0c;按名访问而提出的&#xff0c;从…

Python进阶教学——多线程高级应用

目录 一、线程间的通讯机制 二、线程中的消息隔离机制 三、线程同步信号量 四、线程池和进程池 一、线程间的通讯机制 1、Queue消息队列 消息队列是在消息的传输过程中保存消息的容器&#xff0c;主要用于不同线程间任意类型数据的共享。消息队列最经典的用法就是消费者和…

【Linux】项目自动化构建工具 make/Makefile

1、背景原理 一个工程中的源文件不计数&#xff0c;其按类型、功能、模块分别放在若干个目录中&#xff0c;makefile 定义了一系列的规则来指定&#xff0c;哪些文件需要先编译&#xff0c;哪些文件需要后编译&#xff0c;哪些文件需要重新编译&#xff0c;甚至于进行更复杂的功…

Day 01 web前端基础知识

首先我们要了解什么事前端&#xff1f; 先简单用文字介绍一下&#xff1a; 一、入门知识 Web前端是指网站或应用程序的用户界面部分。它包括HTML、CSS、JavaScript等语言和技术&#xff0c;用于创建用户可浏览和交互的网页。Web前端的特点在于其交互性和动态性&#xff0c;可…

富芮坤蓝牙FR801xH GPIO

通过规格书&#xff0c;可查看到芯片共有32个引脚&#xff0c;如图&#xff1a; 除如电源、晶振等固定用途的引脚外&#xff0c;开发板已引出其余引脚。 通常情况下&#xff0c;一个IO口除了可作普通输入输出口外&#xff0c;还有可能作其它用途&#xff0c;如作I2C接口的数据…

Linux static_key原理与应用

文章目录 背景1. static-key的使用方法1.1. static-key定义1.2 初始化1.3 条件判断1.4 修改判断条件 2、示例代码参考链接 背景 内核中有很多判断条件在正常情况下的结果都是固定的&#xff0c;除非极其罕见的场景才会改变&#xff0c;通常单个的这种判断的代价很低可以忽略&a…

18 自增长主键的实现 以及 记录的插入

前言 这里主要是 探索一下 mysql 的自增长主键 和 insert into tz_test (field1) values ("12111111111"); 的实现 这里 有一些地方 会有不求甚解的地方, 不然 篇幅 就有点太长了 测试表结构 mysql 主键自增长 读取自增长值的地方, 读取 table->autoinc 作…

激活函数总结(三十四):激活函数补充(FReLU、CReLU)

激活函数总结&#xff08;三十四&#xff09;&#xff1a;激活函数补充 1 引言2 激活函数2.1 FReLU激活函数2.2 CReLU激活函数 3. 总结 1 引言 在前面的文章中已经介绍了介绍了一系列激活函数 (Sigmoid、Tanh、ReLU、Leaky ReLU、PReLU、Swish、ELU、SELU、GELU、Softmax、Sof…

React 全栈体系(七)

第四章 React ajax 一、理解 1. 前置说明 React本身只关注于界面, 并不包含发送ajax请求的代码前端应用需要通过ajax请求与后台进行交互(json数据)react应用中需要集成第三方ajax库(或自己封装) 2. 常用的ajax请求库 jQuery: 比较重, 如果需要另外引入不建议使用axios: 轻…

AI无法提振台积电股价

来源&#xff1a;猛兽财经 作者&#xff1a;猛兽财经 总结&#xff1a; &#xff08;1&#xff09;台积电的股价已经从最高点下跌了18%&#xff0c;很多期权交易员正在押注台积电的股价会进一步下跌。 &#xff08;2&#xff09;华尔街分析师目前也下调了台积电的收入和盈利预期…

EasyUI combobox 实现搜索(模糊匹配)功能

很简单的一个下拉框搜索模糊匹配功能&#xff0c;在此记录&#xff1a; 1&#xff1a;页面实现&#xff1a; <select class"easyui-combobox" name"combobox" id"combobox" style"width:135px;height:25px;" headerValue"请选…

9.19-21,openEuler与您相约2023欧洲开源峰会

2023年9月19日-21日&#xff0c;openEuler将参加在西班牙毕尔巴鄂举办的 OSSUMMIT 2023&#xff08;Open Source Summit Europe 2023&#xff09;&#xff0c;这是openEuler继去年正式亮相后的第二次全面参加该峰会。 Open Source Summit Europe是由Linux基金会主办&#xff0…

来喽!!炒鸡详细的“数据在内存中的存储”真的来喽!

目录​​​​​​​ 1. 整数在内存中的存储 1.1 ⼆进制介绍 1.1.1 2进制转10进制 1.1.2 10进制转2进制 1.1.3 2进制转8进制 1.1.4 2进制转16进制 1.2 原码、反码、补码 2. ⼤⼩端字节序和字节序判断 2.1 什么是⼤⼩端&#xff1f; 2.2 为什么有⼤⼩端? 2.3 练习 …

01目标检测-问题引入

目录 一、目标检测问题定义 二、目标检测过程中的常见的问题 三、目标检测VS图像分类区别 目标检测&#xff1a; 图像分类&#xff1a; 总结&#xff1a; 四、目标检测VS目标分割 目标分割&#xff1a; 目标检测是计算机视觉领域的一个重要任务&#xff0c;旨在从图像或…

[管理与领导-93]:IT基层管理者 - 扩展技能 - 5 - 职场丛林法则 -7- 复杂问题分析能力与复杂问题的解决能力:系统化思维

目录 前言&#xff1a; 一、系统化思维 VS 分解思维 1.1 系统化思维 1.2 分解思维 二、中医与西医思维模式的区别 三、正向闭环/正反馈 VS 负向闭环/负反馈 VS 开环 3.1 开环与管理 3.2 闭环与管理 3.3 生态系统是闭环系统 3.4 团队是一个闭环系统 3.5 正向闭环/正反…

有趣的设计模式——适配器模式让两脚插头也能使用三孔插板

版权声明 本文原创作者&#xff1a;谷哥的小弟作者博客地址&#xff1a;http://blog.csdn.net/lfdfhl 场景与问题 众所周知&#xff0c;我们国家的生活用电的电压是220V而笔记本电脑、手机等电子设备的工作压没有这么高。为了使笔记本、手机等设备可以使用220V的生活用电就需…

API(九)基于协程的并发编程SDK

一 基于协程的并发编程SDK 场景&#xff1a; 收到一个请求会并发发起多个请求,使用openresty提供的协程说明&#xff1a; 这个是高级课程,如果不理解可以先跳过遗留&#xff1a; APSIX和Kong深入理解openresty 标准lua的协程 ① 早期提供的轻量级协程SDK ngx.thread ngx…

国内外交通数据集介绍(附参数说明)

国外数据集 NGSIM数据集 NGSIM数据集采集自美国&#xff0c;数据集中包含两条高速公路&#xff08;US-101&#xff0c;I-80&#xff09;及两条城市道路&#xff08;lankershim&#xff0c;peachtree&#xff09;的数据&#xff0c;每条道路的采集时间为45min。数据集中包含包含…