Shopify / draggable

The JavaScript Drag & Drop library your grandparents warned you about.
https://shopify.github.io/draggable
MIT License
17.96k stars 1.09k forks source link

Constrain dragging to an element #130

Open jarben opened 6 years ago

jarben commented 6 years ago

It would be great if there is a plugin (or mirror option) that allows to constrain dragging inside a specific element. For example:

const sortable = new Sortable(el, {
    draggable: 'li',
    mirror: {
        yAxis: false,
        constrain:el
    }
});
tsov commented 6 years ago

Thanks for your question! You should be able to use the Collidable plugin for that:

import {Sortable, Plugins} from '@shopify/draggable';

const sortable = new Sortable(containers, {
  draggable: 'li',
  collidables: 'body:not(.cant-move-out-of-this-element)'
});

The Collidable plugin is still very simple and limited, but should give you the effect you want. For more information check out the docs about Collidable: https://github.com/Shopify/draggable/tree/master/src/Plugins/Collidable

Let me know if that worked for you

beefchimi commented 6 years ago

Hey @jarben - so you are looking for something that does not allow you to drag the mirror outside of a container (bounding box)? I suspect in this scenario the mirror originates from within the container, can be moved around within the container, but cannot be moved OUTSIDE of the container. Am I getting this right? Image example below showing the blue mirror unable to cross the black border container:

example

As @tsov mentioned - Collidable could be used for this... but it would be a different implementation than what I think you are looking for. Essentially (if I understand this correctly), you would need to have invisible elements on all sides of the container that are the "collidables" with which your mirror cannot cross.

I can see the possibility of extending the mirror to allow for a constrainToParent option (a boolean?) or a constrainToContainer which could be any ancestor node passed to the property. @tsov and I will chat about this - not sure if we add something to the mirror or expand on the capabilities of Collidable.

As an alternative, you could definitely code this logic within the drag:move event. Basically, you would get the bounding box of the container and compare the coordinates with your mouse position while moving around. Then, you could cancel the event or do something else to prevent the mirror from moving outside. The Intersection Observer API may also be your friend here.

Let us know if that makes sense! Cheers 😄

jarben commented 6 years ago

Hey @tsov & @beefchimi, thanks for your quick answers!

Yeah, the constrainToParent or constrainToContainer is exactly what I'm trying to achieve. Tried collidables on the parent container but that didn't seem to do the trick, I'll create an online JS fiddle and try to reproduce.

Good point around the drag:move event, will give it a try!

Regarding API design, it kind of makes sense to put it beside the xAxis/yAxis constraints doesn't it? Maybe a container property? Collidables feels a bit more external and related to drag source -> drag target (like shaking invalid target etc) so restricting mirror (source) might not fit into it.. not 100% sure thought!

Anyway, thanks again for your answers and this great library!!!

dmitrybelyakov commented 5 years ago

Hi! Would really love this feature as well. Could you please add it?

dmitrybelyakov commented 5 years ago

Also neither the collidable nor listening to drag:move actually allows to solve the issue.

greendrake commented 3 years ago

Where are we with this feature now? Any progress? Is there any more simple and straightforward way to achieve this than via a drag:move handler?

nnnell commented 1 year ago

In the meantime, this drag:move handler has been working reasonably well for me:

draggableInstance.on('drag:move', (event) => {
  let mirror = document.querySelector('.draggable-mirror')
  let gameContainer = document.getElementById('game-container')
  let bounds = gameContainer.getBoundingClientRect()
  // To give a little extra room around the edges:
  let boundsPaddingX = 50
  let boundsPaddingY = 60

  let minX = (bounds.x - boundsPaddingX) + (mirror.offsetWidth / 2)
  let maxX = (bounds.x + bounds.width + boundsPaddingX) - (mirror.offsetWidth / 2)
  let minY = (bounds.y - boundsPaddingY) + (mirror.offsetHeight / 2)
  let maxY = (bounds.y + bounds.height + boundsPaddingY) - (mirror.offsetHeight / 2)

  let ex = event.sensorEvent.clientX
  let ey = event.sensorEvent.clientY

  if (ex < minX || ex > maxX || ey < minY || ey > maxY) {
    event.cancel()
  }
})