效果:
实现可视化编辑器,第一步难点 是 拖拽
提示:链接和图片默认是可拖动的,不需要draggable属性。
在拖放操作的不同阶段使用并可能发生许多事件属性:
-
在可拖动目标上触发的事件(源元素):
ondragstart - 当用户开始拖动元素时触发
ondrag - 拖动元素时触发
ondragend - 在用户完成拖动元素时触发
-
在放置目标上触发的事件:
ondragenter - 当被拖动的元素进入放置目标时触发
ondragover - 当被拖动的元素超过放置目标时触发
ondragleave - 当被拖动的元素离开放置目标时触发
ondrop - 当被拖动的元素放在放置目标上时触发
这种代码结构更加清楚, demo 和content是俩个完全没有关系的兄弟div
现在需求的 红色拖拽到蓝色中, 这里的方法是定位
情况一:
-
demo的操作逻辑代码
<div id="demo" draggable onDragStart={(e) => handleDragStart(e, 1)} style={{ width: '100px', height: '100px', backgroundColor: 'red', margin: '30px', }} > demo2 </div> const handleDragStart = (e: DragEvent<HTMLDivElement>, id: number) => { e.dataTransfer.setData('text/plain', id.toString()); // 存储id, 和 data-XX一个道理 };
-
content 的逻辑代码
<div id="content" onDrop={handleDrop} style={{ width: '300px', height: '300px', margin: '30px', backgroundColor: 'blue', position: 'relative', }} > content {demos.map((demo) => ( <div key={demo.id} style={{ width: '100px', height: '100px', backgroundColor: 'red', position: 'absolute', left: `${demo.x}px`, top: `${demo.y}px`, }} > demo {demo.id} </div> ))} </div> const handleDrop = (e: DragEvent<HTMLDivElement>) => { e.preventDefault(); const clientX = e.clientX; const clientY = e.clientY; const contentStyle = document .getElementById('content') .getBoundingClientRect(); const x = clientX - contentStyle.left const y = clientY - contentStyle.top; const newDemo: Demo = { x, y, id: +new Date() }; setDemos([...demos, newDemo]); };
上面代码测试结果:
代码测试 ,会有一些偏差,原因是 鼠标拖拽的位置的不是红色div的左上角顶点, 这样的就不会发生偏移, 但是实际情况无法保证每次都是拖拽顶点, 那需要在开始拖拽的计算的鼠标相对于红色div的偏移值
情况二:完整的代码
import React, { useState, DragEvent, useEffect, MouseEvent } from 'react';
interface Demo {
id: number;
x: number;
y: number;
}
const App: React.FC = () => {
const [demos, setDemos] = useState<Demo[]>([]);
const handleDragStart = (e: DragEvent<HTMLDivElement>, id: number) => {
e.dataTransfer.setData('text/plain', id.toString());
const offsetX = e.clientX - e.currentTarget.getBoundingClientRect().left;
const offsetY = e.clientY - e.currentTarget.getBoundingClientRect().top;
e.dataTransfer.setData('offsetX', offsetX.toString());
e.dataTransfer.setData('offsetY', offsetY.toString());
};
const handleDrop = (e: DragEvent<HTMLDivElement>) => {
e.preventDefault();
const clientX = e.clientX;
const clientY = e.clientY;
const contentStyle = document
.getElementById('content')
.getBoundingClientRect();
const offsetX = e.dataTransfer.getData('offsetX');
const offsetY = e.dataTransfer.getData('offsetY');
const x = clientX - contentStyle.left - offsetX;
const y = clientY - contentStyle.top - offsetY;
const newDemo: Demo = { x, y, id: +new Date() };
setDemos([...demos, newDemo]);
};
const handleDragOver = (e: DragEvent<HTMLDivElement>) => {
e.preventDefault();
};
const onMouseDown = (e: MouseEvent<HTMLDivElement>) => {
console.info('onMouseDown', e);
};
const onMouseUp = (e: MouseEvent<HTMLDivElement>) => {
console.info('onMouseUp', e);
};
const onDragEnd = (e: MouseEvent<HTMLDivElement>) => {
console.info('onDragEnd', e);
};
return (
<div>
<div
id="demo"
draggable
onDragStart={(e) => handleDragStart(e, 1)}
onDragEnd={onDragEnd}
style={{
width: '100px',
height: '100px',
backgroundColor: 'red',
margin: '30px',
}}
>
demo2
</div>
<div
id="content"
onDrop={handleDrop}
onDragOver={handleDragOver}
onMouseDown={onMouseDown}
onMouseUp={onMouseUp}
style={{
width: '300px',
height: '300px',
margin: '30px',
backgroundColor: 'blue',
position: 'relative',
}}
>
content
{demos.map((demo) => (
<div
key={demo.id}
style={{
width: '100px',
height: '100px',
backgroundColor: 'red',
position: 'absolute',
left: `${demo.x}px`,
top: `${demo.y}px`,
}}
>
demo {demo.id}
</div>
))}
</div>
</div>
);
};
export default App;