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;
相关推荐
崔庆才丨静觅3 小时前
hCaptcha 验证码图像识别 API 对接教程
前端
passerby60614 小时前
完成前端时间处理的另一块版图
前端·github·web components
掘了4 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅4 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅4 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
崔庆才丨静觅5 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment5 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
崔庆才丨静觅5 小时前
刷屏全网的“nano-banana”API接入指南!0.1元/张量产高清创意图,开发者必藏
前端
剪刀石头布啊5 小时前
jwt介绍
前端
爱敲代码的小鱼5 小时前
AJAX(异步交互的技术来实现从服务端中获取数据):
前端·javascript·ajax