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