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;
相关推荐
超哥--5 小时前
B站视频内容智能分析系统(九):React 前端与管理面板
前端·react.js·前端框架
Cutecat_7 小时前
视频字幕处理工具横向:提取模式 vs 编辑模式,该如何选择
android·前端·ios·语音识别
dsyyyyy11017 小时前
JavaScript变量
开发语言·javascript·ecmascript
qq_422152578 小时前
PDF 加水印工具怎么选?2026 年文档版权保护方案对比
前端·pdf·github
kyriewen8 小时前
手写 Promise.all、race、any:不到 30 行代码,解决并发异步的所有姿势
前端·javascript·面试
brucelee1869 小时前
OpenClaw 浏览器控制(Chrome MCP)完整教程
前端·chrome
ct9789 小时前
React 状态管理方案深度对比
开发语言·前端·react
胡志辉的博客9 小时前
深入浅出理解浏览器事件循环:从一道输出题讲到 Chrome 源码
前端·javascript·chrome·chromium·event loop
代码不加糖9 小时前
js中不会冒泡的事件有哪些?
前端·javascript·vue.js
懂懂tty10 小时前
Vue2与Vue3之间API差异
前端·javascript·vue.js