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;

以上两点做个补充。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/709954.shtml

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

相关文章

1、docker入门

文章目录 1、tocker简介2、tocker的安装&环境配置2、配置阿里云镜像3、基本命令1、镜像命令2、docker基本命令3、镜像基本命令4、Docker 容器常用命令 1、tocker简介 新一代的虚拟化技术 2、tocker的安装&环境配置 uname -r1、首先查看liunx的内核 yum update -y2、更…

生成voc格式数据集

数据集存放格式&#xff1a;&#xff08;Annotations文件夹放标注的xml文件&#xff0c;JPEGImages文件夹放标注的图片&#xff09; 运行代码&#xff1a; import os import random import xml.etree.ElementTree as ETimport numpy as npdef get_classes(classes_path):with …

USACO 2024 Feb Bronze铜组题解

闲话:今天是4年一度的奇观——2月29日!(地球人都知道) 所以为了纪念这个特殊的日子&#xff0c;我决定倒着讲。这是什么奇怪的规矩&#xff1f;(雾 Maximizing Productivity: 二分即可。 #include <bits/stdc.h> using namespace std; const int maxn200005; int c[ma…

补一次buuctf_crypto的脚本

题目是&#xff1a;Buuctf_crypto_还原大师 本篇写作原因&#xff1a;嗯&#xff1f;&#xff1f;&#xff1f;&#xff1f;&#xff1f;我记得我这篇文章写过啊&#xff0c;怎么找不到了。在这传一下我改进的脚本&#xff1a; import hashlib k TASC?O3RJMV?WDJKX?ZM for…

深入理解Java中的优先级队列(堆)——PriorityQueue

引言&#xff1a; 在Java中&#xff0c;优先级队列&#xff08;PriorityQueue&#xff09;是一种基于堆结构实现的队列&#xff0c;其中每个元素都有一个优先级&#xff0c;优先级高的元素在队列中具有更高的优先级&#xff0c;排在前面。优先级队列常用于任务调度、事件处理等…

LeetCode——栈和队列(Java)

栈和队列 简介[简单] 232. 用栈实现队列[简单] 225. 用队列实现栈[简单] 20. 有效的括号[简单] 1047. 删除字符串中的所有相邻重复项[中等] 150. 逆波兰表达式求值[困难] 239. 滑动窗口最大值[中等] 347. 前 K 个高频元素 简介 记录一下自己刷题的历程以及代码。写题过程中参考…

网工内推 | 项目经理,软考证书优先,最高26K,加班补贴

01 龙盈智达 招聘岗位&#xff1a;项目经理 职责描述&#xff1a; 1 根据业务员需求&#xff0c;完成生态圈下账簿中心系统的开发管理工作。 2 负责账簿中心实施过程中的需求调研分析、方案设计、开发测试、系统上线等工作的计划、组织协调、沟通等方面管理工作。 3 完成系统核…

Spring八股 常见面试题

什么是Spring Bean 简单来说&#xff0c;Bean 代指的就是那些被 IoC 容器所管理的对象。我们需要告诉 IoC 容器帮助我们管理哪些对象&#xff0c;这个是通过配置元数据来定义的。配置元数据可以是 XML 文件、注解或者 Java 配置类。 将一个类声明为 Bean 的注解有哪些? Com…

发掘效率黑科技:Allure报告助您事半功倍提升测试效能!

Allure 简介与安装 Allure 是由 Java 语⾔开发的⼀个轻量级&#xff0c;灵活的测试报告⼯具。 Allure 多平台的 Report 框架。 Allure ⽀持多语⾔&#xff0c;包括 python、JaveScript、PHP、Ruby 等。 可以为开发/测试/管理等人员提供详尽的的测试报告&#xff0c;包括测试…

el-table 指定表格合并行与单元格,以及表头合并单元格

1&#xff1a;页面html <template><div class"container"><div class"flex-end"><el-button type"primary" click"allEndBtn">批量办结</el-button><el-button type"primary" click"…

