Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

HTML5 Drag'n'Drop - Not Working on iOS 12.1.2 (Safari and Chrome)

Background

I have a list that is sortable via drag and drop. It works perfectly on desktop browser, and on Chrome on Android. However, it didn't work at all on Safari and Chrome on iOS 12.1.2 (iPhone 8).

Current Code

See snippet below, and also for easy mobile testing: https://codepen.io/Kelderic/pen/KJMRgb

var dragging = null;

document.addEventListener('dragstart', function(event) {
  var target = getLI(event.target);
  dragging = target;
  event.dataTransfer.setData('text/plain', null);
  event.dataTransfer.setDragImage(self.dragging, 0, 0);
});

document.addEventListener('dragover', function(event) {
  event.preventDefault();
  var target = getLI(event.target);
  var bounding = target.getBoundingClientRect()
  var offset = bounding.y + (bounding.height / 2);
  if (event.clientY - offset > 0) {
    target.style['border-bottom'] = 'solid 4px blue';
    target.style['border-top'] = '';
  } else {
    target.style['border-top'] = 'solid 4px blue';
    target.style['border-bottom'] = '';
  }
});

document.addEventListener('dragleave', function(event) {
  var target = getLI(event.target);
  target.style['border-bottom'] = '';
  target.style['border-top'] = '';
});

document.addEventListener('drop', function(event) {
  event.preventDefault();
  var target = getLI(event.target);
  if (target.style['border-bottom'] !== '') {
    target.style['border-bottom'] = '';
    target.parentNode.insertBefore(dragging, event.target.nextSibling);
  } else {
    target.style['border-top'] = '';
    target.parentNode.insertBefore(dragging, event.target);
  }
});

function getLI(target) {
  while (target.nodeName.toLowerCase() != 'li' && target.nodeName.toLowerCase() != 'body') {
    target = target.parentNode;
  }
  if (target.nodeName.toLowerCase() == 'body') {
    return false;
  } else {
    return target;
  }
}
ul.sorting {
  padding: 0;
  margin: 0;
  list-style: none;
  max-height: 300px;
  overflow-y: auto;
  box-shadow: inset 0 0 3px 1px rgba(0, 0, 0, 0.2);
}

ul.sorting li {
  padding: 10px;
  border-bottom: 1px solid black;
  user-select: none;
  cursor: move;
}

ul.sorting li:last-child {
  border-bottom: none;
}
<ul class="sorting">
  <li draggable="true" style="user-drag:element;">List Item 15</li>
  <li draggable="true" style="-webkit-user-drag:element;">List Item 2</li>
  <li draggable="true" style="-webkit-user-drag:element;">List Item 3</li>
  <li draggable="true" style="-webkit-user-drag:element;">List Item 4</li>
  <li draggable="true" style="-webkit-user-drag:element;">List Item 5</li>
  <li draggable="true" style="-webkit-user-drag:element;">List Item 6</li>
  <li draggable="true" style="-webkit-user-drag:element;">List Item 7</li>
  <li draggable="true" style="-webkit-user-drag:element;">List Item 8</li>
  <li draggable="true" style="-webkit-user-drag:element;">List Item 9</li>
  <li draggable="true" style="-webkit-user-drag:element;">List Item 10</li>
  <li draggable="true" style="-webkit-user-drag:element;">List Item 11</li>
  <li draggable="true" style="-webkit-user-drag:element;">List Item 12</li>
</ul>

Video of Behavior on iOS

https://imgur.com/a/I8hzPxC (Size is too large for direct embed)

Question

Why is this not working on iOS? I can't even get the dragstart event to fire. On Android Chrome, a longpress fires the dragstart.

My fallback idea is to make a longpress using touchstart and touchend, then create my own absolutely positioned ghost element and drag it around manually. That is a lot of extra code when the drag* events should just work.

Am I missing something?

Edit

LI elements in mobile Safari have an ondragstart event as part of their prototype. The question is getting it to fire.

Also, according to caniuse, mobile Safari should support this. However, caniuse also shows Android Chrome as not supporting, which isn't true.

