React 模态框的设计(八)优化补充
在之前的弹窗的设计中,有两处地方现在做一点小小的优化,就是把_Draggable.jsx中的 onPointerEnter 事件 用 useLayoutEffect来规换,效果更佳,同样的,在_ModelContainer.jsx中也是一样。如下所示:
_Draggable.jsx
/** @jsxImportSource @emotion/react */
import { css, keyframes } from '@emotion/react'
import React, { useEffect, useLayoutEffect, useRef, useState } from 'react';
import Box from '@mui/material/Box';
import { useOutsideClick } from './_useOutsideClick';
import { useWindowSize } from './_useWindowSize';
import { minHeight, minWidth } from './_ModelConfigure';//弹窗的动画
const attentionKeyframes = keyframes`from,to {transform: scale(1);}50% {transform: scale(1.03);}
`;//弹窗的开始时动画
const anim = css`animation: ${attentionKeyframes} 400ms ease;
`;//弹窗的结束时动画
const stopAnim = css`animation: null;
`;const draggableHandler = ".model-handler"; // 拖动句柄的类名/*** 拖动组件,使被包裹的组件可以拖动,支持拖动句柄* @param {是否启用拖动句柄 } enableHandler * @param {拖动句柄的类名} draggableHandler* @param {外部点击事件} onOutsideClick*/
export default function Draggable({children, // 子组件enableDragging = true,enableHandler = false, // 是否启用拖动句柄stateMode
}) {const [attentionStyle, setAttentionStyle] = useState(anim); // 弹窗动画,当点击外部时,弹窗会有一个动画效果const [isDragging, setIsDragging] = useState(false); // 是否正在拖动const [canDrag, setCanDrag] = useState(true); // 是否可以触发拖动操作,改变鼠标样式const normalPos = useRef({ x: 0, y: 0 }); // 正常模式下弹窗的位置(translate的值)const minPos = useRef({ x: 0, y: 0 }); // 最小化时的位置const maxPos = { x: 0, y: 0 }; // 最大化时的位置,因为最大化时弹窗的位置是固定的,所以不需要ref// 当所有模式下的位置变化都是通过position来反映到UI上的,所以position是唯一的位置状态const [position, setPosition] = useState({x: 0, y: 0}); // 弹窗的位置(translate的值)// 当鼠标按下时,记录鼠标的位置并以当前位置为基准进行拖动(相对位置),与position的差值为偏移量,position为上一次的偏移量。// 因为采用的是translate的方式进行拖动,这种方式下,是以组件第一次渲染的位置为基准参考点(也就是相对0,0的位置)进行拖动的.// 正常模式下的偏移量const normalOffsetX = useRef(0); // x轴偏移量const normalOffsetY = useRef(0); // y轴偏移量// 最小化时的偏移量const minOffsetX = useRef(0); // x轴偏移量const minOffsetY = useRef(0); // y轴偏移量const initedRect = useRef(0); // 初始化后的弹窗大小const wrapperRef = useRef(null);const windowSize = useWindowSize();// 当点击外部时,弹窗会有一个注目动画效果useOutsideClick(wrapperRef, () => {setAttentionStyle(anim);});// 弹窗注目动画的监听useEffect(function () {// 弹窗动画监听事件const listener = (e) => {if (e.type === "animationend") {setAttentionStyle(stopAnim);}};if (wrapperRef.current !== null) {wrapperRef.current.addEventListener("animationend", listener, true);}return () => {if (wrapperRef.current !== null) {wrapperRef.current.removeEventListener("animationend", listener);}};}, []);// document的鼠标移动事件和鼠标抬起事件监听useEffect(() => {// 鼠标移动事件const handleMouseMove = (e) => {if (isDragging) {switch (stateMode) {case 0:const xt = e.clientX - minOffsetX.current;const yt = e.clientY - minOffsetY.current;const xtMinTop = -((windowSize.height - minHeight) / 2 - 10);const xtMaxTop = (windowSize.height - minHeight) / 2 - 10;const xtMinLeft = -((windowSize.width - minWidth) / 2 - 10);const xtMaxLeft = (windowSize.width - minWidth) / 2 - 10;const xm = xt < xtMinLeft ? xtMinLeft : xt > xtMaxLeft ? xtMaxLeft : xt;const ym = yt < xtMinTop ? xtMinTop : yt > xtMaxTop ? xtMaxTop : yt;minPos.current = { x: xm, y: ym};setPosition({ ...minPos.current });break;case 2:break;default:const xTmp = e.clientX - normalOffsetX.current;const yTmp = e.clientY - normalOffsetY.current;const minLetf = -(windowSize.width - initedRect.current.width) / 2; const minTop = -(windowSize.height - initedRect.current.height) / 2;const maxLeft = (windowSize.width - initedRect.current.width) / 2;const maxTop = (windowSize.height - initedRect.current.height) / 2;const x = xTmp < minLetf ? minLetf : xTmp > maxLeft ? maxLeft : xTmp;const y = yTmp < minTop ? minTop : yTmp > maxTop ? maxTop : yTmp;normalPos.current = { x, y };setPosition({ ...normalPos.current });break;}}};// 鼠标抬起事件const handleMouseUp = (e) => {if (e.button !== 0) return;setIsDragging(false);};// 在相关的事件委托到document上if (isDragging) {document.addEventListener('mousemove', handleMouseMove);document.addEventListener('mouseup', handleMouseUp);} else {document.removeEventListener('mousemove', handleMouseMove);document.removeEventListener('mouseup', handleMouseUp);}// 组件卸载时移除事件return () => {document.removeEventListener('mousemove', handleMouseMove);document.removeEventListener('mouseup', handleMouseUp);};}, [isDragging]);// 弹窗位置的监听, 每当弹窗状态改变时,都会重新设置弹窗的位置, 将相应状态下的最后位置设置为当前位置// 但最小化状态下的位置有所不同,因为最小化状态下的初始位置为左下角,每次从其它状态切换到最小化状态时都要进行相同的设置。useEffect(() => {switch (stateMode) {case 0:const initX = -((windowSize.width - minWidth - 20) / 2);const initY = windowSize.height / 2 - minHeight + 10;setPosition({ x: initX, y: initY });minPos.current = { x: initX, y: initY };break;case 2:setPosition({...maxPos.current});break;default:setPosition({ ...normalPos.current });break;}}, [stateMode]);// ref对象的鼠标移动事件,用于判断是否在拖动句柄上const onMouseMove = (e) => {if (!enableDragging) {setCanDrag(false);return;}if (enableHandler) {const clickedElement = e.target;// 检查鼠标点击的 DOM 元素是否包含特定类名if (clickedElement.classList.contains(draggableHandler)) {setCanDrag(true);} else {setCanDrag(false);}}}// ref对象的鼠标按下事件,用于触发拖动操作,// 如果启用了拖动句柄,那么只有在拖动句柄上按下鼠标才会触发拖动操作,// 否则直接按下鼠标就会触发拖动操作const handleMouseDown = (e) => {if (!enableDragging) return;switch (stateMode) {case 0:if (enableHandler) {// 判断是否在拖动句柄上const curElement = e.target;// 检查鼠标点击的 DOM 元素是否包含特定类名if (curElement.classList.contains(draggableHandler)) {if (e.button !== 0) return;setIsDragging(true);minOffsetX.current = e.clientX - minPos.current.x;minOffsetY.current = e.clientY - minPos.current.y;} else {setCanDrag(false);}} else {if (e.button !== 0) return;setIsDragging(true);minOffsetX.current = e.clientX - minPos.current.x;minOffsetY.current = e.clientY - minPos.current.y;}return;case 2:return; default:if (enableHandler) {// 判断是否在拖动句柄上const curElement = e.target;// 检查鼠标点击的 DOM 元素是否包含特定类名if (curElement.classList.contains(draggableHandler)) {if (e.button !== 0) return;setIsDragging(true);normalOffsetX.current = e.clientX - normalPos.current.x;normalOffsetY.current = e.clientY - normalPos.current.y;} else {setCanDrag(false);}} else {if (e.button !== 0) return;setIsDragging(true);normalOffsetX.current = e.clientX - normalPos.current.x;normalOffsetY.current = e.clientY - normalPos.current.y;}return;}};// 初始化时获取弹窗的大小,此方法替代了onPointerEnter事件,效果要好一些useLayoutEffect(() => {if (wrapperRef.current) {const rect = wrapperRef.current.getBoundingClientRect();initedRect.current = {width: rect.width,height: rect.height,};}}, []);return (<Boxref={wrapperRef}sx={{transform: `translate(${position.x}px, ${position.y}px)`,cursor: canDrag ? isDragging ? "grabbing" : "grab" : "default",transition: isDragging ? null : `transform 200ms ease-in-out`,}}onMouseDown={handleMouseDown}onMouseMove={onMouseMove}onClick={(e) => { e.preventDefault(); e.stopPropagation(); }}// onPointerEnter={() => {// if (initedRect.current === 0 && wrapperRef.current !== null) {// const rect = wrapperRef.current.getBoundingClientRect();// initedRect.current = {// width: rect.width,// height: rect.height,// };// }// }}><Boxsx={{transform: `${isDragging ? "scale(1.03)" : "scale(1)"}`,transition: `transform 200ms ease-in-out`,}}css={attentionStyle}>{children}</Box></Box>);
}
在 _ModelContainer.jsx中做如下更改:
_ModelContainer.jsx
/** @jsxImportSource @emotion/react */
import { css } from '@emotion/react'
import { useLayoutEffect, useRef, useState } from 'react';
import { Paper } from '@mui/material';
import { useModelState } from './useModel';
import { infoLevel } from './_ModelConfigure';// 计算不同状态下的高度
const calHeight = (sizeMode, normalHeight) => {switch (sizeMode) {case 0:return '45px';case 1:return normalHeight > 0 ? normalHeight + 'px' : 'auto';case 2:return '100vh';default:return 'auto';}
}// 最大化时的固定样式
const maxSizeCss = css`width: 100vw;height: 100vh;top: 0;left: 0;`;/*** 弹窗容器* @param {*} param0 * @returns */
const ModelContainer = ({ children }) => {const modelState = useModelState();const {stateMode, // 弹窗的状态,0: 最小化, 1: 正常, 2: 最大化level, // 弹窗的类型(主要是颜色类型),选项有:default, error, warning, success, infoisDark, //是否是暗黑模式 } = modelState;const [nomalSize, setNormalSize] = useState({ width: 0, height: 0 });const containerRef = useRef(null);useLayoutEffect(() => {if (containerRef.current !== null) {const rect = containerRef.current.getBoundingClientRect();setNormalSize({width: rect.width,height: rect.height,});}}, []);return (<Paperref={containerRef}css={css`border: 1px solid #A0A0A0;border-radius: 5px;width: ${ stateMode === 2 ? '100vw' : stateMode === 0 ? '300px' : '576px' };height: ${ calHeight(stateMode, nomalSize.height) };overflow: hidden;max-width: 100%;max-height: 100vh;display: flex;flex-direction: column;background-color: ${isDark ? '#333' : infoLevel[level] ? infoLevel[level].color : "white" };${stateMode === 2 ? maxSizeCss : null};transition: all 0.3s;`}>{children}</Paper>);
};export default ModelContainer;
以上两点做个补充。
相关文章:
React 模态框的设计(八)优化补充
在之前的弹窗的设计中,有两处地方现在做一点小小的优化,就是把_Draggable.jsx中的 onPointerEnter 事件 用 useLayoutEffect来规换,效果更佳,同样的,在_ModelContainer.jsx中也是一样。如下所示: _Draggabl…...
知识积累(三):深度学习相关概念(查看检索时看到)
文章目录 1. 知识蒸馏2. 可微搜索索引(DSI)参考资料 在找论文时,发现的相关概念。 1. 知识蒸馏 知识蒸馏(knowledge distillation)是模型压缩的一种常用的方法,不同于模型压缩中的剪枝和量化,知…...
计算机专业必看的几部电影
目录 编辑 1. 《第九区》(District 9,2009) 2. 《谍影重重》(The Bourne Identity,2002) 3. 《源代码》(Source Code,2011) 4. 《她》(Her,…...
工业人工智能需要注意的10件事
我们无法逃避人工智能这个风口,宣传人工智能软件的广告铺天盖地,似乎每个供应商都在推出最新的工具包,每天都有关于 ChatGPT、Bard 等新用例的文章。似乎全世界都在说:你现在需要人工智能! 人工智能确实正在成为自动化…...
软考-系统集成项目管理中级-信息系统建设与设计
本章重点考点 1.信息系统的生命周期 信息系统建设的内容主要包括设备采购、系统集成、软件开发和运维服务等。信息系统的生命周期可以分为四个阶段:立项、开发、运维和消亡。 2.信息系统开发方法 信息系统常用的开发方法有结构化方法、原型法、面向对象方法等 1)结构化方法 …...
C++从零开始的打怪升级之路(day39)
这是关于一个普通双非本科大一学生的C的学习记录贴 在此前,我学了一点点C语言还有简单的数据结构,如果有小伙伴想和我一起学习的,可以私信我交流分享学习资料 那么开启正题 今天分享的是关于模板的知识点 1.非类型模板参数 模板参数分为…...
Java面试题之并发
并发 1.并发编程的优缺点?2.并发编程三要素?3.什么叫指令重排?4.如何避免指令重排?5.并发?并行?串行?6.线程和进程的概念和区别?7.什么是上下文切换?8.守护线程和用户线程的定义?9.什么是线程死锁?10.形成死锁的四个条件?11.怎么避免死锁?12.创建线程的四种方式?…...
Python GUI自动化定位代码参考
一、pyautogui原始逻辑 import pyautogui # 获取指定图片在屏幕上的位置 image_path path/to/image.png target_position pyautogui.locateCenterOnScreen(image_path) if target_position is not None: # 获取偏移量 offset_x 10 offset_y 10 # 计算实际点…...
11.网络游戏逆向分析与漏洞攻防-游戏网络架构逆向分析-接管游戏接收网络数据包的操作
内容参考于:易道云信息技术研究院VIP课 上一个内容:接管游戏发送数据的操作 码云地址(master 分支):https://gitee.com/dye_your_fingers/titan 码云版本号:8256eb53e8c16281bc1a29cb8d26d352bb5bbf4c 代…...
特斯拉一面算法原题
来自太空的 X 帖子 埃隆马斯克(Elon Musk)旗下太空探索技术公司 SpaceX 于 2 月 26 号,从太空往社交平台 X(前身为推特,已被马斯克全资收购并改名)发布帖子。 这是 SpaceX 官号首次通过星链来发送 X 帖子&a…...
【Leetcode每日一题】二分查找 - 山脉数组的峰顶索引(难度⭐⭐)(23)
1. 题目解析 Leetcode链接:852. 山脉数组的峰顶索引 这个问题的理解其实相当简单,只需看一下示例,基本就能明白其含义了。 核心在于找到题目中所说的峰值所在的下标并返回他们的下标即可。 2. 算法原理 峰顶及两侧数据特点分析 峰顶数据…...
Linux添加用户分组练习
一、复制/etc/skel目录为/home/tuser1(/home/tuser1及其内部文件的属组和其它用户均没有任何访问权限)。 cp -a /etc/skel /home/tuser1 chown -R tuser1:tuser1 /home/tuser1 chmod -R 700 /home/tuser1 二、编辑/etc/group文件,添加组h…...
云快充充电桩系统设计书
充电桩系统设计书 一、系统设计概述 随着新能源汽车市场的快速发展,充电桩作为电动汽车的重要配套设施,其市场需求日益增长。本系统旨在提供一套稳定、高效、易用的充电桩解决方案,以满足市场上新能源充电桩的主流需求。通过实现云快充V1.6协…...
oracle DG 原理
在Oracle中,什么是DG?DG有哪些优缺点? DG(Data Guard,数据卫士)不是一个备份恢复的工具,然而,DG却拥有备份的功能,在物理DG下它可以和主库一模一样,但是它存…...
MySQL篇—持久化和非持久化统计信息介绍(第一篇,总共三篇)
☘️博主介绍☘️: ✨又是一天没白过,我是奈斯,DBA一名✨ ✌✌️擅长Oracle、MySQL、SQLserver、Linux,也在积极的扩展IT方向的其他知识面✌✌️ ❣️❣️❣️大佬们都喜欢静静的看文章,并且也会默默的点赞收藏加关注❣…...
Leetcode—65. 有效数字【困难】
2024每日刷题(118) Leetcode—65. 有效数字 实现代码 class Solution { public:bool isNumber(string s) {if(s.empty()) {return false;}bool seenNum false;bool seenE false;bool seenDot false;for(int i 0; i < s.size(); i) {switch(s[i]…...
【Java程序设计】【C00322】基于Springboot的高校竞赛管理系统(有论文)
基于Springboot的高校竞赛管理系统(有论文) 项目简介项目获取开发环境项目技术运行截图 项目简介 这是一个基于Springboot的高校竞赛管理系统,本系统有管理员、老师、专家以及用户四种角色; 管理员:首页、个人中心、管…...
41、网络编程/TCP.UDP通信模型练习20240301
一、编写基于TCP的客户端实现以下功能: 通过键盘按键控制机械臂:w(红色臂角度增大)s(红色臂角度减小)d(蓝色臂角度增大)a(蓝色臂角度减小)按键控制机械臂 1.基于TCP服务器的机械臂…...
Python中操作MySQL和SQL Server数据库的基础与实战【第97篇—MySQL数据库】
Python中操作MySQL和SQL Server数据库的基础与实战 在Python中,我们经常需要与各种数据库进行交互,其中MySQL和SQL Server是两个常见的选择。本文将介绍如何使用pymysql和pymssql库进行基本的数据库操作,并通过实际代码示例来展示这些操作。…...
【兔子机器人】五连杆运动学解算与VMC(virtual model control)
VMC (virtual model control,虚拟模型控制) 是一种直觉控制方式,其关键是在每个需要控制的自由度上构造恰当的虚拟构件以产生合适的虚拟力。虚拟力不是实际执行机构的作用力或力矩,而是通过执行机构的作用经过机构转换而成。对于一些控制问题…...
Linux相关概念和易错知识点(42)(TCP的连接管理、可靠性、面临复杂网络的处理)
目录 1.TCP的连接管理机制(1)三次握手①握手过程②对握手过程的理解 (2)四次挥手(3)握手和挥手的触发(4)状态切换①挥手过程中状态的切换②握手过程中状态的切换 2.TCP的可靠性&…...
【第二十一章 SDIO接口(SDIO)】
第二十一章 SDIO接口 目录 第二十一章 SDIO接口(SDIO) 1 SDIO 主要功能 2 SDIO 总线拓扑 3 SDIO 功能描述 3.1 SDIO 适配器 3.2 SDIOAHB 接口 4 卡功能描述 4.1 卡识别模式 4.2 卡复位 4.3 操作电压范围确认 4.4 卡识别过程 4.5 写数据块 4.6 读数据块 4.7 数据流…...
【大模型RAG】Docker 一键部署 Milvus 完整攻略
本文概要 Milvus 2.5 Stand-alone 版可通过 Docker 在几分钟内完成安装;只需暴露 19530(gRPC)与 9091(HTTP/WebUI)两个端口,即可让本地电脑通过 PyMilvus 或浏览器访问远程 Linux 服务器上的 Milvus。下面…...
浅谈不同二分算法的查找情况
二分算法原理比较简单,但是实际的算法模板却有很多,这一切都源于二分查找问题中的复杂情况和二分算法的边界处理,以下是博主对一些二分算法查找的情况分析。 需要说明的是,以下二分算法都是基于有序序列为升序有序的情况…...
python报错No module named ‘tensorflow.keras‘
是由于不同版本的tensorflow下的keras所在的路径不同,结合所安装的tensorflow的目录结构修改from语句即可。 原语句: from tensorflow.keras.layers import Conv1D, MaxPooling1D, LSTM, Dense 修改后: from tensorflow.python.keras.lay…...
2025季度云服务器排行榜
在全球云服务器市场,各厂商的排名和地位并非一成不变,而是由其独特的优势、战略布局和市场适应性共同决定的。以下是根据2025年市场趋势,对主要云服务器厂商在排行榜中占据重要位置的原因和优势进行深度分析: 一、全球“三巨头”…...
return this;返回的是谁
一个审批系统的示例来演示责任链模式的实现。假设公司需要处理不同金额的采购申请,不同级别的经理有不同的审批权限: // 抽象处理者:审批者 abstract class Approver {protected Approver successor; // 下一个处理者// 设置下一个处理者pub…...
Python Einops库:深度学习中的张量操作革命
Einops(爱因斯坦操作库)就像给张量操作戴上了一副"语义眼镜"——让你用人类能理解的方式告诉计算机如何操作多维数组。这个基于爱因斯坦求和约定的库,用类似自然语言的表达式替代了晦涩的API调用,彻底改变了深度学习工程…...
安卓基础(Java 和 Gradle 版本)
1. 设置项目的 JDK 版本 方法1:通过 Project Structure File → Project Structure... (或按 CtrlAltShiftS) 左侧选择 SDK Location 在 Gradle Settings 部分,设置 Gradle JDK 方法2:通过 Settings File → Settings... (或 CtrlAltS)…...
算法打卡第18天
从中序与后序遍历序列构造二叉树 (力扣106题) 给定两个整数数组 inorder 和 postorder ,其中 inorder 是二叉树的中序遍历, postorder 是同一棵树的后序遍历,请你构造并返回这颗 二叉树 。 示例 1: 输入:inorder [9,3,15,20,7…...