面向对象编程入门:掌握C++类的基础(2/3):深入理解C++中的类成员函数

在C编程中&#xff0c;类是构建程序的基石&#xff0c;而理解类的默认成员函数对于高效使用C至关重要。本文将深入探讨这六个默认成员函数及其他相关概念&#xff0c;提供给读者一个全面的视角。 类的6个默认成员函数&#xff1a; 如果一个类中什么成员都没有&#xff0c;简称为…

【长期更新】游戏开发中可能会用到的数学小工具

从一个向量生成一组正交基 https://graphics.pixar.com/library/OrthonormalB/paper.pdf

【前端素材】推荐优质后台管理系统Dashy平台模板(附源码)

一、需求分析 后台管理系统&#xff08;或称作管理后台、管理系统、后台管理平台&#xff09;是一种专门用于管理网站、应用程序或系统后台运营的软件系统。它通常由一系列功能模块组成&#xff0c;为管理员提供了管理、监控和控制网站或应用程序的各个方面的工具和界面。以下…

MySQL 如何从 Binlog 找出变更记录并回滚

文章目录 前言1. 案例模拟1.1 确认信息1.2 下载 Binlog1.3 准备环境1.4 注册 Binlog1.5 准备结构信息1.6 Python 订阅1.7 输出结果展示 2. 原理解析2.1 程序设计2.2 模块版本 总结 前言 最近有研发同学问我&#xff1a;有一个问题&#xff0c;想查一个 ID 为 xxxx 的 sku 什么…

经典DP-最长单调子序列

最长递增子序列 思路 定义状态&#xff1a; 我们定义一个数组 dp&#xff0c;其中 dp[i] 表示以 nums[i] 结尾的最长递增子序列的长度。初始化状态&#xff1a; 对于数组中的每个元素 nums[i]&#xff0c;初始时都可以被视为一个长度为1的递增子序列&#xff0c;因此 dp[i] 的…

常用的桌面端自动化测试工具

桌面端软件相比Web端软件而言&#xff0c;因为界面元素的多种形态&#xff0c;导致其定位更难&#xff0c;目前市面上常见的定位方式一般是两种&#xff0c;一种是通过控件本身的属性定位&#xff0c;第二种是通过图片识别来定位&#xff0c;市面上常用的可以支持不同操作系统的…

Qt6内嵌CEF

一、下载CEF CEF下载地址&#xff1a;https://cef-builds.spotifycdn.com/index.html 或https://bitbucket.org/chromiumembedded/cef/src/master/ 选择对应系统的版本&#xff08;本教程选择的是116.0.19&#xff09; CMake下载地址&#xff1a;https://cmake.org/download…

抖店电子面单怎么发货?发货失败是什么原因?看完你就知道了!

我是电商珠珠 对于抖店来说&#xff0c;必须用平台的电子面单发货&#xff0c;否则就会被平台判定无货源违规。但是&#xff0c;关于发货这方面&#xff0c;一些新手还不知道怎么搞&#xff0c;今天我就来给大家详细的讲一下。 怎么用电子面单发货&#xff1f; 1、电脑搜索“…

thefour--Love is like a tide

最后一部分了&#xff0c;要开始进行我们的训练了。 先上代码&#xff1a; import os import numpy as np from tqdm import tqdm import tensorflow as tf from thetwo import NeuralStyleTransferModel import theone import thethree #创建模型 modelNeuralStyleTransferM…

代码随想录训练营第31天 | 理论基础、LeetCode 455.分发饼干、

目录 理论基础 视频讲解&#xff1a;手把手带你学会操作链表 | 贪心算法理论基础&#xff01;_哔哩哔哩_bilibili LeetCode 455.分发饼干 文章讲解&#xff1a;代码随想录(programmercarl.com) 视频讲解&#xff1a;贪心算法&#xff0c;你想先喂哪个小孩&#xff1f;| Le…