当前位置: 首页 > 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 语句该怎么写四、…...

label-studio的使用教程(导入本地路径)

文章目录 1. 准备环境2. 脚本启动2.1 Windows2.2 Linux 3. 安装label-studio机器学习后端3.1 pip安装(推荐)3.2 GitHub仓库安装 4. 后端配置4.1 yolo环境4.2 引入后端模型4.3 修改脚本4.4 启动后端 5. 标注工程5.1 创建工程5.2 配置图片路径5.3 配置工程类型标签5.4 配置模型5.…...

《Playwright:微软的自动化测试工具详解》

Playwright 简介:声明内容来自网络&#xff0c;将内容拼接整理出来的文档 Playwright 是微软开发的自动化测试工具&#xff0c;支持 Chrome、Firefox、Safari 等主流浏览器&#xff0c;提供多语言 API&#xff08;Python、JavaScript、Java、.NET&#xff09;。它的特点包括&a…...

【网络安全产品大调研系列】2. 体验漏洞扫描

前言 2023 年漏洞扫描服务市场规模预计为 3.06&#xff08;十亿美元&#xff09;。漏洞扫描服务市场行业预计将从 2024 年的 3.48&#xff08;十亿美元&#xff09;增长到 2032 年的 9.54&#xff08;十亿美元&#xff09;。预测期内漏洞扫描服务市场 CAGR&#xff08;增长率&…...

ios苹果系统,js 滑动屏幕、锚定无效

现象&#xff1a;window.addEventListener监听touch无效&#xff0c;划不动屏幕&#xff0c;但是代码逻辑都有执行到。 scrollIntoView也无效。 原因&#xff1a;这是因为 iOS 的触摸事件处理机制和 touch-action: none 的设置有关。ios有太多得交互动作&#xff0c;从而会影响…...

10-Oracle 23 ai Vector Search 概述和参数

一、Oracle AI Vector Search 概述 企业和个人都在尝试各种AI&#xff0c;使用客户端或是内部自己搭建集成大模型的终端&#xff0c;加速与大型语言模型&#xff08;LLM&#xff09;的结合&#xff0c;同时使用检索增强生成&#xff08;Retrieval Augmented Generation &#…...

JAVA后端开发——多租户

数据隔离是多租户系统中的核心概念&#xff0c;确保一个租户&#xff08;在这个系统中可能是一个公司或一个独立的客户&#xff09;的数据对其他租户是不可见的。在 RuoYi 框架&#xff08;您当前项目所使用的基础框架&#xff09;中&#xff0c;这通常是通过在数据表中增加一个…...

MFC 抛体运动模拟:常见问题解决与界面美化

在 MFC 中开发抛体运动模拟程序时,我们常遇到 轨迹残留、无效刷新、视觉单调、物理逻辑瑕疵 等问题。本文将针对这些痛点,详细解析原因并提供解决方案,同时兼顾界面美化,让模拟效果更专业、更高效。 问题一:历史轨迹与小球残影残留 现象 小球运动后,历史位置的 “残影”…...

Linux-进程间的通信

1、IPC&#xff1a; Inter Process Communication&#xff08;进程间通信&#xff09;&#xff1a; 由于每个进程在操作系统中有独立的地址空间&#xff0c;它们不能像线程那样直接访问彼此的内存&#xff0c;所以必须通过某种方式进行通信。 常见的 IPC 方式包括&#…...

PydanticAI快速入门示例

参考链接&#xff1a;https://ai.pydantic.dev/#why-use-pydanticai 示例代码 from pydantic_ai import Agent from pydantic_ai.models.openai import OpenAIModel from pydantic_ai.providers.openai import OpenAIProvider# 配置使用阿里云通义千问模型 model OpenAIMode…...

Android屏幕刷新率与FPS(Frames Per Second) 120hz

Android屏幕刷新率与FPS(Frames Per Second) 120hz 屏幕刷新率是屏幕每秒钟刷新显示内容的次数&#xff0c;单位是赫兹&#xff08;Hz&#xff09;。 60Hz 屏幕&#xff1a;每秒刷新 60 次&#xff0c;每次刷新间隔约 16.67ms 90Hz 屏幕&#xff1a;每秒刷新 90 次&#xff0c;…...