成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專(zhuān)欄INFORMATION COLUMN

Drag&Drop 拖放API簡(jiǎn)介以及在React中的實(shí)踐

lcodecorex / 951人閱讀

摘要:如其他屬性及方法,詳細(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è):dragdragstart、dragend;
剩下5個(gè)事件綁定在釋放區(qū)域元素上:dragenterdragover、dragleave、dragexit、drop
具體定義可以參考mdn

定義可拖動(dòng)元素

瀏覽器中,有三種元素,默認(rèn)是可以被拖動(dòng)的,它們是:
1、被選中后的文本;
2、圖片;
3、鏈接
其他元素要轉(zhuǎn)成可拖動(dòng)元素,必須添加draggable="true",如:

"true">div>

注意:這里不能略寫(xiě),如寫(xiě)成:

div>

是無(wú)效的。

定義可被釋放區(qū)域

要使一塊元素可被釋放,首先需要綁定dragenterdragover事件,然后阻止事件,如下:

"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)型的拖拽元素,即dropEffecteffectAllowed
effectAllowed只能在dragstart事件中設(shè)置,在dragenterdragover事件中,需要設(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ò)拖放,比如瀏覽...

    legendmohe 評(píng)論0 收藏0
  • 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...

    molyzzx 評(píng)論0 收藏0
  • 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ì)象和放置...

    dantezhao 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<