React結(jié)合Drag?API實(shí)現(xiàn)拖拽示例詳解
認(rèn)識拖拽
鼠標(biāo)拖拽是一個(gè)常見的交互場景,在這個(gè)熟悉的過程將會發(fā)生哪些事件?

拖拽事件指用戶通過鼠標(biāo)(或其他指針設(shè)備)將元素移到一個(gè)新的位置上。拖拽過程涉及兩個(gè)對象:被拖拽元素(上圖中 A )和可釋放目標(biāo)(上圖中 B )
被拖拽元素
默認(rèn)情況下,圖片、鏈接和文本是可拖動(dòng)的。HTML5 在所有 HTML 元素上規(guī)定了一個(gè) draggable 屬性, 表示元素是否可以拖動(dòng)。圖片和鏈接的 draggable 屬性自動(dòng)被設(shè)置為 true,而其他所有元素此屬性的默認(rèn)值為 false。
某個(gè)元素被拖動(dòng)時(shí),會依次觸發(fā)以下事件:
ondragstart:拖動(dòng)開始,當(dāng)鼠標(biāo)按下并且開始移動(dòng)鼠標(biāo)時(shí),觸發(fā)此事件;整個(gè)周期只觸發(fā)一次;ondrag:只要元素仍被拖拽,就會持續(xù)觸發(fā)此事件;ondragend:拖拽結(jié)束,當(dāng)鼠標(biāo)松開后,會觸發(fā)此事件;整個(gè)周期只觸發(fā)一次。
可釋放目標(biāo)
當(dāng)把拖拽元素移動(dòng)到一個(gè)有效的放置目標(biāo)時(shí),目標(biāo)對象會觸發(fā)以下事件:
ondragenter:只要一把拖拽元素移動(dòng)到目標(biāo)時(shí),就會觸發(fā)此事件;ondragover:拖拽元素在目標(biāo)中拖動(dòng)時(shí),會持續(xù)觸發(fā)此事件;ondragleave或ondrop:拖拽元素離開目標(biāo)時(shí)(沒有在目標(biāo)上放下),會觸發(fā)ondragleave;當(dāng)拖拽元素在目標(biāo)放下(松開鼠標(biāo)),則觸發(fā)ondrop事件。
?? 目標(biāo)元素默認(rèn)是不能夠被拖放的,即不會觸發(fā)
ondrop事件,可以通過在目標(biāo)元素的ondragover事件中取消默認(rèn)事件來解決此問題。
生命周期

