HTML5可拖动元素隐藏原始元素

36

使用HTML5的draggable属性拖动一个元素时,原始元素仍然可见,导致有两个元素可见,而不是一个。

我应该怎么做才能只看到被拖动的元素(原始元素应暂时隐藏)?

<script>
  function startDrag() {
    // hide initial element
  }

  function endDrag() {
    // reset initial element
  }
</script>

<div class="draggable" draggable="true"
  ondragstart="startDrag(event)"
  ondragend="endDrag(event)"
></div>

这里有一个 jsfiddle,用于展示问题https://jsfiddle.net/gjc5p4qp/


最好的黑客在这里https://stackoverflow.com/a/69869851/22957928 - undefined
7个回答

28

您可以通过一种不太正规的方法来实现这一点。本机的可拖动性不允许使用CSS样式,如:opacity:0;visibility:hiddendisplay:none

但是您可以使用:transform:translateX(-9999px) 来实现它。

function startDrag(e) {
  let element = e.target;
  
  element.classList.add('hide');
}

function endDrag(e) {
  let element = e.srcElement;
  
  element.classList.remove('hide');
}
.draggable {
  border-radius: 4px;
  background: #CC0000;
  width: 40px;
  height: 40px;
}
.hide {
  transition: 0.01s;
  transform: translateX(-9999px);
}
<div
  class="draggable"
  draggable="true"
  ondragstart="startDrag(event)"
  ondragend="endDrag(event)"
/>

我已经使用解决方案更新了您的JSFiddle

[编辑]:

根据Andrew Hedges的建议,更新了JSFiddle示例,使用requestAnimationFrame而不是setTimeout

[编辑 2]:

通过添加transition CSS属性而不是使用requestAnimationFrameJason Yin提供了更好的解决方案,它将处理从脚本转移到渲染。


@julesbou Filipe,这个问题还没有一个合适的解决方案吗? - oldboy
为什么你必须使用setTimeout?!没有它会出现故障。 - oldboy
@Anthony 在您点击元素后,浏览器会“复制”该元素,如果该元素是隐藏的,则浏览器将复制一个透明元素。超时时间允许浏览器复制并随后隐藏该元素。 - Filipe
@Filipe 谢谢,我不知道那个。拖放对我来说是新的 :) - oldboy
@Filipe 那么有没有办法去除“复制”对象的透明度呢? - oldboy
显示剩余3条评论

15

由于简单地设置visibility: hidden不起作用,我找到了另一个有点hacky的解决方案:在拖动事件开始后的一毫秒内设置visibility: hidden

function startDrag(e) {
  setTimeout(function() {
    e.target.style.visibility = "hidden";
  }, 1);
}

function endDrag(e) {
  setTimeout(function() {
    e.target.style.visibility = "";
  }, 1);
}
body {
  display: inline-block;
  outline: 1px solid black;
}
.draggable {
  border-radius: 4px;
  background: #CC0000;
  width: 40px;
  height: 40px;
}
<div class="draggable" draggable="true" ondragstart="startDrag(event)" ondragend="endDrag(event)">

</div>


9
不必使用setTimeout,您可以使用window.requestAnimationFrame实现此功能:function startDrag(e) { window.requestAnimationFrame(function() { e.target.style.visibility = "hidden"; }); } - Andrew Hedges
1
@AndrewHedges 你有什么办法可以防止降低不透明度吗?! - oldboy
@Anthony,你找到模仿旧的拖放方式(没有原始元素可见且拖动副本中没有不透明度)的解决方案了吗? - Rantiev
@Rantiev 是的,基本上你需要从零开始创建自己的拖放功能,并实际移动用户正在移动的元素,这并不像看起来那么困难。使用 mousedownmouseupmousemoveclientXclientY,等等。 - oldboy
@anthony 这意味着我们不能模仿,只能使用旧的方式?我已经有了... 只是在 FF 中遇到了一些奇怪的问题,必须附加 dragstart 监听器并在内部调用 e.preventDefault(),以防止拖动停滞不前。这就是为什么我开始研究新方法(其创建者需要永远被关进地狱)。 - Rantiev
@Rantiev 说实话,我现在不确定是否可能。我已经有一段时间没有尝试拖放操作了,所以... - oldboy

15

不需要使用以前答案中的hacky技巧即可实现此目标。

关键是监听drag事件,而不是dragstart事件。

//listen to drag event, not dragstart!
document.querySelector(".draggable").addEventListener("drag", (e)=>{
  e.target.classList.add("dragging");
});
document.querySelector(".draggable").addEventListener("dragend", (e)=>{
  e.target.classList.remove("dragging");
});
.draggable {
  width: 200px;
  height: 30px;
  background-color:#5856d6;
  text-align:center;
  line-height:30px;
}

.dragging {
  background: transparent;
  color: transparent;
  border: 1px solid #5856d6;
}
<div draggable="true" class="draggable">Drag me!</div>


5

如何隐藏您的元素

如果要隐藏元素,可以使用CSS属性 visibility:hiddendisplay:none

我建议使用visibility属性,因为它可以隐藏元素而不改变文档的布局。但是,如果您的元素有子元素,并且您想要将它们隐藏,您需要在每个子元素上设置visibility:inherit

何时隐藏它

您正确地使用了dragstart事件。 但是,由可拖动属性创建的元素的克隆体出现在dragstart事件的末尾。

由于JavaScript引擎是单线程解释器,如果你选择在这里隐藏它,你的元素将被掩盖为它的克隆体,它将复制属性visibility:hidden。实际上,为了避免这种情况,你需要在Javascript调用堆栈中创建克隆体后再隐藏它。
要做到这一点,使用setTimout()函数,并将其设置为0毫秒。这样,原始元素的掩码就被放置在堆栈的末尾,在创建它的克隆体之后。
在拖动结束时,为了使它重新出现,你只需要在dragend事件中调用visibility:visible来设置元素可见即可。

代码示例

对于你的示例,代码可以像这样:

<div 
    class="draggable" 
    draggable="true" 
    ondragstart="startDrag(event)" 
    ondragend="endDrag(event)" 
    style="background: #e66465; color:white; width:80px; height:20px; text-align:center;">
    Drag me
</div>

<script>
    function startDrag(e) {
        setTimeout(function(){
            e.target.style.visibility = "hidden";
        }, 0);
    }
    function endDrag(e){
        e.target.style.visibility = "visible";
    }
</script>


2

Filipe的回答真的很有帮助。如果setTimeoutrequestAnimationFrame不起作用,请尝试添加过渡效果。

transition: transform 0.01s;
transform: translateX(-9999px);

1
您可以在dragStart事件中添加以下代码:
event.dataTransfer.setDragImage(new Image(), 0, 0); // Hide the dragged element

它将隐藏您拖动的目标元素。

这是一个非常有用的答案,而且只有一行代码,谢谢! - kirbby

0
发现这个:https://dev59.com/MZPfa4cB1Zd3GeqPEINc#35120001 在dragover处理程序中,有一个干净的解决方案来隐藏源/原始元素。
var dragging;
function dragstart(e) {
  dragging = $(this);
}

function dragover(e) {
  dragging.hide();
}

function dragend(e) {
  if (dragging) {
    dragging.show();
    dragging = undefined;
  }
}

hide()show()是jQuery方法。 - shmup

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接