当前位置: 首页 > news >正文

react 实现拖动元素

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 = Operationsexport 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.utilsexport 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.initialshared.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/objectconst 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.Operationsfunction 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;
} 

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

相关文章:

react 实现拖动元素

demo使用create-react-app脚手架创建 删除一些文件&#xff0c;创建一些文件后 结构目录如下截图com/index import Movable from ./move import { useMove } from ./move.hook import * as Operations from ./move.opMovable.useMove useMove Movable.Operations Operationse…...

【EI会议】第二届声学,流体力学与工程国际学术会议(AFME 2023)

第二届声学&#xff0c;流体力学与工程国际学术会议 2023 2nd International Conference on Acoustics, Fluid Mechanics and Engineering&#xff08;AFME 2023&#xff09; 声学、流体力学两个古老的学科发展至今&#xff0c;无时无刻都在影响着我们的生活。小到日常使用的耳…...

Android StringFog 字符串自动加密

一、StringFog 作用 一款自动对dex/aar/jar文件中的字符串进行加密Android插件工具&#xff0c;正如名字所言&#xff0c;给字符串加上一层雾霭&#xff0c;使人难以窥视其真面目。可以用于增加反编译难度&#xff0c;防止字符串代码重复。 支持java/kotlin。支持app打包生成…...

上四休三,未来的期许

近日“少上一天班&#xff0c;究竟香不香”引发关注&#xff0c;英国媒体2月21日报道&#xff0c;一项全世界目前为止参加人数最多的“四天工作制”试验&#xff0c;不久前在英国取得了成功。很多人表示上过四天班之后&#xff0c;给多少钱也回不去五天班的时代了。 来百度APP畅…...

怎么防止360安全卫士修改默认浏览器?

默认的浏览器 原先选项是360极速浏览器&#xff08;如果有安装的话&#xff09;&#xff0c;我这里改成了Chrome。 先解锁 才能修改。...

调整参数提高mysql读写速度

要提升MySQL的写入速度,您可以采取一些参数调整和优化措施,这些措施可以根据您的具体应用和环境进行调整。以下是一些常见的参数和优化建议: InnoDB存储引擎: 如果您使用的是InnoDB存储引擎,确保以下参数被设置得合理: innodb_buffer_pool_size:增加内存池大小,以便更多…...

第一届电子纸产业创新应用论坛-邀请函

...

Go expvar包

介绍与使用 expvar 是 exposed variable的简写 expvar包[1]是 Golang 官方为暴露Go应用内部指标数据所提供的标准对外接口&#xff0c;可以辅助获取和调试全局变量。 其通过init函数将内置的expvarHandler(一个标准http HandlerFunc)注册到http包ListenAndServe创建的默认Serve…...

Yolo v8代码逐行解读

train.py文件 1.FILE Path(__file__).resolve() __file__代表的是train.py文件&#xff0c;Path(__file__).resolve()结果是train.py文件的绝对路径。 2.ROOT FILE.parents[0] 获得train.py父目录的绝对路径 3.sys.path 是一个列表list&#xff0c;里面包含了已经添加到系…...

9.18号作业

完善登录框 点击登录按钮后&#xff0c;判断账号&#xff08;admin&#xff09;和密码&#xff08;123456&#xff09;是否一致&#xff0c;如果匹配失败&#xff0c;则弹出错误对话框&#xff0c;文本内容“账号密码不匹配&#xff0c;是否重新登录”&#xff0c;给定两个按钮…...

Spring源码阅读(spring-framework-5.2.24)

spring-aop spring-aspects spring-beans spring-context 等等 第一步&#xff1a; Tags spring-projects/spring-framework GitHub 找到相应的release版本 第二步&#xff1a; 下载相应版本的gardle&#xff0c;如何看版本 spring-framework/gradle/wrapper /gradl…...

【SpringMVC】文件上传与下载、JREBEL使用