拖拽操作中的數(shù)據(jù)傳輸
除非數(shù)據(jù)受影響,否則簡單的拖放并沒有實(shí)際意義。為實(shí)現(xiàn)拖動(dòng)操作中的數(shù)據(jù)傳輸,event 對象上暴露了 dataTransfer 對象,用于從被拖動(dòng)元素向放置目標(biāo)傳遞字符串?dāng)?shù)據(jù)。我們使用它來通知畫布,當(dāng)前需要渲染的組件是什么。
dataTransfer 對象主要有兩個(gè)方法:getData() 和 setData(),分別用來獲取和存儲值。setData()的第一個(gè)參數(shù)以及 getData()的唯一參數(shù)是一個(gè)字符串,表示要設(shè)置的數(shù)據(jù)類型:"text"或"URL"
?? 雖然這兩種數(shù)據(jù)類型是 IE 最初引入的,但 HTML5 已經(jīng)將其擴(kuò)展為允許任何 MIME 類型。為向后 兼容,HTML5 還會繼續(xù)支持"text"和"URL",但它們會分別被映射到"text/plain"和"text/uri-list”
需要注意的是:存儲在 dataTransfer 對象中的數(shù)據(jù)只能在放置事件中讀取。如果沒有在 ondrop 事件中取得這些數(shù)據(jù),dataTransfer 對象就會被銷毀,數(shù)據(jù)也會丟失。
代碼實(shí)現(xiàn)
我在項(xiàng)目中使用 React 來實(shí)現(xiàn),并且考慮到跨組件通信,我使用了 dva 來管理數(shù)據(jù)流。
如何標(biāo)記當(dāng)前拖拽的元素?
HTML5 支持的 data-x 屬性,我們可以將當(dāng)前組件的類型 Rectangle 賦值給它,這樣處理和畫布組件通信方便一些
const Block = (props) => {
const handleDragStart = (e: React.DragEvent<HTMLDivElement>) => {
// 向拖拽數(shù)據(jù)中添加項(xiàng)目
e.dataTransfer.setData('text', e.target.dataset.index);
};
return (
<div onDragStart={handleDragStart}>
<Button draggable data-index="Rectangle">
二維碼
</Button>
</div>
);
};
在上文中講到,dataTransfer 的數(shù)據(jù)必須在 handleDrop 方法中獲取。實(shí)際的用來保存畫布中的所有組件的數(shù)據(jù):
function DragEditor(props) {
const { dvaStore, dispatch } = props;
// 阻止瀏覽器默認(rèn)事件,否則 ondrop 不會觸發(fā)
const handleDragOver = (e: React.DragEvent<HTMLDivElement>) => {
e.preventDefault();
};
const handleDrop = (e: React.DragEvent<HTMLDivElement>) => {
e.preventDefault();
// 獲取拖拽元素的組件類型
const type = e.dataTransfer.getData('text');
// COMPONENT_LIST 定義了組件的數(shù)據(jù)格式,根據(jù) type 匹配
const component = COMPONENT_LIST.filter(
(i) => i.component === type,
)[0];
// 將組件數(shù)據(jù)添加到 store,畫布將會根據(jù)數(shù)據(jù)渲染出組件
if (component) {
dispatch?.({
type: 'store/addComponent',
payload: component,
});
}
};
return (...);
}
在畫布中拖動(dòng)
拖動(dòng)主要依賴組件的初始位置,鼠標(biāo)開始位置、結(jié)束位置。根據(jù)后兩組得到鼠標(biāo)移動(dòng)的距離,和初始位置相加后,得到最終位置。
function DragEditor(props: IEditorProps) {
const { dvaStore, dispatch } = props;
const [startAxis, setStartAxis] = React.useState({ x: 0, y: 0 }); // 鼠標(biāo)開始拖動(dòng)時(shí)的位置
const handleDragStart = (e: React.DragEvent<HTMLDivElement>) => {
setStartAxis({ x: e.clientX, y: e.clientY });
};
const handleDragEnd = (e: React.DragEvent<HTMLDivElement>, data: IComponentSchema) => {
// 鼠標(biāo)移動(dòng)的距離
const displacementX = e.clientX - startAxis.x;
const displacementY = e.clientY - startAxis.y;
// 計(jì)算組件的終點(diǎn)位置:初始位置 + 鼠標(biāo)移動(dòng)的距離
const endX = Number(data.style.left) + displacementX;
const endY = Number(data.style.top) + displacementY;
// 限制坐標(biāo)的最小值為 0
const top = Math.max(endY, 0);
const left = Math.max(endX, 0);
// 更新當(dāng)前組件樣式
dispatch?.({
type: 'store/setShapeStyle',
payload: { top, left },
});
};
return (
{dvaStore.componentsData.map((i) => {
return (
<RenderComponent
type={i.component}
componentData={i}
key={i.generateId}
onDragStart={handleDragStart}
onDragEnd={(e) => handleDragEnd(e, i)}
/>
);
})}
);
}
數(shù)據(jù)結(jié)構(gòu)
最后,就是組件和數(shù)據(jù)結(jié)構(gòu)的設(shè)計(jì),RenderComponent 是一個(gè)自定義的組件,會根據(jù)傳入的 type 屬性渲染對應(yīng)的組件。組件的數(shù)據(jù)結(jié)構(gòu)設(shè)計(jì)如下:
export const COMPONENT_LIST = [
{
component: 'Rectangle', // 組件名稱
label: '矩形', // 左側(cè) Blocks 組件列表中顯示的名字
propValue: '', // 組件所使用的值
icon: 'BorderOuterOutlined', // 左側(cè)組件列表中顯示的 icon 圖標(biāo)
animations: [], // 動(dòng)畫列表
events: {}, // 事件列表
style: { // 組件樣式
width: 100,
height: 100,
top: 0,
left: 0,
},
},
{
component: 'Text',
label: '文字',
propValue: '文字',
icon: '',
animations: [],
events: {},
style: {
width: 200,
height: 33,
fontSize: 14,
fontWeight: 500,
lineHeight: '',
letterSpacing: 0,
textAlign: '',
color: '',
},
},
];總結(jié)
拖拽是非常有趣的一種交互,特別是在低代碼場景下非常重要。使用原生 API 能夠讓我們更加了解底層的一些細(xì)節(jié),React 社區(qū)也有一些優(yōu)秀的第三方框架,如:react-dragable, react-beautiful-dnd,大家有興趣不妨再多了解下。
Links HTML 拖放 API
以上就是React結(jié)合Drag API實(shí)現(xiàn)拖拽示例詳解的詳細(xì)內(nèi)容,更多關(guān)于React Drag API拖拽的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
解讀react的onClick自動(dòng)觸發(fā)等相關(guān)問題
這篇文章主要介紹了解讀react的onClick自動(dòng)觸發(fā)等相關(guān)問題,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-02-02
React Native之ListView實(shí)現(xiàn)九宮格效果的示例
本篇文章主要介紹了React Native之ListView實(shí)現(xiàn)九宮格效果的示例,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-08-08
create-react-app使用antd按需加載的樣式無效問題的解決
這篇文章主要介紹了create-react-app使用antd按需加載的樣式無效問題的解決,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2019-02-02
react項(xiàng)目升級報(bào)錯(cuò),babel報(bào)錯(cuò),.babelrc配置兼容等問題及解決
這篇文章主要介紹了react項(xiàng)目升級報(bào)錯(cuò),babel報(bào)錯(cuò),.babelrc配置兼容等問題及解決方案,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-08-08
使用useMutation和React Query發(fā)布數(shù)據(jù)demo
這篇文章主要為大家介紹了使用useMutation和React Query發(fā)布數(shù)據(jù)demo,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-12-12
React onClick/onChange傳參(bind綁定)問題
這篇文章主要介紹了React onClick/onChange傳參(bind綁定)問題,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-02-02
手動(dòng)用webpack搭建第一個(gè)ReactApp的示例
本篇文章主要介紹了手動(dòng)用webpack搭第一個(gè) ReactApp的示例,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2018-04-04
React?SSR架構(gòu)Streaming?Render與Selective?Hydration解析
這篇文章主要為大家介紹了React?SSR架構(gòu)Streaming?Render與Selective?Hydration解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-03-03

