Search code examples
htmldraggablehtml5-draggable

Find out in dragend event if drag was cancelled


I'm dragging divs around. Sometimes, I want to cancel the dragging by hitting the keyboard escape key. This ends the drag (the "dragend" is fired) but I do not seem to be able to figure out how in my "dragend" handler I can distinguish if the drag was successfully completed or cancelled. Is there a way?

Note that I'm not using the automatic copy/move/data transfer mechanism, because I have specific things that happen when dragging objects that are different from simply moving the object around in the DOM. Therefore, the "dropeffect" is always "none" in my case.

More generally, I would very much like to receive "keydown" events during the drag, but they all vanish completely. This is exceptionally annoying since I do want to make a UI that has great keyboard support and one of the features I want is to be able to modify the element during the drag (e.g., imagine a "snapping" functionality that you want to turn on or off during the drag).

I do believe the latter one is impossible, unfortunately. But is at least the former doable?


Solution

  • I think you are intended to determine what happened with respect to the drag through the dropEffect of the dataTransfer object of the drag event. It it's 'none', it didn't work. Anything else is successful.

    Firefox has a prefixed prop mozUserCancelled that will tell you if it was aborted, but it's not standard.

    I looked through the Processing model spec and the best mention of the processing of the "escape" key doesn't really differentiate from other aborts:

    1. If the current drag operation is "none" (no drag operation), or, if the user ended the drag-and-drop operation by canceling it (e.g. by hitting the Escape key), or if the current target element is null, then the drag operation failed. Run these substeps:

      1. Let dropped be false.

      2. If the current target element is a DOM element, fire a DND event named dragleave at it; otherwise, if it is not null, use platform-specific conventions for drag cancelation.

      3. Set the current drag operation to "none".

    const dragEl = document.querySelector('#drag');
    const dropEl = document.querySelector('#drop');
    
    dragEl.addEventListener('drag', (e) => {
      e.preventDefault();
    });
    
    dragEl.addEventListener('dragend', (e) => {
      e.preventDefault();
    
      //console.log(e.dataTransfer);
    
      console.log(`mozUserCancelled: ${e.dataTransfer.mozUserCancelled}`);
      console.log(`dropped: ${e.dataTransfer.dropped}`);
      console.log(`dropEffect: ${e.dataTransfer.dropEffect}`);
    });
    
    dropEl.addEventListener('dragenter', (e) => {
      e.preventDefault();
    });
    
    dropEl.addEventListener('dragover', (e) => {
      e.preventDefault();
    });
    
    dropEl.addEventListener('drop', (e) => {
      e.preventDefault();
    
      console.log('dropped');
    
      const t = e.dataTransfer.getData('text/plain');
      console.log(t);
    });
    #drag,
    #drop {
      padding: 1em;
      margin: 1em;
      background-color: lightblue;
      color: white;
    }
    <div id="drag" draggable="true">Drag</div>
    
    <div id="drop">Drop</div>