like image 497
Andy Mercer Avatar asked Jan 17 '19 14:01

Andy Mercer


3 Answers

I have done a lot of research over the past week on this topic, and I've done a lot of testing and debugging. What I have discovered is that iOS supports drag events, but iOS doesn't trigger them. (As of iOS 12)

If you test webpage on iOS Safari, you'll see that all HTML elements have ondragstart attached to their prototype. The drag events are all there. They just never get triggered.

Others have run into this issue as well. Event Modernizer doesn't detect Drag and Drop support because of this false support.

Additionally, I dug through the Safari docs, and found this:

iOS Note: Although drag and drop are not supported, you can produce...

It says "supported", but immediately below, there is an event table, which shows drag and says that it isn't "Generated".

This means that caniuse is currently wrong, and needs updated.


Direct Answer To Question

The code is not working solely because Apple has chosen not to trigger drag events.

like image 93
Andy Mercer Avatar answered Sep 20 '22 23:09

Andy Mercer


Unfortunately I do not have a device with iOS12 (my device does not suppot it) and I can not test it. And I do not know why iOS/Apple support writes that they support this events in iOS 11.2 and on your new device it is not supported.

Alternative workaround

With this workaround you will get a solution for all modern devices and browsers. It is better than to wait for support from iOS/Apple.

dragstart, dragover, dragleave, drop events do not work on all touch devices.

You have to use the apropriate event names for your touch events like:

  • touchstart
  • touchend
  • touchcancel
  • touchmove

Read the documentation about this events.

In order to provide quality support for touch-based user interfaces, touch events offer the ability to interpret finger (or stylus) activity on touch screens or trackpads.

The touch events interfaces are relatively low-level APIs that can be used to support application specific multi-touch interactions such as a two-finger gesture. A multi-touch interaction starts when a finger (or stylus) first touches the contact surface. Other fingers may subsequently touch the surface and optionally move across the touch surface. The interaction ends when the fingers are removed from the surface. During this interaction, an application receives touch events during the start, move and end phases.

Touch events are similar to mouse events except they support simultaneous touches and at different locations on the touch surface. The TouchEvent interface encapsulates all of the touch points that are currently active. The Touch interface, which represents a single touch point, includes information such as the position of the touch point relative to the browser viewport.

Unfortunatelly, the events touchenter and touchleave were deleted from the specification and because of this if you need it then you have to write a workaround using document.elementFromPoint() like follows:

document.addEventListener('touchmove', function(e)
{
    var touch = e.touches[0],
        elementFromPoint = document.elementFromPoint(touch.pageX - window.pageXOffset, touch.pageY - window.pageYOffset);

    if(elem == elementFromPoint) //elem is your element which was entered
    {
        //your code for touchenter event
    }
    else //the code for touchleave event
});

Maybe for some other mobile devices and their browsers you will need to use polyfills (1, 2) which enable HTML5 drag drop support on mobile (touch) devices.

like image 29
Bharata Avatar answered Sep 17 '22 23:09

Bharata


I forked your codepen test case here: https://codepen.io/bobacus/pen/MLjZMg

I was wondering if the listeners needed to be added to the <li> elements, so changed some of the code:

listEl = document.getElementById('my_list');
for (var i = 0; i < listEl.children.length; i ++) {
    itemEl = listEl.children[i];
    itemEl.addEventListener('dragstart', function(event) {
      dragging = getLI(event.target);
      if (dragging) {
          event.dataTransfer.setData('text/plain', null);
          event.dataTransfer.setDragImage(dragging, 0, 0);
      }
    });
}

But that didn't work.

I tried a few different things, but what finally got it to work in Safari was adding a polyfill described here: https://www.codeproject.com/Articles/1091766/Add-support-for-standard-HTML-Drag-and-Drop-operat

I added this to the top of the HTML snippet:

<script src="http://bernardo-castilho.github.io/DragDropTouch/DragDropTouch.js"></script>

I hope this is helpful.

like image 31
Rob Avatar answered Sep 21 '22 23:09

Rob