目录 一、引言 二、文件的上传 1、单文件上传 1.1、数据表准备 1.2、添加依赖 1.3、配置文件 1.4、编写表单 1.5、编写controller层 2、多文件上传 2.1、编写form表单 2.2、编写controller层 2.3、测试 三、文件下载 四、JREBEL使用 1、下载注册 2、离线设置 一…...

数据结构 第二章作业 线性表 西安石油大学

在顺序表中插入和删除一个结点需平均移动多少个结点&#xff1f;具体的移动次数取决于 哪两个因素&#xff1f; 在顺序表中插入和删除一个结点时&#xff0c;平均移动的结点数量取决于两个因素&#xff1a;插入/删除位置和当前顺序表的长度。 插入/删除位置&#xff1a;如果要…...

vue.mixin全局混合选项

在Vue.js中&#xff0c;Vue.mixin 是一个用来全局混合(mixin)选项的方法。它允许你在多个组件中共享相同的选项&#xff0c;例如数据、方法、生命周期钩子等。这可以用来在组件之间重复使用一些逻辑或共享一些通用的功能 Vue.mixin({// 在这里定义混合的选项data() {return {s…...

VMware Fusion 13+Ubuntu ARM Server 22.04.3在M2芯片的Mac上共享文件夹

因为Server版没有桌面&#xff0c;VMware Tools不能直接装&#xff0c;导致没办法共享文件。 Ubuntu中的包如果需要更新&#xff0c;先执行下面的步骤 sudo apt update 再执行 sudo apt upgrade 不需要更新的话&#xff0c;直接执行下面的步骤 先把open-vm-tools卸载了 …...

PostgreSQL serial类型

serial类型和序列 postgresql序列号&#xff08;SERIAL&#xff09;类型包括 smallserial&#xff08;smallint,short&#xff09;,serial(int)bigserial(bigint,long long int) 不管是smallserial,serial还是bigserial&#xff0c;其范围都是(1,9223372036854775807)&#…...

[创业之路-76] - 创业公司如何在长期坚持中顺势而为?诚迈科技参观交流有感

目录 一、创业环境 1.1. VUCA乌卡时代&#xff1a;易变、复杂、不确定性、模糊的时代 1.2. 中国用了四十年的时间完成了三次工业革命&#xff1a;机械化、电气化、数字化 1.3. 中国正在经历着第四次工业革命&#xff1a;智能化、生态化、拟人化 1.4 国产替代&#xff1a;国…...

人脸修复祛马赛克算法CodeFormer——C++与Python模型部署

一、人脸修复算法 1.算法简介 CodeFormer是一种基于AI技术深度学习的人脸复原模型&#xff0c;由南洋理工大学和商汤科技联合研究中心联合开发&#xff0c;它能够接收模糊或马赛克图像作为输入&#xff0c;并生成更清晰的原始图像。算法源码地址&#xff1a;https://github.c…...

linux入门到精通-第三章-vi(vim)编辑器

目录 文本编辑器gedit介绍vi(vim)命令模式命令模式编辑模式末行模式 帮助教程保存文件切换到编辑模式光标移动(命令模式下)复制粘贴删除撤销恢复保存退出查找替换可视模式替换模式分屏其他用法配置文件 文本编辑器 gedit介绍 gedit是一个GNOME桌面环境下兼容UTF-8的文本编辑器…...

Mybatis面试题(三)

文章目录 前言一、Xml 映射文件中&#xff0c;除了常见的 select|insert|updae|delete 标签之外&#xff0c;还有哪些标签&#xff1f;二、当实体类中的属性名和表中的字段名不一样&#xff0c;如果将查询的结果封装到指定 pojo&#xff1f;三、模糊查询 like 语句该怎么写四、…...

OpenLayers 可视化之热力图

