React 模态框的设计(一)拖动组件的设计

春节终结束了,忙得我头疼。终于有时间弄自己的东西了。今天来写一个关于拖动的实例讲解。先看效果:

这是一个简单的组件设计,如果用原生的js设计就很简单,但在React中有些事件必须要多考虑一些。这是一个系列的文章,专门针对实际应用开发过程中的技术难点逐个讲解,相信大家能用得着。

再次说明,我的示例都是基于MUI框架的,如果你不讲究样式的话,也可以直接采用原生dom的样式设计。关于项目的创建及MUI的应用请查看我以往的文章,都是详细的教程讲解。

要点解说

设计的思路是只把把要移动的组件进行包裹就可以对其进行拖动,注意是拖动,(不是拖放,我后期会出一个拖放的技术文章,请大家另行期待)

第一步: 首先是触发机制,当在目标组件上按下左键时开始拖动,所以要有个标记记录拖动的状态。

javascript 复制代码
const [isDragging, setIsDragging] = useState(false);

当按下左键时设置为 true, 放开时设置为false

javascript 复制代码
// 鼠标按下左键时的事件
const handleMouseDown = (event) => {
      if (event.button !== 0) return;  // 按下的不是左键则直接返回。
      setIsDragging(true);
};

// 鼠标放开左键时的事件
const handleMouseUp = (event) => {
    if(event.button !== 0) return;
    setIsDragging(false);
};

第二步: 单击左键时要记录下鼠标的位置信息,我们定义一个state来记录这个值:

javascript 复制代码
const offsetX = useRef(0);
const offsetY = useRef(0);

第三步: 单击左键不放进行移动,要记录下相对于position的变化量。因为要把这个变化反应到UI上,所以要用useState:

javascript 复制代码
const [position, setPosition] = useState({ x: 0, y: 0 });

继续下面的代码:

javascript 复制代码
// 鼠标按下左键时的事件
const handleMouseDown = (event) => {
    if (event.button !== 0) return;
    offsetX.current = event.clientX - position.x;
    offsetY.current = event.clientY - position.y;

    setIsDragging(true);
};

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

或许你会想直接把这些事件绑定到要拖动的组件上就行了,但这里有个问题,有时我们拖着拖着由于速度过快,鼠标就移出了组件,这就达不到我们的设计效果了。所以呢,我们要把相应的事件绑定到document上是最靠谱的。我们只要把触发事件绑定到拖动组件上就可以了。

javascript 复制代码
if (isDragging) {
    document.addEventListener('mousemove', handleMouseMove);
    document.addEventListener('mouseup', handleMouseUp);
} else {
    document.removeEventListener('mousemove', handleMouseMove);
    document.removeEventListener('mouseup', handleMouseUp);
}

document上绑定的事件在组件卸载后还要移除,所以我们用到useEffect,完整的代码如下:

javascript 复制代码
import React, { useEffect, useRef, useState } from 'react';

export default function Draggable({children}) {
    const [isDragging, setIsDragging] = useState(false);
    const [position, setPosition] = useState({ x: 0, y: 0 });
    const offsetX = useRef(0);
    const offsetY = useRef(0);

    useEffect(() => {
        const handleMouseMove = (event) => {
            if (isDragging) {
                setPosition({
                    x: event.clientX - offsetX.current,
                    y: event.clientY - offsetY.current
                });
            }
        };

        const handleMouseUp = (event) => {
            if(event.button !== 0) return;
            setIsDragging(false);
        };

        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 handleMouseDown = (event) => {
        event.preventDefault();
        event.stopPropagation();
        if (event.button !== 0) return;
        offsetX.current = event.clientX - position.x;
        offsetY.current = event.clientY - position.y;
        
        setIsDragging(true);
    };

    return (
        <div
            style={{
                position: 'relative',
                userSelect: 'none',
                cursor: isDragging ? 'grabbing' : 'grab',
                transform: `translate(${position.x}px, ${position.y}px)`
            }}
            onMouseDown={handleMouseDown}
        >
            {children}
        </div>
    );
}

这样一个基本的拖动组件就设计完成了。快试试效果吧。

javascript 复制代码
import React from "react";
import Draggable from "../framework-kakaer/SModel/_Dragable";
import Box from "@mui/material/Box";
import Stack from "@mui/material/Stack";
import Typegraphy from "@mui/material/Typography";

function DraggableTest() {
    return (
        <Box
            sx={{
                display: 'flex',
                flexDirection: 'column',
                alignItems: 'center',
                justifyContent: 'center',
                height: '100vh',
        }}>
           
            <Stack spacing={2}>
                <Typegraphy variant="h4">拖动组件设计测试</Typegraphy>
                <Draggable>
                    <Box sx={{ width: 100, height: 100, bgcolor: 'red' }} />
                </Draggable>

                <Draggable>
                    <Box sx={{ width: 100, height: 100, bgcolor: 'blue' }} />
                </Draggable>
            </Stack>
        </Box>
        
    )
}

export default DraggableTest;
相关推荐
鹏北海9 分钟前
多标签页登录状态同步:一个简单而有效的解决方案
前端·面试·架构
_AaronWong14 分钟前
基于 Vue 3 的屏幕音频捕获实现:从原理到实践
前端·vue.js·音视频开发
孟祥_成都22 分钟前
深入 Nestjs 底层概念(1):依赖注入和面向切面编程 AOP
前端·node.js·nestjs
let_code23 分钟前
CopilotKit-丝滑连接agent和应用-理论篇
前端·agent·ai编程
Apifox1 小时前
Apifox 11 月更新|AI 生成测试用例能力持续升级、JSON Body 自动补全、支持为响应组件添加描述和 Header
前端·后端·测试
木易士心1 小时前
深入剖析:按下 F5 后,浏览器前端究竟发生了什么?
前端·javascript
在掘金801101 小时前
vue3中使用medium-zoom
前端·vue.js
xump1 小时前
如何在DevTools选中调试一个实时交互才能显示的元素样式
前端·javascript·css
折翅嘀皇虫1 小时前
fastdds.type_propagation 详解
java·服务器·前端
Front_Yue1 小时前
深入探究跨域请求及其解决方案
前端·javascript