36

When starting to drag an element using HTML5 draggable attribute, original element is still visible, so I end up having two elements visible instead of one.

How can I do to have only the element being dragged visible (the original one should be momentarily hidden).

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

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

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

Here's a jsfiddle to show the problem https://jsfiddle.net/gjc5p4qp/

julesbou
  • 5,570
  • 4
  • 31
  • 36

7 Answers7

28

You may succeed this with a hacky solution. The native draggability doesn't allow CSS styles like: opacity:0;, visibility:hidden or display:none.

But you can do it using: 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)"
/>

I've updated your JSFiddle with the solution.

[EDIT]:

Updated JSFiddle example with Andrew Hedges suggestion by using requestAnimationFrame instead setTimeout.

[EDIT 2]:

Updated with a better solution by Jason Yin adding transition CSS property instead using requestAnimationFrame, it moves the processing from scripting to rendering.

Filipe
  • 1,788
  • 2
  • 13
  • 18
  • Wow that's really good, do you know how to set opacity of dragged element to 1 ? – julesbou Apr 02 '16 at 23:13
  • Haha, thanks. I couldn't find a way to do that, seems the dragged "ghost element" isn't avaible at the DOM, so we get some limitations. Check out this [answer](http://stackoverflow.com/a/10904112/3938676) – Filipe Apr 02 '16 at 23:19
  • @julesbou Filipe is there still no proper solution to this? – oldboy May 13 '17 at 05:34
  • why do you have to use setTimeout?! it glitches out without it. – oldboy May 13 '17 at 05:48
  • @Anthony after you click the element, the browser 'copies' the element, if the element is hidden the browser will copy a transparent element. The timeout allows the browser to copy and only then hide the element. – Filipe May 13 '17 at 13:16
  • @Filipe thanks, i didn't know that. drag and drop is new to me :) – oldboy May 16 '17 at 21:29
  • @Filipe then wouldn't there be a way to remove the transparency of the "copied" object?? – oldboy May 17 '17 at 05:16
  • Transitions with really small durations removes the needs for the timeout if anyone was wondering – Rico Kahler Apr 05 '18 at 04:03
15

Since simply setting visibility: hidden doesn't work, I found another somewhat hacky solution: set visibility: hidden one millisecond after the drag event starts.

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>
Hatchet
  • 5,320
  • 1
  • 30
  • 42
  • 9
    Rather than use a `setTimeout`, you can use `window.requestAnimationFrame` for this: `function startDrag(e) { window.requestAnimationFrame(function() { e.target.style.visibility = "hidden"; }); }` – Andrew Hedges Oct 03 '16 at 04:34
  • 1
    @AndrewHedges any idea how to prevent the lowering of opacity?! – oldboy May 13 '17 at 05:55
  • @Anthony Have you found a solution for mimicking old way drag and drop (with no original element visible and no opacity in drag copy)? – Rantiev Aug 08 '18 at 09:53
  • @Rantiev yes, you basically have to create your own drag and drop functionality from scratch, and actually move the element which is being moved by the user, which isn't as difficult as it might seem. use `mousedown`, `mouseup`, `mousemove`, `clientX`, `clientY`, etc – oldboy Aug 09 '18 at 13:53
  • @anthony So, this means we can't mimic, we just have to use old way? I already have it... just had some weird issue with FF, where i had to attach dragstart listener and call e.preventDefault() inside, to prevent drag stucking there. That's why i started to look into the new way (creator of which need to burn in hell forever) – Rantiev Aug 10 '18 at 09:00
  • @Rantiev tbh, im not sure if its possible now. i havent messed around with drag and drop for a while, so... – oldboy Aug 10 '18 at 21:22
14

This could be achieved without hacky trick from the previous answers.

The key point is to listening to the drag event, instead of the 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>
didxga
  • 5,935
  • 4
  • 43
  • 58
4

How to hide your element

If you want to hide your element, you can use the CSS property visibility:hidden or display:none.

I recommend using the visibility property because it's hides an element without changing the layout of the document. But, if you're element have childrens and you want to hide them to, you need to set up visibility:inherit on each of childrens.

When to hide it

You were right to use the dragstart event. But the clone of the element who is created by the draggable attribute appears at the end of the dragstart event.

Because JavaScript engine is a single-threaded interpreter if you choose to hide it in here, you're element will be masked as its clone which will copy the property visibility:hidden. In fact, to avoid this you need to hide it after the creation of the clone in the Javascript callstack.

To do it, use the setTimout() function and set it to 0 ms. This way, the masking of the original element is put at the end of the stack, after the creation of his clone.

At the end of the drag, to make it reappear, you just need to set the element visible by calling visibility:visible in the dragend event.

Code exemple

For you're exemple, the code can look like this :

<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>
PaulCrp
  • 624
  • 1
  • 8
  • 19
2

Filipe's answer is really helpful. If setTimeout or requestAnimationFrame doesn't work for you. try adding transition.

transition: transform 0.01s;
transform: translateX(-9999px);
Mario Petrovic
  • 7,500
  • 14
  • 42
  • 62
Jason Yin
  • 21
  • 1
1

You can add to your dragStart event this code:

event.dataTransfer.setDragImage(new Image(), 0, 0); // Hide the dragged element

It will hide your target dragged element.

arielhad
  • 1,753
  • 15
  • 12
0

Found this: https://stackoverflow.com/a/35120001/1807542

With a clean solution to hide the source/original element in the dragover handler instead.

var dragging;
function dragstart(e) {
  dragging = $(this);
}

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

function dragend(e) {
  if (dragging) {
    dragging.show();
    dragging = undefined;
  }
}
Michel Jansson
  • 1,803
  • 1
  • 13
  • 14