摘要:如其他屬性及方法,詳細(xì)可以查看跨終端能力跨終端能力是最大的特點(diǎn)。在指定區(qū)域的事件中,通過(guò)對(duì)象的屬性,即可獲得文件列表信息,如打印文件名在中實(shí)踐在項(xiàng)目中使用,依然遵循數(shù)據(jù)驅(qū)動(dòng)的原則,即事件數(shù)據(jù)更新。同時(shí),在事件中執(zhí)行判斷。
最近有個(gè)需求,需要產(chǎn)品導(dǎo)航欄支持拖放。
雖然開(kāi)源社區(qū)已有不少成熟的拖放庫(kù),但考慮到代碼可控性和可定制性,還是自己寫(xiě)吧。
關(guān)于選型,前端實(shí)現(xiàn)拖放功能,無(wú)外乎幾種:
1、通過(guò)樣式布局+鼠標(biāo)事件,采用此方案的插件如:@shopify/draggable
2、Canvas繪制,插件如:konva
3、Drag&Drop接口,插件如:dragula
經(jīng)過(guò)一番研究,最終選擇了原生Drag&Drop的方案,原因如下:
1、原生拖放事件,順應(yīng)JS語(yǔ)言發(fā)展趨勢(shì);
2、兼容性符合項(xiàng)目要求;
3、在Can I use...中有如下描述:
事件
一個(gè)拖放行為,自然牽涉到兩部分元素,即拖動(dòng)元素和釋放區(qū)域元素。
與之相關(guān)的事件總共有8個(gè),其中綁定在拖動(dòng)元素的事件有三個(gè):drag、dragstart、dragend;
剩下5個(gè)事件綁定在釋放區(qū)域元素上:dragenter、dragover、dragleave、dragexit、drop。
具體定義可以參考mdn
瀏覽器中,有三種元素,默認(rèn)是可以被拖動(dòng)的,它們是:
1、被選中后的文本;
2、圖片;
3、鏈接
其他元素要轉(zhuǎn)成可拖動(dòng)元素,必須添加draggable="true",如:
"true">div>
注意:這里不能略寫(xiě),如寫(xiě)成:
div>
是無(wú)效的。
定義可被釋放區(qū)域
要使一塊元素可被釋放,首先需要綁定dragenter或dragover事件,然后阻止事件,如下:
"return false">
<div ondragover="event.preventDefault()">
因?yàn)椋@兩個(gè)事件的默認(rèn)行為就是“不觸發(fā)”drop事件,所以要定義成可被釋放區(qū)域,就反其道而行之即可。
DataTransfer對(duì)象
一個(gè)完整的拖放操作,除了拖動(dòng)一個(gè)元素,在指定區(qū)域釋放之外,還有最重要的一步,就是將元素?cái)y帶的信息在被釋放區(qū)域中展示。
比如,拖放一張圖片,本質(zhì)上就是獲取到被拖動(dòng)的圖片src屬性值,并在釋放時(shí),在釋放區(qū)域展示一張相同src的圖片。
而這個(gè)信息,就存儲(chǔ)在DataTransfer對(duì)象中。
對(duì)于非默認(rèn)可拖放元素來(lái)說(shuō),其包含的信息需要在dragstart事件中設(shè)置,使用DataTransfer.setData(),如:
dragItem.ondragstart = e => {
e.dataTransfer.setData("text/plain", "drag info");
}
如果希望拖動(dòng)時(shí),展示自定義的圖片,還可以調(diào)用dataTransfer.setDragImage,如:
dragItem1.ondragstart = e => {
const img = new Image();
img.src = "img_url.jpg";
e.dataTransfer.setDragImage(img, 0, 0);
}
在drop事件中,可以取得拖放元素的信息,并將指定信息通過(guò)dom操作,展示在特定區(qū)域,如:
dropArea.ondrop = e => {
e.preventDefault();
const data = event.dataTransfer.getData("text/plain");
const div = document.createElement("div");
div.textContent = data;
e.target.appendChild(div);
}
在DataTransfer對(duì)象還有一對(duì)屬性,用來(lái)確保釋放區(qū)域只能釋放特定類(lèi)型的拖拽元素,即dropEffect和effectAllowed。
effectAllowed只能在dragstart事件中設(shè)置,在dragenter或dragover事件中,需要設(shè)置dropEffect的值與effectAllowed一致,才能觸發(fā)drop事件。如:
dragItem.ondragstart = e => {
e.dataTransfer.effectAllowed = "move";
}
dropArea.ondragover = e => {
e.preventDefault();
e.dataTransfer.dropEffect = "move";
}
其他屬性及方法,詳細(xì)可以查看mdn
跨終端能力
跨終端能力是drag&drop最大的特點(diǎn)。
最常見(jiàn)的跨終端需求,就是從用戶的本地拖放文件到瀏覽器中指定區(qū)域?qū)崿F(xiàn)上傳功能。
在指定區(qū)域的drop事件中,通過(guò)DataTransfer對(duì)象的files屬性,即可獲得文件列表信息,如:
dropArea.ondrop = e => {
e.preventDefault();
const files = e.dataTransfer.files;
if (files.length) {
Array.prototype.forEach.call(files, f => {
console.log(f.name); //打印文件名
});
}
}
在React中實(shí)踐
在React項(xiàng)目中使用drag&drop,依然遵循React數(shù)據(jù)驅(qū)動(dòng)的原則,即事件->數(shù)據(jù)->DOM更新。
所以,像之前提到的,通過(guò)DataTransfer對(duì)象傳遞數(shù)據(jù)的方式,在React項(xiàng)目中,可以改為操作組件對(duì)象屬性,保證數(shù)據(jù)流的清晰。
但除此之外,在實(shí)際實(shí)踐中,還是遇到了一些問(wèn)題,需要特殊處理。具體如下:
1、必須保留dataTransfer.setData
起初,為保證數(shù)據(jù)流清晰,在React組件中,綁定onDragStart,僅負(fù)責(zé)監(jiān)聽(tīng)事件,數(shù)據(jù)的變動(dòng)和傳遞全部修改組件屬性,但是會(huì)遇到Firefox瀏覽器無(wú)法拖放的兼容問(wèn)題。經(jīng)查發(fā)現(xiàn),在Firefox中,可拖放元素必須滿足:
1、添加draggable="true";
2、綁定事件dragstart;
3、在dragstart中,dataTransfer.setData設(shè)置數(shù)據(jù)
所以,即使e.dataTransfer.setData("text", "");設(shè)置空字符串,也必須添加上這一條。
2、防止跨終端拖拽或不合法拖拽
drop&drag跨終端能力有時(shí)也會(huì)成為干擾。在項(xiàng)目中,會(huì)發(fā)現(xiàn),如果沒(méi)有做判斷,同一個(gè)頁(yè)面同時(shí)打開(kāi)兩個(gè)瀏覽器tab,其拖放元素可以跨tab拖動(dòng),可能會(huì)造成意外BUG。為此,需要增加判斷。
一種方式,在組件實(shí)例構(gòu)建時(shí),生成一個(gè)隨機(jī)字符,借助dataTransfer.setData,為拖放元素打上標(biāo)記。同時(shí),在drop事件中執(zhí)行判斷。
當(dāng)然,如果拖放元素和釋放區(qū)域分屬不同組件,則需要在他們的父組件中,生成隨機(jī)字符,以props形式,傳遞到兩個(gè)子組件。
3、防止Firefox自動(dòng)打開(kāi)新頁(yè)面
在上述提到的為拖放元素打標(biāo)簽中,起初采用的是這樣的寫(xiě)法:
e.dataTransfer.setData("text", uniqDataTransferTag);
結(jié)果在Firefox中,每次drop事件觸發(fā)時(shí),瀏覽器會(huì)自動(dòng)打開(kāi)新tab并搜索uniqDataTransferTag(隨機(jī)字符)。
根據(jù)官方解釋?zhuān)枰?b>drop事件中調(diào)用e.preventDefault(),同時(shí)阻止冒泡e.stopPropagation(),但經(jīng)過(guò)嘗試,依然不生效。初步判斷,可能與React的SyntheticEvent機(jī)制有關(guān)。于是只好曲線救國(guó),改為設(shè)置自定義的MIME type,如:
e.dataTransfer.setData("ucloud_drag_tag", uniqDataTransferTag);
4、節(jié)流與避免event被回收
在項(xiàng)目中,需要在onDragOver中,判斷被拖放元素當(dāng)前位置,并執(zhí)行DOM操作。
根據(jù)定義,dragover事件會(huì)在被拖放元素拖到釋放區(qū)域上時(shí),每幾百毫秒觸發(fā)一次,顯然不做任何處理會(huì)非常影響性能。這里,自然想到采用節(jié)流throttle方式優(yōu)化。
由于節(jié)流是異步操作,而根據(jù)React的SyntheticEvent,event對(duì)象會(huì)在當(dāng)前事件循環(huán)結(jié)束后移除,除非調(diào)用e.persist(),才能在異步操作中訪問(wèn)到。
5、HACK拖放元素拖動(dòng)過(guò)程中,實(shí)現(xiàn)“被拖走”的視覺(jué)效果
根據(jù)設(shè)計(jì)師要求,項(xiàng)目中希望實(shí)現(xiàn)元素拖動(dòng)開(kāi)始后要被拖走,如下圖:
但默認(rèn)的拖放效果,其實(shí)是這樣:
很可惜,官方并沒(méi)有提供對(duì)被拖放元素拖動(dòng)開(kāi)始后設(shè)置效果的接口。經(jīng)過(guò)嘗試,找到一個(gè)通過(guò)樣式HACK方法,如下:
1、新增一個(gè)css class,包含樣式:
transform: translateX(-9999px);
2、對(duì)被拖放元素添加樣式:
transition: transform 0.1s;
3。在拖動(dòng)開(kāi)始后,添加上述第一步的css class。
6、實(shí)現(xiàn)長(zhǎng)按元素激活拖放效果
根據(jù)交互設(shè)計(jì),需要實(shí)現(xiàn)長(zhǎng)按元素一定時(shí)長(zhǎng)后才可以觸發(fā)拖拽。
起初,采用的方案是,綁定鼠標(biāo)事件mousedown,觸發(fā)setTimeout,達(dá)到固定時(shí)長(zhǎng)后觸發(fā)state更新,改變拖放元素的draggable值。但實(shí)際測(cè)試中發(fā)現(xiàn),這種方法存在一定的失敗率,即明明已經(jīng)達(dá)到了長(zhǎng)按的時(shí)長(zhǎng),依然不能拖放。而且,在Firefox中這個(gè)問(wèn)題更加明顯。
推測(cè),可能是draggable的更新偶爾會(huì)晚于dragstart事件,導(dǎo)致拖放失敗。
于是轉(zhuǎn)變思路,增設(shè)組件的屬性作為判斷標(biāo)志,在mousedown事件中更新判斷標(biāo)志,而draggable始終設(shè)為true。如下:
// mousedown事件處理函數(shù)
handleLongPress = e => {
this.resetDragTimer(); // 清除定時(shí)器
return (this.triggerDragTimer = setTimeout(() => {
this.isMenuDraggable = true; // 判斷標(biāo)志
}, this.triggerDragInterval));
};
// dragstart事件處理函數(shù)
handleDragStart = e => {
if (!this.isMenuDraggable) {
e.preventDefault();
} else {
...
}
};
總結(jié)
Drag&Drop作為原生拖放API,可以用最少代碼實(shí)現(xiàn)拖放,看似“簡(jiǎn)單”,實(shí)際并非如此。在實(shí)踐中,還是需要對(duì)官方接口定義,以及各瀏覽器差異有足夠了解,才能避免各種未知錯(cuò)誤。而在React這類(lèi)數(shù)據(jù)驅(qū)動(dòng)的框架中運(yùn)用時(shí),如何處理事件監(jiān)聽(tīng),同時(shí)又不打亂組件的數(shù)據(jù)流,還是需要好好設(shè)計(jì)一番。
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/7280.html
相關(guān)文章
-
使用 Drag and Drop 給Web應(yīng)用提升交互體驗(yàn)
摘要:注意點(diǎn)在鼠標(biāo)操作拖放期間,有一些事件可能觸發(fā)多次,比如和??赏献г?,建議使用,設(shè)定可拖拽元素的鼠標(biāo)游標(biāo),提升交互。在中使用拖拽中使用可以直接綁定到組件上。
什么是 Drag and Drop (拖放)?
簡(jiǎn)單來(lái)說(shuō),HTML5 提供了 Drag and Drop API,允許用戶用鼠標(biāo)選中一個(gè)可拖動(dòng)元素,移動(dòng)鼠標(biāo)拖放到一個(gè)可放置到元素的過(guò)程。
我相信每個(gè)人都或多或少接觸過(guò)拖放,比如瀏覽...
-
React-sortable-hoc 結(jié)合 hook 實(shí)現(xiàn) Draggin 和 Droppin
摘要:?jiǎn)?dòng)項(xiàng)目教程最終的目的是構(gòu)建一個(gè)帶有趣的應(yīng)用程序來(lái)自,可以在視口周?chē)蟿?dòng)。創(chuàng)建組件,添加樣式和數(shù)據(jù)為簡(jiǎn)單起見(jiàn),我們將在文件中編寫(xiě)所有樣式??梢钥闯?,就是在當(dāng)前的外層包裹我們所需要實(shí)現(xiàn)的功能。現(xiàn)在已經(jīng)知道如何在項(xiàng)目中實(shí)現(xiàn)拖放
翻譯:https://css-tricks.com/draggi...
React 社區(qū)提供了許多的庫(kù)來(lái)實(shí)現(xiàn)拖放的功能,例如 react-dnd, react-b...
-
HTML5拖放API Drag and Drop
摘要:此文研究中的拖放接口,提供各個(gè)屬性和方法的說(shuō)明,解決拖放過(guò)程中的拖拽數(shù)據(jù)對(duì)象存儲(chǔ)和獲取問(wèn)題。方法增加一個(gè)拖拽數(shù)據(jù)對(duì)象到屬性中,并返回增加的拖拽數(shù)據(jù)對(duì)象。若拖拽數(shù)據(jù)對(duì)象是文本字符串類(lèi)型,通過(guò)回調(diào)函數(shù)獲取拖拽數(shù)據(jù)中的字符串?dāng)?shù)據(jù)。
此文研究Web API中的拖放接口,提供各個(gè)屬性和方法的說(shuō)明,解決拖放過(guò)程中的拖拽數(shù)據(jù)對(duì)象存儲(chǔ)和獲取問(wèn)題。
拖放API作用到兩個(gè)目標(biāo)對(duì)象,分別是拖拽目標(biāo)對(duì)象和放置...
發(fā)表評(píng)論
0條評(píng)論
lcodecorex
男|高級(jí)講師
TA的文章
閱讀更多
盤(pán)點(diǎn)前端開(kāi)發(fā)中那些用得少卻很實(shí)用的功能
閱讀 489·2019-08-30 15:44
重學(xué)前端學(xué)習(xí)筆記(十九)--JavaScript中的函數(shù)
閱讀 903·2019-08-30 10:55
html+js(swiper.js)+css左右滑動(dòng)切換頁(yè)面效果,適配移動(dòng)端
閱讀 2737·2019-08-29 15:16
PostCSS自學(xué)筆記(二)【插件篇】
閱讀 942·2019-08-29 13:17
Javascript基礎(chǔ)之-this
閱讀 2810·2019-08-26 13:27
[譯] 關(guān)于 Angular 動(dòng)態(tài)組件你需要知道的
閱讀 578·2019-08-26 11:53
【全棧之路】JAVA基礎(chǔ)課程十一_JDK8十大新特性(20190706v1.2)
閱讀 2125·2019-08-23 18:31
jQuery之模擬實(shí)現(xiàn)$().animate()(上)
閱讀 1893·2019-08-23 18:23