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;
相关推荐
LaughingZhu4 小时前
Product Hunt 每日热榜 | 2026-05-21
前端·人工智能·经验分享·chatgpt·html
怕浪猫5 小时前
Electron 开发实战(一):从零入门核心基础与环境搭建
前端·electron·ai编程
小鹏linux5 小时前
Ubuntu 22.04 部署开源免费具有精美现代web页面的Casdoor账号管理系统
linux·前端·ubuntu·开源·堡垒机
前端若水6 小时前
会话管理:创建、切换、删除对话历史
前端·人工智能·python·react.js
Bigger6 小时前
mini-cc:一个轻量级 AI 编程助手的诞生
前端·ai编程·claude
涵涵(互关)7 小时前
Naive-ui树型选择器只显示根节点
前端·ui·vue
BY组态7 小时前
Ricon组态系统最佳实践:从零开始构建物联网监控平台
前端·物联网·iot·web组态·组态
BY组态7 小时前
Ricon组态系统vs传统组态软件:为什么选择新一代Web组态平台
前端·物联网·iot·web组态·组态
SoaringHeart7 小时前
Flutter进阶:OverlayEntry 插入图层管理器 NOverlayZIndexManager
前端·flutter
放下华子我只抽RuiKe57 小时前
React 从入门到生产(四):自定义 Hook
前端·javascript·人工智能·深度学习·react.js·自然语言处理·前端框架