The HTML5 Drag and Drop API is one of those APIs on the web platform that I find people either despise or begrudgingly tolerate. It’s not that said API is particularly tricky to use or implement, but it’s non-existence on mobile without polyfills or shims is painful to get correct without a lot of code, or if you’ve tried to use them with shadowRoots and web components, the situation can be doubling confusing.
Most of that confusion comes from the examples or libraries that folks use that are really only looking at
event.target, which people find just don’t work. The reason becomes pretty clear if you look at any web component with some depth of a shadowRoot:
event.target gives you to the top most element you’re hoving over and that’s probably not what you want. What to do?
event.composedPath() to the rescue.
Events, events, everywhere
The problem that folks run into is that the using
event.target is not going to give you the depth you want against the delegation of said event. It’ll tell us the element on which the event occurred, which in most cases will be our top most web component. Similarly,
event.curentTarget is going to reference where our handler has been attached. While this is a bit of a simplification, the general gist is that we won’t exactly have what we need when it comes to things like ShadowRoots.
Instead, what we want to use is event.composedPath(). This gives us the array of nodes and objects where our listeners are going to be invoked, including within the scope of shadowRoots (as long as the mode is not closed).
As an example, let’s look a screenshot to see what exactly a
DragEvent targets when web components are involved:
In Box #1 above, you can see that the
event.target is set to a
main-view web component. However, we also see that our
main-view and a lot of other nodes. Box #2 actually points to the a
drop-list component in our
path which is what we want to operate against.
How do we work with this? Let’s look a few examples.
To help illustrate this concept let’s mock-up at two simple vanilla web components,
drop-list-item is going to be a simple thing we can drag around, so it needs a few things:
- It needs to know it’s a draggable
- It needs to tell us when it is be dragged
- It should listen for other things being dragged over it (in case maybe we wanted to sort a list or something)
To handle this, we write up the basics:
As you can see above, we’re not doing a lot of heavy lifting at all. We set some attributes based on the events so we can apply some style and as you’ll see, so we can figure out who we might need to grab in our drop zone.
How does this relate to a dropzone? Let’s look at
drop-list and see what magic is under the hood:
In the component above, we can see that our dragOver event handler does some lifting, taking into account the
event.composedPath(), grabbing the node (which is usually the first item in the array) and then looking through that node to see if we have a
Since it’s a bit hard to visualize, the video I made below shows the behavior in action across a wide range of examples (many of which comes from questions I’ve received lately).
All the examples are available all on the demo site as well if you’d like to give them a spin.
Making it work on mobile
That’s all fine and dandy Justin, but what about mobile you say? None of the polyfills work and what is a person to do?
This entire walk-through stems from that very question. Most of the polyfills or various shims don’t use
event.composePath() and as such and this became a pain in my side (I did not have this lying around in my private toolbelt as this hasn’t been a huge ask over the years). Alas, we need something.
If you happened to follow the the examples above or watched the video, you’ll note that they live in justinribeiro/html5-dragdroptouch-shim repo. That repo is my opinionated shim that polyfills HTML5 drag and drop support on mobile devices with Event.ComposedPath() support. While this is in large part an ES Modules refactor of Bernado’s dragdroptouch polyfill (which deserves the bulk of the love by the way), this version differs in two keys areas:
Re: finding the draggable. Uses event.composedPath() to allow use to hunt for draggables within open ShadowRoots
Re: finding the dropzone. Uses event.composedPath() to find the target shadowRoot, then uses DocumentOrShadowRoot.elementFromPoint to locate our dropzone target.
This allows it to be more readily be used with ShadowDOM and web components, which is my primary use case for it to be honest. It does however work fine without web components use case for mobile as well.
It’s early days for said shim, but I’ve used on couple projects without issue. It is available on NPM and if you find bugs, do let me know.
Now I’m not saying this is the end all be all; there is more you could do (I don’t go into list sorting for instance) and you surely would firm up those web components (ala…ditch those events on disconnect and be a good component citizen). Hopefully this gives you a spring board that using the bare metal web platform and the tooling it gives you is not the scary complicated beast some folks make it out to be. We have the tools, you just have to hone in on the right one’s for the job.
So get out there, explore the web platform, and build some cool stuff. 🎉🎉🎉