新聞中心
dragover效果

網(wǎng)站建設(shè)哪家好,找創(chuàng)新互聯(lián)!專注于網(wǎng)頁(yè)設(shè)計(jì)、網(wǎng)站建設(shè)、微信開發(fā)、小程序制作、集團(tuán)企業(yè)網(wǎng)站建設(shè)等服務(wù)項(xiàng)目。為回饋新老客戶創(chuàng)新互聯(lián)還提供了溫江免費(fèi)建站歡迎大家使用!
這次接著探索一下如何自定義 dragover 樣式。
一、dragenter 和 dragleave
要實(shí)現(xiàn)這樣的效果,少不了和dragenter和dragleave打交道。
- 當(dāng)拖動(dòng)的元素進(jìn)入有效的放置目標(biāo)時(shí), 將會(huì)觸發(fā)dragenter 事件。
- 當(dāng)拖動(dòng)的元素離開有效的放置目標(biāo)時(shí),將會(huì)觸發(fā)dragleave 事件。
拖拽目標(biāo)和放置目標(biāo)
假設(shè)現(xiàn)在有這樣一個(gè)結(jié)構(gòu),這里 img是拖拽目標(biāo),div.content是放置目標(biāo)。
然后在document監(jiān)聽一下;
document.addEventListener('dragleave', function(ev) {
console.log('dragleave', ev.target)
})
document.addEventListener('dragenter', function(ev) {
console.log('dragenter', ev.target)
})那么,將img拖入div.content的過程中,肯定會(huì)觸發(fā)dragenter和dragleave這兩個(gè)事件,如下:
dragenter和dragleave
如果頁(yè)面比較簡(jiǎn)單,要自定義拖拽過程就比較容易了;
document.addEventListener('dragleave', function(ev) {
ev.target.toggleAttribute('over',false);
})
document.addEventListener('dragenter', function(ev) {
ev.target.toggleAttribute('over',true);
})通過添加over屬性自定義樣式;
.content[over]{
outline: 4px solid slateblue;
}效果如下:
dragover效果
是不是非常容易呢?
實(shí)際使用起來其實(shí)還存在很多局限性,下面一一介紹。
二、當(dāng)放置目標(biāo)有子元素時(shí)
大部分情況下,放置目標(biāo)并不是空的,還有其他子元素,如果采用上面的方式就會(huì)有問題了,假設(shè)布局是這樣的,為了區(qū)分,可以給需要放置的元素添加一個(gè)屬性,比如allowdrop,表示允許放置;
不允許放置
這里通過屬性區(qū)分一下:
document.addEventListener('dragleave', function(ev) {
if (ev.target.getAttribute('allowdrop')!==null) {
ev.target.toggleAttribute('over',false);
}
})
document.addEventListener('dragenter', function(ev) {
if (ev.target.getAttribute('allowdrop')!==null) {
ev.target.toggleAttribute('over',true);
}
})效果如下:
有子元素的情況下
可以看到,當(dāng)拖拽目標(biāo)經(jīng)過子元素時(shí),外面的樣式已經(jīng)丟失了。原因其實(shí)很簡(jiǎn)單,在經(jīng)過子元素時(shí),放置目標(biāo)也觸發(fā)了dragleave事件!
那有沒有辦法不觸發(fā)呢?這里有兩種方式:
首先可以取消dragleave的監(jiān)聽,因?yàn)樵趫?zhí)行dragleave時(shí),元素本身是不知道即將進(jìn)入哪一個(gè)區(qū)域,很容易“誤傷”。取而代之的是每次dragenter時(shí),先移除上一次放置目標(biāo)的屬性,然后再添加新的,有點(diǎn)類似選項(xiàng)卡的操作,具體實(shí)現(xiàn)如下:
var lastDrop = null;
document.addEventListener('dragenter', function(ev) {
if (lastDrop) {
lastDrop.toggleAttribute('over',false);
}
const dropbox = ev.target.closest('[allowdrop]'); // 獲取最近的放置目標(biāo)
if (dropbox) {
dropbox.toggleAttribute('over',true);
lastDrop = dropbox;
}
})
還有另一種方式:借助 CSS 就非常容易了。
這里有一個(gè)非常簡(jiǎn)單粗暴的方式,直接將子元素禁用鼠標(biāo)響應(yīng),如下:
.content[allowdrop](empty::after{ "allowdrop") *{
pointer-events: none;
}這樣,在滑過任何子元素都不會(huì)有響應(yīng)了,完美。
有子元素的情況,完美
三、多層嵌套放置目標(biāo)
上面這種方式其實(shí)可以解決大多數(shù)問題了,畢竟大部分場(chǎng)景都是扁平的。不過有時(shí)候也會(huì)碰到多層結(jié)構(gòu),比如那種可視化編輯工具,尤其是目前比較火的低代碼平臺(tái),就會(huì)涉及到多層結(jié)構(gòu),假設(shè) HTML 是這樣的。
不允許拖拽
如果按照 CSS 的處理方式(JS 方式?jīng)]有問題),由于所有子元素都被禁用,里面的結(jié)構(gòu)自然也無(wú)法響應(yīng)了。
多層嵌套結(jié)構(gòu)無(wú)響應(yīng)
那如何讓里面的放置目標(biāo)可以響應(yīng)呢?其實(shí)只需要改一下上面的 CSS 即可,如下:
.content[allowdrop](empty::after{ "allowdrop")>*:not([allowdrop]){
pointer-events: none;
}這里使用了>選擇器,表示只選擇子元素,不包含后代元素,然后排除掉放置目標(biāo),這樣就能實(shí)現(xiàn)多層嵌套了,效果如下:
多層嵌套結(jié)構(gòu),完美
是不是出乎意料的簡(jiǎn)單呢?
四、其他交互細(xì)節(jié)
不知道大家發(fā)現(xiàn)沒,上面的例子在拖拽開始,鼠標(biāo)就一直處于這種“可放置”狀態(tài),不管是在放置目標(biāo)外部還是內(nèi)部,如下:
鼠標(biāo)指針狀態(tài)
這是因?yàn)樵O(shè)置了dragover屬性,所以整個(gè)document都變成了可放置目標(biāo),都允許觸發(fā)drop事件。
document.addEventListener('dragover', function(ev){
ev.preventDefault()
})如果希望交互更加細(xì)膩,體驗(yàn)更好,那么在鼠標(biāo)指示上也可以進(jìn)一步的優(yōu)化,可以在進(jìn)入放置目標(biāo)后才變成這種狀態(tài),實(shí)現(xiàn)如下:
document.addEventListener('dragover', function(ev){
const dropbox = ev.target.closest('[allowdrop]');
if (dropbox) {
ev.preventDefault()
}
})效果如下(注意觀察鼠標(biāo)的變化):
拖拽過程中的鼠標(biāo)變化
除此之外,還應(yīng)該在drop結(jié)束后移除掉over屬性。
document.addEventListener('drop', function(ev){
const dropbox = ev.target.closest('[allowdrop]');
if (dropbox) {
dropbox.toggleAttribute('over',false);
}
})這樣就實(shí)現(xiàn)了一個(gè)完全通用的自定義 dragover效果,區(qū)區(qū)數(shù)十行,劃重點(diǎn),完整代碼如下:
document.addEventListener('dragover', function(ev){
const dropbox = ev.target.closest('[allowdrop]');
if (dropbox) {
ev.preventDefault()
}
})
document.addEventListener('drop', function(ev){
ev.target.toggleAttribute('over',false);
})
document.addEventListener('dragleave', function(ev) {
if (ev.target.getAttribute('allowdrop')!==null) {
ev.target.toggleAttribute('over',false);
}
})
document.addEventListener('dragenter', function(ev) {
if (ev.target.getAttribute('allowdrop')!==null) {
ev.target.toggleAttribute('over',true);
}
})
// 或者以下方式,無(wú)需dragleave,無(wú)需額外 CSS
var lastDrop = null;
document.addEventListener('dragenter', function(ev) {
if (lastDrop) {
lastDrop.toggleAttribute('over',false);
}
const dropbox = ev.target.closest('[allowdrop]'); // 獲取最近的放置目標(biāo)
if (dropbox) {
dropbox.toggleAttribute('over',true);
lastDrop = dropbox;
}
})當(dāng)然還少不了 CSS 的配合,同樣重要。
content: '拖放此處';
}
[allowdrop](empty::after{ "allowdrop"):empty::after{
content: '松開放置';
}
[allowdrop](empty::after{ "allowdrop"){
/*自定義樣式*/
}
[allowdrop](empty::after{ "allowdrop")>*:not([allowdrop]){
pointer-events: none;
}
這里有個(gè) CSS 小技巧,上面例子在拖放過程中的文字提示變化其實(shí)是通過偽元素實(shí)時(shí)變化的。
你也可以查看在線鏈接:自定義 dragover (codepen.io)[1]或者自定義 dragover (juejin.cn)[2]。
五、總結(jié)和說明
以上就是自定義 dragover 效果的完整實(shí)現(xiàn)了,不算復(fù)雜,但也有一些小技巧,特別是借助了 CSS 的能力。其實(shí)在這一版實(shí)現(xiàn)之前,我還嘗試過很多別的實(shí)現(xiàn),但都不如這種方式簡(jiǎn)潔明了,下面總結(jié)一下:
- 為了更好的體驗(yàn),可以在拖拽過程中給與用戶適當(dāng)?shù)淖兓崾尽?/li>
- 主要實(shí)現(xiàn)方法在于 dragenter 和 dragleave。
- 當(dāng)放置目標(biāo)存在子元素時(shí),也會(huì)觸發(fā) dragleave 事件,干擾原有邏輯。
- 可以移除 dragleave 去除子元素的干擾,dragenter 需要先移除再添加 over。
- 通過 CSS pointer-events 可以去除子元素的干擾。
- 如果有多層可放置結(jié)構(gòu),可以通過 :not 過濾可放置目標(biāo)。
- 通過鼠標(biāo)指針也可以改善交互體驗(yàn)。
- 在 DOM 操作中千萬(wàn)不要忘記了 CSS,這點(diǎn)很重要。
當(dāng)然,拖拽在頁(yè)面中的交互細(xì)節(jié)還有很多,比如拖拽排序過程中的擠壓動(dòng)畫效果,后面有空再研究吧,爭(zhēng)取出一個(gè)通用的解決方案。
參考資料
[1]自定義 dragover (codepen.io): https://codepen.io/xboxyan/pen/yLvjXdJ
[2]自定義 dragover (juejin.cn): https://code.juejin.cn/pen/7104250686161813540
網(wǎng)站欄目:讓拖拽更加人性化-如何自定義Dragover樣式?
網(wǎng)站地址:http://fisionsoft.com.cn/article/dhcccie.html


咨詢
建站咨詢
