Shopify / draggable

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

[bug / feature req] Drag offset error when inside transform: scale() #139

Open bjorsen opened 6 years ago

bjorsen commented 6 years ago

We have a drag and drop environment that can be scaled by the end user (for instance zoom out to get a better overview).

What happens technically is that a CSS transform of scale is applied to the parent container of the drag and drop region. After this has happened, the drag and drop behaviour is "off". As soon as you click on a draggable item, it moves a distance away from its original position because the drag and drop behaviour does not take the transform into account.

beefchimi commented 6 years ago

@bjorsen is there any chance you could cook up a example for me to use? You can either create a codepen, or open up a branch that adds a new example to the examples/ folder of this repo. It will help a lot πŸ˜„

Otherwise, perhaps you can provide some screenshots of your issue along with a few code snippets?

Thanks!

tsov commented 6 years ago

Let us know @bjorsen πŸ™‚

bjorsen commented 6 years ago

Apologies all, it's been hectic and I haven't had the time to further look into this project. I'll try to make some time over the weekend to set up a minimalistic example with the isolated issue.

jacobbridges commented 6 years ago

πŸ‘‹ Hello @beefchimi and @tsov! I ran into this issue as well -- that is to say when dragging an item within a scaled container, not only is the initial drag offset off but also the item will get "out of sync" with the cursor the further it is moved from the origin point.

Example codepen: https://codepen.io/jacobbridges/pen/RQdwVQ (Sorry for the random colors, just tried to make a simple example.)

beefchimi commented 6 years ago

Hey @jacobbridges , thanks for making a Codepen!

I tinkered with it a bit... but something else was funky and the solution I had in mind wasn't behaving. So, I'm going to experiment with this in the Examples project when I get the chance... hopefully over the weekend.

Apologies for the delay on resolving this. I'll investigate and report back πŸ‘

bjorsen commented 6 years ago

Hi @jacobbridges, thanks for picking up my slack in putting up an example illustrating the issue. I've been dealing with medical issues in the family every spare moment lately.

On Thu, 1 Mar 2018, 23:13 Curtis Dulmage, notifications@github.com wrote:

Hey @jacobbridges https://github.com/jacobbridges , thanks for making a Codepen!

I tinkered with it a bit... but something else was funky and the solution I had in mind was behaving. So, I'm going to experiment with this in the Examples project when I get the chance... hopefully over the weekend.

Apologies for the delay on resolving this. I'll investigate and report back πŸ‘

β€” You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/Shopify/draggable/issues/139#issuecomment-369749468, or mute the thread https://github.com/notifications/unsubscribe-auth/ABLFLtGqu3T-Ko5VWDIvXScvOalKWS3oks5taHKigaJpZM4R2-78 .

beefchimi commented 6 years ago

@bjorsen very sorry to hear about that – hope everything is alright.

This is indeed a bug... and a very strange one.

Even if my container has just transform: scale(1)... the mirror will still be heavily offset! The presence of scale alone causes the problem. And its not simply a stacking context conundrum... adding/removing position: relative makes no difference.

I have yet to figure out the solution but I'm going to continue looking into this. I want to see first what CSS sorcery is causing this issue... and depending on what I find, I may have to bug @tsov to consider a JS solution.

Will update this thread once I've had the chance to dig deeper.

Hang tight my friends!

bzle commented 6 years ago

@beefchimi I'm also having an offset problem when the parent/container has transform: translate.

beefchimi commented 6 years ago

Sorry for the late reply. To give some insight into the CSS behaviour:

a transformed element creates a containing block for all its positioned descendants – http://meyerweb.com/eric/thoughts/2011/09/12/un-fixing-fixed-elements-with-css-transforms/

Basically, once you have a transform on a container, any children of that container will be positioned relative to that parent. So, instead of position: fixed; being relative to the viewport, it is now relative to the transformed parent.

Depending on how you have built your styling / how your app needs to function, I believe in most cases this can be easily resolved by using the appendTo option to append the mirror higher up in the DOM.