注&#xff1a;当前使用的是 ol 5.3.0 版本&#xff0c;天地图使用的key请到天地图官网申请&#xff0c;并替换为自己的key 热力图&#xff08;Heatmap&#xff09;又叫热点图&#xff0c;是一种通过特殊高亮显示事物密度分布、变化趋势的数据可视化技术。采用颜色的深浅来显示…...

【kafka】Golang实现分布式Masscan任务调度系统

要求&#xff1a; 输出两个程序&#xff0c;一个命令行程序&#xff08;命令行参数用flag&#xff09;和一个服务端程序。 命令行程序支持通过命令行参数配置下发IP或IP段、端口、扫描带宽&#xff0c;然后将消息推送到kafka里面。 服务端程序&#xff1a; 从kafka消费者接收…...

黑马Mybatis

Mybatis 表现层&#xff1a;页面展示 业务层&#xff1a;逻辑处理 持久层&#xff1a;持久数据化保存 在这里插入图片描述 Mybatis快速入门 ![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/6501c2109c4442118ceb6014725e48e4.png //logback.xml <?xml ver…...

逻辑回归:给不确定性划界的分类大师

想象你是一名医生。面对患者的检查报告&#xff08;肿瘤大小、血液指标&#xff09;&#xff0c;你需要做出一个**决定性判断**&#xff1a;恶性还是良性&#xff1f;这种“非黑即白”的抉择&#xff0c;正是**逻辑回归&#xff08;Logistic Regression&#xff09;** 的战场&a…...

镜像里切换为普通用户

如果你登录远程虚拟机默认就是 root 用户&#xff0c;但你不希望用 root 权限运行 ns-3&#xff08;这是对的&#xff0c;ns3 工具会拒绝 root&#xff09;&#xff0c;你可以按以下方法创建一个 非 root 用户账号 并切换到它运行 ns-3。 一次性解决方案&#xff1a;创建非 roo…...

【决胜公务员考试】求职OMG——见面课测验1

2025最新版&#xff01;&#xff01;&#xff01;6.8截至答题&#xff0c;大家注意呀&#xff01; 博主码字不易点个关注吧,祝期末顺利~~ 1.单选题(2分) 下列说法错误的是:&#xff08; B &#xff09; A.选调生属于公务员系统 B.公务员属于事业编 C.选调生有基层锻炼的要求 D…...

JVM暂停(Stop-The-World,STW)的原因分类及对应排查方案

JVM暂停(Stop-The-World,STW)的完整原因分类及对应排查方案,结合JVM运行机制和常见故障场景整理而成: 一、GC相关暂停​​ 1. ​​安全点(Safepoint)阻塞​​ ​​现象​​:JVM暂停但无GC日志,日志显示No GCs detected。​​原因​​:JVM等待所有线程进入安全点(如…...

Mac下Android Studio扫描根目录卡死问题记录

环境信息 操作系统: macOS 15.5 (Apple M2芯片)Android Studio版本: Meerkat Feature Drop | 2024.3.2 Patch 1 (Build #AI-243.26053.27.2432.13536105, 2025年5月22日构建) 问题现象 在项目开发过程中&#xff0c;提示一个依赖外部头文件的cpp源文件需要同步&#xff0c;点…...

Hive 存储格式深度解析:从 TextFile 到 ORC,如何选对数据存储方案?

在大数据处理领域&#xff0c;Hive 作为 Hadoop 生态中重要的数据仓库工具&#xff0c;其存储格式的选择直接影响数据存储成本、查询效率和计算资源消耗。面对 TextFile、SequenceFile、Parquet、RCFile、ORC 等多种存储格式&#xff0c;很多开发者常常陷入选择困境。本文将从底…...

Python Ovito统计金刚石结构数量

大家好,我是小马老师。 本文介绍python ovito方法统计金刚石结构的方法。 Ovito Identify diamond structure命令可以识别和统计金刚石结构,但是无法直接输出结构的变化情况。 本文使用python调用ovito包的方法,可以持续统计各步的金刚石结构,具体代码如下: from ovito…...