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,虚拟模型控制) 是一种直觉控制方式,其关键是在每个需要控制的自由度上构造恰当的虚拟构件以产生合适的虚拟力。虚拟力不是实际执行机构的作用力或力矩,而是通过执行机构的作用经过机构转换而成。对于一些控制问题…...
基于大模型的 UI 自动化系统
基于大模型的 UI 自动化系统 下面是一个完整的 Python 系统,利用大模型实现智能 UI 自动化,结合计算机视觉和自然语言处理技术,实现"看屏操作"的能力。 系统架构设计 #mermaid-svg-2gn2GRvh5WCP2ktF {font-family:"trebuchet ms",verdana,arial,sans-…...
线程同步:确保多线程程序的安全与高效!
全文目录: 开篇语前序前言第一部分:线程同步的概念与问题1.1 线程同步的概念1.2 线程同步的问题1.3 线程同步的解决方案 第二部分:synchronized关键字的使用2.1 使用 synchronized修饰方法2.2 使用 synchronized修饰代码块 第三部分ÿ…...
STM32+rt-thread判断是否联网
一、根据NETDEV_FLAG_INTERNET_UP位判断 static bool is_conncected(void) {struct netdev *dev RT_NULL;dev netdev_get_first_by_flags(NETDEV_FLAG_INTERNET_UP);if (dev RT_NULL){printf("wait netdev internet up...");return false;}else{printf("loc…...
c++ 面试题(1)-----深度优先搜索(DFS)实现
操作系统:ubuntu22.04 IDE:Visual Studio Code 编程语言:C11 题目描述 地上有一个 m 行 n 列的方格,从坐标 [0,0] 起始。一个机器人可以从某一格移动到上下左右四个格子,但不能进入行坐标和列坐标的数位之和大于 k 的格子。 例…...
2021-03-15 iview一些问题
1.iview 在使用tree组件时,发现没有set类的方法,只有get,那么要改变tree值,只能遍历treeData,递归修改treeData的checked,发现无法更改,原因在于check模式下,子元素的勾选状态跟父节…...
python爬虫:Newspaper3k 的详细使用(好用的新闻网站文章抓取和解析的Python库)
更多内容请见: 爬虫和逆向教程-专栏介绍和目录 文章目录 一、Newspaper3k 概述1.1 Newspaper3k 介绍1.2 主要功能1.3 典型应用场景1.4 安装二、基本用法2.2 提取单篇文章的内容2.2 处理多篇文档三、高级选项3.1 自定义配置3.2 分析文章情感四、实战案例4.1 构建新闻摘要聚合器…...
【AI学习】三、AI算法中的向量
在人工智能(AI)算法中,向量(Vector)是一种将现实世界中的数据(如图像、文本、音频等)转化为计算机可处理的数值型特征表示的工具。它是连接人类认知(如语义、视觉特征)与…...
从零实现STL哈希容器:unordered_map/unordered_set封装详解
本篇文章是对C学习的STL哈希容器自主实现部分的学习分享 希望也能为你带来些帮助~ 那咱们废话不多说,直接开始吧! 一、源码结构分析 1. SGISTL30实现剖析 // hash_set核心结构 template <class Value, class HashFcn, ...> class hash_set {ty…...
今日科技热点速览
🔥 今日科技热点速览 🎮 任天堂Switch 2 正式发售 任天堂新一代游戏主机 Switch 2 今日正式上线发售,主打更强图形性能与沉浸式体验,支持多模态交互,受到全球玩家热捧 。 🤖 人工智能持续突破 DeepSeek-R1&…...
IP如何挑?2025年海外专线IP如何购买?
你花了时间和预算买了IP,结果IP质量不佳,项目效率低下不说,还可能带来莫名的网络问题,是不是太闹心了?尤其是在面对海外专线IP时,到底怎么才能买到适合自己的呢?所以,挑IP绝对是个技…...
