春节终结束了,忙得我头疼。终于有时间弄自己的东西了。今天来写一个关于拖动的实例讲解。先看效果:
这是一个简单的组件设计,如果用原生的js设计就很简单,但在React中有些事件必须要多考虑一些。这是一个系列的文章,专门针对实际应用开发过程中的技术难点逐个讲解,相信大家能用得着。
再次说明,我的示例都是基于MUI框架的,如果你不讲究样式的话,也可以直接采用原生dom的样式设计。关于项目的创建及MUI的应用请查看我以往的文章,都是详细的教程讲解。
要点解说
设计的思路是只把把要移动的组件进行包裹就可以对其进行拖动,注意是拖动
,(不是拖放,我后期会出一个拖放的技术文章,请大家另行期待)
第一步: 首先是触发机制,当在目标组件上按下左键时开始拖动,所以要有个标记记录拖动的状态。
const [isDragging, setIsDragging] = useState(false);
当按下左键时设置为 true
, 放开时设置为false
// 鼠标按下左键时的事件
const handleMouseDown = (event) => {
if (event.button !== 0) return; // 按下的不是左键则直接返回。
setIsDragging(true);
};
// 鼠标放开左键时的事件
const handleMouseUp = (event) => {
if(event.button !== 0) return;
setIsDragging(false);
};
第二步: 单击左键时要记录下鼠标的位置信息,我们定义一个state
来记录这个值:
const offsetX = useRef(0);
const offsetY = useRef(0);
第三步: 单击左键不放进行移动,要记录下相对于position
的变化量。因为要把这个变化反应到UI上,所以要用useState
:
const [position, setPosition] = useState({ x: 0, y: 0 });
继续下面的代码:
// 鼠标按下左键时的事件
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
上是最靠谱的。我们只要把触发事件绑定到拖动组件上就可以了。
if (isDragging) {
document.addEventListener('mousemove', handleMouseMove);
document.addEventListener('mouseup', handleMouseUp);
} else {
document.removeEventListener('mousemove', handleMouseMove);
document.removeEventListener('mouseup', handleMouseUp);
}
document
上绑定的事件在组件卸载后还要移除,所以我们用到useEffect
,完整的代码如下:
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>
);
}
这样一个基本的拖动组件就设计完成了。快试试效果吧。
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;