效果:
实现可视化编辑器,第一步难点 是 拖拽
提示:链接和图片默认是可拖动的,不需要draggable属性。
在拖放操作的不同阶段使用并可能发生许多事件属性:
-
在可拖动目标上触发的事件(源元素):
ondragstart - 当用户开始拖动元素时触发
ondrag - 拖动元素时触发
ondragend - 在用户完成拖动元素时触发 -
在放置目标上触发的事件:
ondragenter - 当被拖动的元素进入放置目标时触发
ondragover - 当被拖动的元素超过放置目标时触发
ondragleave - 当被拖动的元素离开放置目标时触发
ondrop - 当被拖动的元素放在放置目标上时触发
这种代码结构更加清楚, demo 和content是俩个完全没有关系的兄弟div
现在需求的 红色拖拽到蓝色中, 这里的方法是定位
情况一:
- demo的操作逻辑代码
<divid="demo"draggableonDragStart={(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 的逻辑代码
<divid="content"onDrop={handleDrop}style={{width: '300px',height: '300px',margin: '30px',backgroundColor: 'blue',position: 'relative',}}>content{demos.map((demo) => (<divkey={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.leftconst 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><divid="demo"draggableonDragStart={(e) => handleDragStart(e, 1)}onDragEnd={onDragEnd}style={{width: '100px',height: '100px',backgroundColor: 'red',margin: '30px',}}>demo2</div><divid="content"onDrop={handleDrop}onDragOver={handleDragOver}onMouseDown={onMouseDown}onMouseUp={onMouseUp}style={{width: '300px',height: '300px',margin: '30px',backgroundColor: 'blue',position: 'relative',}}>content{demos.map((demo) => (<divkey={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;