React模态框设计(三)可拖动组件的完善

我在上次的Draggable组件的设计中给了一个简化的方法,今天我来完善一下这个组件,可用于任何可移动组件的包裹。完善后的效果如下所示:

这个优化中,增加了一个注目的效果,还增加了触发可拖动区域的指定功能,这样我们的可拖动组件使用上更加的自由了。

Draggable 中增加以下两个Props

  • enableHandler 是否启用可拖动句柄

  • draggableHandler 可拖动句柄字符

上面的draggableHandler就是一个类名,如果 enableHandler true 的情况下,可拖动组件中含有 draggableHandler 指定的类名的子组件的 mouseDown 事件会触发拖动的移动效果。其实触发事件是通过Draggable代理实现的。

javascript 复制代码
import React, { useEffect, useRef, useState } from 'react';
import Box from '@mui/material/Box';

/**
 * 拖动组件
 * @param {是否启用拖动句柄 } enableHandler 
 * @param {拖动句柄的类名} draggableHandler
 */
export default function Draggable({
    children, // 子组件
    enableHandler = false, // 是否启用拖动句柄
    draggableHandler = ".modelHandler" // 拖动句柄的类名
}) {
    const [isDragging, setIsDragging] = useState(false); // 是否正在拖动
    const [canDrag, setCanDrag] = useState(!enableHandler); // 是否可以拖动
    const [position, setPosition] = useState({ x: 0, y: 0 }); // 位置
    const offsetX = useRef(0); // x轴偏移量
    const offsetY = useRef(0); // y轴偏移量

    useEffect(() => {
        // 鼠标移动事件
        const handleMouseMove = (e) => {
            if (isDragging) {
                setPosition({
                    x: e.clientX - offsetX.current,
                    y: e.clientY - offsetY.current
                });
            }
        };

        // 鼠标抬起事件
        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]);

    const onMouseMove = (e) => {
        if (enableHandler) {
            // 判断是否在拖动句柄上
            if (document.elementFromPoint(e.clientX, e.clientY).className.includes(draggableHandler)) {
                setCanDrag(true);
            } else {
                setCanDrag(false);
            }
        }
        
    }

    const handleMouseDown = (e) => {
        e.preventDefault();
        e.stopPropagation();

        if (enableHandler) {
            // 判断是否在拖动句柄上
            if (document.elementFromPoint(e.clientX, e.clientY).className.includes(draggableHandler)) {
                if (e.button !== 0) return;
                setIsDragging(true);
                offsetX.current = e.clientX - position.x;
                offsetY.current = e.clientY - position.y;
            }
        } else {
            if (e.button !== 0) return;
            setIsDragging(true);
            offsetX.current = e.clientX - position.x;
            offsetY.current = e.clientY - position.y;
        }
    };

    return (
        <Box
            sx={{
                position: 'relative',
                transform: `translate(${position.x}px, ${position.y}px)`,
                cursor: canDrag ? isDragging ? "grabbing" : "grab" : "default",
                transition: `transform:`,
            }}
            onMouseDown={handleMouseDown}
            onMouseMove={onMouseMove}
        >
            <Box
                sx={{
                    transform: `${isDragging ? "scale(1.03)" : "scale(1)"}`,
                    transition: `transform 200ms ease-in-out`,
            }}>
                {
                    children
                }
            </Box>
        </Box>
    );
}

上面的逻辑并不复杂,能过下面的语句可以找到含有 指定类名的组件:

javascript 复制代码
document.elementFromPoint(e.clientX, e.clientY).className.includes(draggableHandler)

这样就能判断当前鼠标是否处于指定的组件上启动移动效果。 由于我们要实现抓取注目动画和移动,都是通过 transform实现的,但是我们只要缩放动画,所以我用了两层Box包裹来分割transform属性。

为了测试这个Draggable, 我来做个小组件测试 draggableHandler 的作用。

javascript 复制代码
/** @jsxImportSource @emotion/react */
import { css, jsx } from '@emotion/react'
import Box from '@mui/material/Box';

const titleBarCss = css`
        background-color: #BDBDBD;
        padding: 8px;
    `;

const modelContentCss = css`
        padding: 16px;
    `;

const ModalTest = ({width = 400, height = 300, bgColor = "white"}) => {
    return (
        <Box css={css`
        position: relative;
        background-color: ${bgColor};
        border: 1px solid #ccc;
        border-radius: 5px;
        overflow: hidden;
        box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
        width: ${width}px;
        height: ${height}px;
    `}>
            <Box
                css={titleBarCss}
                className=".modelHandler"
            >
                这是标题
            </Box>
            <Box css={modelContentCss}>
                这是弹窗内容
            </Box>
        </Box>
    );
};

export default ModalTest;

上面的组件中,我们在标题栏上增加了类名: modelHandler。很简单是不是。接下来我们来完整的测试:

javascript 复制代码
import React from "react";
import Stack from "@mui/material/Stack";
import Draggable from "../../framework-kakaer/SModel/_Draggable";
import ModelTest from "../../framework-kakaer/SModel/_DraggableContent";

export default function DraggableTest() {
  return (
      <Stack spacing={3}>
          <Stack direction="row" spacing={2}>
              <Draggable>
                  <div style={{ width: 200, height: 200, backgroundColor: 'red' }}>Draggable</div>
              </Draggable>

              <Draggable>
                  <div style={{ width: 200, height: 200, backgroundColor: 'green' }}>Draggable</div>
              </Draggable>

              <Draggable>
                  <div style={{ width: 200, height: 200, backgroundColor: 'blue' }}>Draggable</div>
              </Draggable>
          </Stack>

          <Stack direction="row" spacing={2}>
              <Draggable enableHandler={true}>
                  <ModelTest width={200} height={200} bgColor="yellow" />
              </Draggable>

              <Draggable>
                  <ModelTest width={200} height={200} bgColor="#FF9500" />
              </Draggable>

              <Draggable>
                  <ModelTest width={200} height={200} bgColor="#5AC8FA" />
              </Draggable>
          </Stack>
    </Stack>
  );
}

这样就有了开头的效果了。相当的完美是不是。

相关推荐
天下无贼!1 小时前
2024年最新版Vue3学习笔记
前端·vue.js·笔记·学习·vue
Jiaberrr1 小时前
JS实现树形结构数据中特定节点及其子节点显示属性设置的技巧(可用于树形节点过滤筛选)
前端·javascript·tree·树形·过滤筛选
赵啸林1 小时前
npm发布插件超级简单版
前端·npm·node.js
罔闻_spider2 小时前
爬虫----webpack
前端·爬虫·webpack
吱吱鼠叔2 小时前
MATLAB数据文件读写:1.格式化读写文件
前端·数据库·matlab
爱喝水的小鼠2 小时前
Vue3(一) Vite创建Vue3工程,选项式API与组合式API;setup的使用;Vue中的响应式ref,reactive
前端·javascript·vue.js
盏灯2 小时前
前端开发,场景题:讲一下如何实现 ✍电子签名、🎨你画我猜?
前端
WeiShuai2 小时前
vue-cli3使用DllPlugin优化webpack打包性能
前端·javascript
Wandra2 小时前
很全但是超级易懂的border-radius讲解,让你快速回忆和上手
前端
ice___Cpu2 小时前
Linux 基本使用和 web 程序部署 ( 8000 字 Linux 入门 )
linux·运维·前端