在 web 开发中,让拖经常会碰到需要拖拽的拽更场景。为了更好的加人体验,拖拽区域需要有一定的性化变化提示,告诉用户:"现在可以放在这里了~",何自例如这样的定义。 dragover效果 这次接着探索一下如何自定义 dragover 样式。样式 要实现这样的拽更效果,少不了和dragenter和dragleave打交道。加人 拖拽目标和放置目标 假设现在有这样一个结构,让拖这里 img是拖拽目标,div.content是放置目标。 然后在document监听一下; document.addEventListener(dragleave, function(ev) { console.log(dragleave, ev.target) }) document.addEventListener(dragenter, function(ev) { console.log(dragenter, ev.target) 那么,将img拖入div.content的过程中,肯定会触发dragenter和dragleave这两个事件,如下: dragenter和dragleave 如果页面比较简单,要自定义拖拽过程就比较容易了; 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; 效果如下: 是站群服务器不是非常容易呢? 实际使用起来其实还存在很多局限性,下面一一介绍。 大部分情况下,放置目标并不是空的,还有其他子元素,如果采用上面的方式就会有问题了,假设布局是这样的,为了区分,可以给需要放置的元素添加一个属性,比如allowdrop,表示允许放置; 这里通过属性区分一下: 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); } 效果如下: 有子元素的情况下 可以看到,当拖拽目标经过子元素时,外面的样式已经丢失了。原因其实很简单,在经过子元素时,放置目标也触发了dragleave事件! 那有没有办法不触发呢?这里有两种方式: 首先可以取消dragleave的监听,因为在执行dragleave时,元素本身是不知道即将进入哪一个区域,很容易“误伤”。取而代之的亿华云计算是每次dragenter时,先移除上一次放置目标的属性,然后再添加新的,有点类似选项卡的操作,具体实现如下: var lastDrop = null; document.addEventListener(dragenter, function(ev) { if (lastDrop) { lastDrop.toggleAttribute(over,false); } const dropbox = ev.target.closest([allowdrop]); // 获取最近的放置目标 if (dropbox) { dropbox.toggleAttribute(over,true); lastDrop = dropbox; } 还有另一种方式:借助 CSS 就非常容易了。 这里有一个非常简单粗暴的方式,直接将子元素禁用鼠标响应,如下: .content[allowdrop](empty::after{ "allowdrop") *{ pointer-events: none; 这样,在滑过任何子元素都不会有响应了,完美。 有子元素的情况,完美 上面这种方式其实可以解决大多数问题了,毕竟大部分场景都是扁平的。不过有时候也会碰到多层结构,比如那种可视化编辑工具,尤其是目前比较火的低代码平台,就会涉及到多层结构,假设 HTML 是这样的。 如果按照 CSS 的云服务器提供商处理方式(JS 方式没有问题),由于所有子元素都被禁用,里面的结构自然也无法响应了。 多层嵌套结构无响应 那如何让里面的放置目标可以响应呢?其实只需要改一下上面的 CSS 即可,如下: .content[allowdrop](empty::after{ "allowdrop")>*:not([allowdrop]){ pointer-events: none; 这里使用了>选择器,表示只选择子元素,不包含后代元素,然后排除掉放置目标,这样就能实现多层嵌套了,效果如下: 多层嵌套结构,完美 是不是出乎意料的简单呢? 不知道大家发现没,上面的例子在拖拽开始,鼠标就一直处于这种“可放置”状态,不管是在放置目标外部还是内部,如下: 鼠标指针状态 这是因为设置了dragover属性,所以整个document都变成了可放置目标,都允许触发drop事件。 document.addEventListener(dragover, function(ev){ ev.preventDefault() 如果希望交互更加细腻,体验更好,那么在鼠标指示上也可以进一步的优化,可以在进入放置目标后才变成这种状态,实现如下: document.addEventListener(dragover, function(ev){ const dropbox = ev.target.closest([allowdrop]); if (dropbox) { ev.preventDefault() } 效果如下(注意观察鼠标的变化 dragover效果