Example:

export default function Example() {
  const sortable = new Sortable(document.querySelector('.TransformedList'), {
    appendTo: 'body',
  });

  return sortable;
}

Here, we simply append our mirror element to the body, rather than its direct parent (presumably, .TransformedList). Problem solved.

Now, for the instances where a consumer must have the mirror appended within that Draggable container... I'm entertaining the idea of adding a new option called relativeToElement, which would take a single selector or Node, and do some offset calculations based on that "elements" BoundingClientRect.

I don't love this at the moment... as it muddies up a few things, such as the cursorOffset options. @tsov and I would need to discuss the best solution to this.

I'll leave this issue open so @tsov and I can discuss the best options.

MateuszSapielakGSI commented 5 years ago

@beefchimi did relativeToElement ever make it to the library? I resolved my problem appending to the body but this would be a much cleaner solution in my case.

scratch that, turns out I could just get rid of the transform in my case

Thanks!

marian-r commented 5 years ago

@beefchimi any movement on this? I would need this feature for a HTML game which uses scaling for the main scene.

joriskoris commented 5 years ago

Any news on this now?

silencio commented 5 years ago

The appendTo option did solve the problem in my case.

However there's a slight typo in @beefchimi's example: appendTo being a mirror option, not a "root" option.

It should be this:

export default function Example() {
  const sortable = new Sortable(document.querySelector('.TransformedList'), {
    mirror: {
      appendTo: 'body',
    }
  });
  return sortable;
}

instead of this:

export default function Example() {
  const sortable = new Sortable(document.querySelector('.TransformedList'), {
    appendTo: 'body',
  });

  return sortable;
}
izhan commented 5 years ago

@beefchimi Any update on this? I'm running into a similar issue with building a Chrome extension. I unfortunately can't restructure the DOM or CSS so appendTo will not work for me.

As a hacky workaround, I've forked the repo, made the container a containing block on drag start by adding transform: translate(0), and modified Mirror.js to position it relative to the container instead of window. But curious what your take on a workaround would be.

Vitj6 commented 4 years ago

ΠΏΡ‚, 23 нояб. 2018 Π³., 0:15 Marian Rusnak notifications@github.com:

@beefchimi https://github.com/beefchimi any movement on this? I would need this feature for a HTML game which uses scaling for the main scene.

β€” You are receiving this because you are subscribed to this thread. Reply to this email directly, view it on GitHub https://github.com/Shopify/draggable/issues/139#issuecomment-441106532, or mute the thread https://github.com/notifications/unsubscribe-auth/Ae_lgVGn3uT9HZOF93AneeOgSHQjdw3tks5uxve7gaJpZM4R2-78 .

hbalardin commented 2 years ago

Hi, everyone!

Any progress on this? I really need that mirror scales with it's parent element. I can't use "appendTo" to fix offsets because in that way, the scale styles does not applies to the mirror...

kostyay commented 2 years ago

Any progress? The appendTo fix doesnt seem to work https://codepen.io/kostyay/pen/vYJxoze

djimnz commented 1 year ago

Hello,

My workaround was to use mirror.appendTo as well as listening mirror:created to override the transform scale and reset the transform-origin, as below:

new Sortable(containers, {
        draggable: `.${classNames.draggableItem}`,
        mirror: {
          // ...
          appendTo: 'body'
        }
      }).on('mirror:created', event => {
          event.data.mirror.children[0].style.transform = `scale(${zoomScale /
            100})`;
          event.data.mirror.children[0].style.transformOrigin = '0 0';
        });

I recommend you wrap your draggable item children into a single element. Please, expect to have some hacky CSS things to do.

Not the best way to handle it, but it works, and this might give you some ideas for your own research.

pdemidyuk-ib commented 1 year ago
.on('mirror:created', event => {
          event.data.mirror.children[0].style.transform = `scale(${zoomScale /
            100})`;
          event.data.mirror.children[0].style.transformOrigin = '0 0';
        });

doesn't scale for me for some reason. the mirror is still unscaled