Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

transform on parent messes up dragging positioning #128

Open
nickav opened this issue Oct 1, 2017 · 44 comments
Open

transform on parent messes up dragging positioning #128

nickav opened this issue Oct 1, 2017 · 44 comments

Comments

@nickav
Copy link

nickav commented Oct 1, 2017

Bug

Expected behaviour

Dragging an item maintains the proper positioning.

Actual behaviour

The item is pushed down by the height of the neighboring element.

Steps to reproduce

Create the following html structure:

<div>
    <div style="height: 100px;"></div>
    <div style="transform: translateY(0);">
        <!-- react-beautiful-dnd component -->
    </div>
</div>

Such that react beautiful dnd is monted as a sibling of an element with some height. The parent of the dnd component should have some transform property as well.

Browser version

Google Chrome: Version 61.0.3163.100 (Official Build) (64-bit)

Demo

https://www.webpackbin.com/bins/-KvOoCqQRkMYJzq63hlS

video: https://user-images.githubusercontent.com/5448363/166853462-519f2b79-43de-43b8-8912-6d84a71a1847.mov

react-beautiful-dnd-bug.mov
@nickav nickav changed the title transform on parent messed up dragging transform on parent messes up dragging positioning Oct 1, 2017
@alexreardon
Copy link
Collaborator

Check out the 'Warning: position: fixed' section under Draggable. At this stage having parent transforms are not supported. We might look at jumping to the new React portal solution to get around this in the future. However, for now it is not supported

@alexreardon
Copy link
Collaborator

Thanks for raising this @nickaversano !!

@nickav
Copy link
Author

nickav commented Oct 1, 2017

@alexreardon what about having an option to use pure position fixed positioning? In this mode, the position of the placeholder would be set entirely by top and left instead of using the transform property

@alexreardon
Copy link
Collaborator

alexreardon commented Oct 1, 2017

Interesting idea @nickaversano - worth thinking about. Generally updating top/left values during a drag is not ideal as it does not use the GPU. However, it may get around the issue. We are trying to keep the API as clean as possible so adding an option might not be ideal. In the mean time, you could achieve this yourself!

Here is the type for the draggable style:

export type DraggingStyle = {|
  // Allow scrolling of the element behind the dragging element
  pointerEvents: 'none',

  // `position: fixed` is used to ensure that the element is always positioned
  // in the correct position and ignores the surrounding position:relative parents
  position: 'fixed',

  // When we do `position: fixed` the element looses its normal dimensions,
  // especially if using flexbox. We set the width and height manually to
  // ensure the element has the same dimensions as before it started dragging
  width: number,
  height: number,

  // When we set the width and height they include the padding on the element.
  // We use box-sizing: border-box to ensure that the width and height are inclusive of the padding
  boxSizing: 'border-box',

  // We initially position the element in the same visual spot as when it started.
  // To do this we give the element the top / left position with the margins considered
  top: number,
  left: number,

  // We clear any top or left margins on the element to ensure it does not push
  // the element positioned with the top/left position.
  // We also clear the margin right / bottom. This has no positioning impact,
  // but it is cleanest to just remove all the margins rather than only the top and left.
  margin: 0,

  // Move the element in response to a user dragging
  transform: ?string,

  // When dragging or dropping we control the z-index to ensure that
  // the layering is correct
  zIndex: ZIndex,
|}

You could set the transform to null and update the top and left values yourself (remember to make the adjustments relative to the initial top and left values).

Here is the format of the transform value:

transform: `translate(${point.x}px, ${point.y}px)`

@alexreardon
Copy link
Collaborator

Let me know how you go

@nickav
Copy link
Author

nickav commented Oct 2, 2017

I think for now I'm just going to remove the transform on the parent. I'm doing an animation, but can remove it after the animation completes.

@alexreardon
Copy link
Collaborator

Even better!

@kasperpihl
Copy link

kasperpihl commented Nov 27, 2017

I managed to get this to work with Portal from react-dom without changing styles etc.

I added a dom element into my html

<div id="draggable"></div>

Added the following styles

position: absolute;
pointer-events: none;
height: 100%;
width: 100%;

And then added this to the Draggable

import React, { PureComponent } from 'react';
import { createPortal } from 'react-dom';

const _dragEl = document.getElementById('draggable');

class DraggableGoal extends PureComponent {
  optionalPortal(styles, element) {
    if(styles.position === 'fixed') {
      return createPortal(
        element,
        _dragEl,
      );
    }
    return element;
  }
  render() {
    const { item } = this.props;
    return (
      <Draggable>
        {(provided, snapshot) => {
          return (
            <div>
              {this.optionalPortal(provided.draggableStyle, (
                <div
                  ref={provided.innerRef}
                  style={provided.draggableStyle}
                  {...provided.dragHandleProps}
                >
                  {item}
                </div>
              ))}
              {provided.placeholder}
            </div>
          );
        }}
      </Draggable>
    );
  }
}

Hope this helps :)

@Valoran
Copy link

Valoran commented Sep 2, 2019

Sorry if i'm not supposed to post in closed issues, but i figured this might help if someone else is still having the same problem.

I had the same issue when moving an item from a nested list to the parent list, due to the nested lists' container getting transformed when it is "moved down" to show where the dragged item will drop.
The problem itself is that when an item (in this case the nested list) gets transformed, that element becomes the new target container for any child elements with position: fixed (essentially changing it to position: absolute), so the nested item i am trying to drag does not position itself to the viewport, but rather to it's parent container.

I finally managed to make a CSS based workaround and thought I should share it incase someone else still has the same problem.

My solution was to monitor the parent containers styles and when it gets a transform, i recalculate the inner items transform to compensate with the formula current X - parentX - position.left and currentY - parentY - position.top
where currentX and currentY is the transform properties i get from provided.draggableProps.style.transform
there is also a +20in there to account for some margins in my case.

<Draggable
         key={index}
         draggableId={cel.contentId}
         index={index}>
                {(provided, snapshot) => {
                           let transform = provided.draggableProps.style.transform;
                           if(snapshot.isDragging){
                           let regex = new RegExp('translate\\((.*?)px, (.*?)px\\)');

                           //the transform property of parent style gotten through reference and passed in as a prop
                           let parentValues = regex.exec(this.props.parentTransform ||"")
                           let childValues = regex.exec(provided.draggableProps.style.transform || "");

                           //if both the parent (the nested list) and the child (item beeing dragged) has transform values, recalculate the child items transform to account for position fixed not working
                           if(childValues != null && parentValues != null){
                                   let x = (parseFloat(childValues[1], 10)) - ((parseFloat(parentValues[1], 10)) + (parseFloat(provided.draggableProps.style.left,10))) + 20;
                                   let y = (parseFloat(childValues[2], 10)) - ((parseFloat(parentValues[2], 10)) + (parseFloat(provided.draggableProps.style.top, 10)));
                                   transform = `translate(${x}px, ${y}px)`;
                          }
              } 
return (<div
        {...provided.draggableProps}
        {...this.props}
        ref={provided.innerRef}
        isDragging={snapshot.isDragging}
        style={{...provided.draggableProps.style, transform}}
        >(... _inner content removed for brevity_ )</div>)
}
</Draggable>

@lkiarest
Copy link

Sorry if i'm not supposed to post in closed issues, but i figured this might help if someone else is still having the same problem.

I had the same issue when moving an item from a nested list to the parent list, due to the nested lists' container getting transformed when it is "moved down" to show where the dragged item will drop.
The problem itself is that when an item (in this case the nested list) gets transformed, that element becomes the new target container for any child elements with position: fixed (essentially changing it to position: absolute), so the nested item i am trying to drag does not position itself to the viewport, but rather to it's parent container.

I finally managed to make a CSS based workaround and thought I should share it incase someone else still has the same problem.

My solution was to monitor the parent containers styles and when it gets a transform, i recalculate the inner items transform to compensate with the formula current X - parentX - position.left and currentY - parentY - position.top
where currentX and currentY is the transform properties i get from provided.draggableProps.style.transform
there is also a +20in there to account for some margins in my case.

<Draggable
         key={index}
         draggableId={cel.contentId}
         index={index}>
                {(provided, snapshot) => {
                           let transform = provided.draggableProps.style.transform;
                           if(snapshot.isDragging){
                           let regex = new RegExp('translate\\((.*?)px, (.*?)px\\)');

                           //the transform property of parent style gotten through reference and passed in as a prop
                           let parentValues = regex.exec(this.props.parentTransform ||"")
                           let childValues = regex.exec(provided.draggableProps.style.transform || "");

                           //if both the parent (the nested list) and the child (item beeing dragged) has transform values, recalculate the child items transform to account for position fixed not working
                           if(childValues != null && parentValues != null){
                                   let x = (parseFloat(childValues[1], 10)) - ((parseFloat(parentValues[1], 10)) + (parseFloat(provided.draggableProps.style.left,10))) + 20;
                                   let y = (parseFloat(childValues[2], 10)) - ((parseFloat(parentValues[2], 10)) + (parseFloat(provided.draggableProps.style.top, 10)));
                                   transform = `translate(${x}px, ${y}px)`;
                          }
              } 
return (<div
        {...provided.draggableProps}
        {...this.props}
        ref={provided.innerRef}
        isDragging={snapshot.isDragging}
        style={{...provided.draggableProps.style, transform}}
        >(... _inner content removed for brevity_ )</div>)
}
</Draggable>

there isn't an official solution for now, right ?

@kaanbayram
Copy link

kaanbayram commented Dec 24, 2019

I managed to get this to work with Portal from react-dom without changing styles etc.

I added a dom element into my html

<div id="draggable"></div>

Added the following styles

position: absolute;
pointer-events: none;
height: 100%;
width: 100%;

And then added this to the Draggable

import React, { PureComponent } from 'react';
import { createPortal } from 'react-dom';

const _dragEl = document.getElementById('draggable');

class DraggableGoal extends PureComponent {
  optionalPortal(styles, element) {
    if(styles.position === 'fixed') {
      return createPortal(
        element,
        _dragEl,
      );
    }
    return element;
  }
  render() {
    const { item } = this.props;
    return (
      <Draggable>
        {(provided, snapshot) => {
          return (
            <div>
              {this.optionalPortal(provided.draggableStyle, (
                <div
                  ref={provided.innerRef}
                  style={provided.draggableStyle}
                  {...provided.dragHandleProps}
                >
                  {item}
                </div>
              ))}
              {provided.placeholder}
            </div>
          );
        }}
      </Draggable>
    );
  }
}

Hope this helps :)

Budy you saved my life many thankful to you

@zachsa
Copy link

zachsa commented Jan 29, 2020

This mostly worked for me.. Or, at least, now when I do the drag things align. However when I drop the animation zooms back to the position it would have been in without the transform.

Is there a way that I can adjust the drop animation start position?

@kaanbayram
Copy link

kaanbayram commented Jan 29, 2020

This mostly worked for me.. Or, at least, now when I do the drag things align. However when I drop the animation zooms back to the position it would have been in without the transform.

Is there a way that I can adjust the drop animation start position?

@zachsa

if you just want to change position:fixed property you don't have to use portal. You can do override. If you don't know how you can do i can look my old project for you. Portal will isolate parent css from child DOM element.

@zachsa
Copy link

zachsa commented Jan 29, 2020

Ah. I didn't think of adjusting the position in the style element. Thank you

@zachsa
Copy link

zachsa commented Jan 29, 2020

Sorry if i'm not supposed to post in closed issues, but i figured this might help if someone else is still having the same problem.
I had the same issue when moving an item from a nested list to the parent list, due to the nested lists' container getting transformed when it is "moved down" to show where the dragged item will drop.
The problem itself is that when an item (in this case the nested list) gets transformed, that element becomes the new target container for any child elements with position: fixed (essentially changing it to position: absolute), so the nested item i am trying to drag does not position itself to the viewport, but rather to it's parent container.
I finally managed to make a CSS based workaround and thought I should share it incase someone else still has the same problem.
My solution was to monitor the parent containers styles and when it gets a transform, i recalculate the inner items transform to compensate with the formula current X - parentX - position.left and currentY - parentY - position.top
where currentX and currentY is the transform properties i get from provided.draggableProps.style.transform
there is also a +20in there to account for some margins in my case.

<Draggable
         key={index}
         draggableId={cel.contentId}
         index={index}>
                {(provided, snapshot) => {
                           let transform = provided.draggableProps.style.transform;
                           if(snapshot.isDragging){
                           let regex = new RegExp('translate\\((.*?)px, (.*?)px\\)');

                           //the transform property of parent style gotten through reference and passed in as a prop
                           let parentValues = regex.exec(this.props.parentTransform ||"")
                           let childValues = regex.exec(provided.draggableProps.style.transform || "");

                           //if both the parent (the nested list) and the child (item beeing dragged) has transform values, recalculate the child items transform to account for position fixed not working
                           if(childValues != null && parentValues != null){
                                   let x = (parseFloat(childValues[1], 10)) - ((parseFloat(parentValues[1], 10)) + (parseFloat(provided.draggableProps.style.left,10))) + 20;
                                   let y = (parseFloat(childValues[2], 10)) - ((parseFloat(parentValues[2], 10)) + (parseFloat(provided.draggableProps.style.top, 10)));
                                   transform = `translate(${x}px, ${y}px)`;
                          }
              } 
return (<div
        {...provided.draggableProps}
        {...this.props}
        ref={provided.innerRef}
        isDragging={snapshot.isDragging}
        style={{...provided.draggableProps.style, transform}}
        >(... _inner content removed for brevity_ )</div>)
}
</Draggable>

there isn't an official solution for now, right ?

I found that although this fixed the dragging offset, when I drop the item the animation is from where the original position would have been

@kaanbayram
Copy link

kaanbayram commented Jan 30, 2020

@zachsa
i think you are changing your Droppable area's location after render. Because of position:fixed even if you change this property for draggable element acting as it was in its old position. Because Droppable area's position property still position:fixed. And unfortunately you can't override droppable area's css as draggable element. Sorry for my english. I was have similar problem as like yours.
https://github.com/kaanbayram/React-antdesign-menu-beatiful-dnd

This is my project, i think this can be helpful to you.

@zachsa
Copy link

zachsa commented Jan 30, 2020

thank you - that does look like it's exactly what i want to do. Before I start looking through the source (still quite difficult for me), would you mind if I just double check with you that I'm on the right track?

  • I have a div that can be dragged around the screen (such as in your code)
  • This div contains a simple DND list.
  • The drag is enabled by traslate instead of changing absolute positioning. Therefore DND drag is incorrect
  • I was able to correct the draggable elements traslate offset. But not the drop animation. The result is that when I 'dropped' the element it would fly back to where it would naturally be and then animate from there

It looks like this is something that your code will fix! I'll give it a go - please let me know if I'm NOT on the correct track !!

@kaanbayram
Copy link

Firstly sorry for my complicated code. I think yes you are in correct track. If you wish you can share your project, maybe i can help to you better.

@alexreardon alexreardon reopened this Jan 30, 2020
@alexreardon
Copy link
Collaborator

I've reopening this as there is still discussion going. I will try to get to it soon

@cuxs
Copy link

cuxs commented Mar 28, 2020

I managed to get this to work with Portal from react-dom without changing styles etc.

I added a dom element into my html

<div id="draggable"></div>

Added the following styles

position: absolute;
pointer-events: none;
height: 100%;
width: 100%;

And then added this to the Draggable

import React, { PureComponent } from 'react';
import { createPortal } from 'react-dom';

const _dragEl = document.getElementById('draggable');

class DraggableGoal extends PureComponent {
  optionalPortal(styles, element) {
    if(styles.position === 'fixed') {
      return createPortal(
        element,
        _dragEl,
      );
    }
    return element;
  }
  render() {
    const { item } = this.props;
    return (
      <Draggable>
        {(provided, snapshot) => {
          return (
            <div>
              {this.optionalPortal(provided.draggableStyle, (
                <div
                  ref={provided.innerRef}
                  style={provided.draggableStyle}
                  {...provided.dragHandleProps}
                >
                  {item}
                </div>
              ))}
              {provided.placeholder}
            </div>
          );
        }}
      </Draggable>
    );
  }
}

Hope this helps :)

This almost works form me, it fixes dragging positioning issue but all the other cards disappears, I just need to tweak this a little more.

@hemavidal
Copy link

hemavidal commented Apr 8, 2020

Sorry if i'm not supposed to post in closed issues, but i figured this might help if someone else is still having the same problem.

I had the same issue when moving an item from a nested list to the parent list, due to the nested lists' container getting transformed when it is "moved down" to show where the dragged item will drop.
The problem itself is that when an item (in this case the nested list) gets transformed, that element becomes the new target container for any child elements with position: fixed (essentially changing it to position: absolute), so the nested item i am trying to drag does not position itself to the viewport, but rather to it's parent container.

I finally managed to make a CSS based workaround and thought I should share it incase someone else still has the same problem.

My solution was to monitor the parent containers styles and when it gets a transform, i recalculate the inner items transform to compensate with the formula current X - parentX - position.left and currentY - parentY - position.top
where currentX and currentY is the transform properties i get from provided.draggableProps.style.transform
there is also a +20in there to account for some margins in my case.

<Draggable
         key={index}
         draggableId={cel.contentId}
         index={index}>
                {(provided, snapshot) => {
                           let transform = provided.draggableProps.style.transform;
                           if(snapshot.isDragging){
                           let regex = new RegExp('translate\\((.*?)px, (.*?)px\\)');

                           //the transform property of parent style gotten through reference and passed in as a prop
                           let parentValues = regex.exec(this.props.parentTransform ||"")
                           let childValues = regex.exec(provided.draggableProps.style.transform || "");

                           //if both the parent (the nested list) and the child (item beeing dragged) has transform values, recalculate the child items transform to account for position fixed not working
                           if(childValues != null && parentValues != null){
                                   let x = (parseFloat(childValues[1], 10)) - ((parseFloat(parentValues[1], 10)) + (parseFloat(provided.draggableProps.style.left,10))) + 20;
                                   let y = (parseFloat(childValues[2], 10)) - ((parseFloat(parentValues[2], 10)) + (parseFloat(provided.draggableProps.style.top, 10)));
                                   transform = `translate(${x}px, ${y}px)`;
                          }
              } 
return (<div
        {...provided.draggableProps}
        {...this.props}
        ref={provided.innerRef}
        isDragging={snapshot.isDragging}
        style={{...provided.draggableProps.style, transform}}
        >(... _inner content removed for brevity_ )</div>)
}
</Draggable>

Worked for me but I have changed this piece to work fine:

const x =
  parseFloat(childValues[1], 10) -
  parseFloat(
    provided.draggableProps.style.left,
    10,
  )
const y =
  parseFloat(childValues[2], 10) -
  parseFloat(
    provided.draggableProps.style.top,
    10,
  )

And this one too:

style={{
  ...provided.draggableProps.style,
  transform,
  position: snapshot.isDragging
    ? 'absolute'
    : '',
}}

@nikitowsky
Copy link

nikitowsky commented Jul 14, 2020

@kasperpihl where I can buy beer for you? You saved my day.

@AnujVarma
Copy link

That is super exhilarating to hear @borisowsky, @kasperpihl, I tried implementing a solution in codesandbox here: https://codesandbox.io/s/react-beautiful-dnd-h4lk7?file=/src/index.js

In this example, I translate the drag and drop list by 30 in the x and y direction, however, even after using portals, the dragging behavior is offset. Any help/ a working example on code sandbox would be amazing, thank you!

@ShongSu
Copy link

ShongSu commented Jul 30, 2020

Sorry if i'm not supposed to post in closed issues, but i figured this might help if someone else is still having the same problem.

I had the same issue when moving an item from a nested list to the parent list, due to the nested lists' container getting transformed when it is "moved down" to show where the dragged item will drop.
The problem itself is that when an item (in this case the nested list) gets transformed, that element becomes the new target container for any child elements with position: fixed (essentially changing it to position: absolute), so the nested item i am trying to drag does not position itself to the viewport, but rather to it's parent container.

I finally managed to make a CSS based workaround and thought I should share it incase someone else still has the same problem.

My solution was to monitor the parent containers styles and when it gets a transform, i recalculate the inner items transform to compensate with the formula current X - parentX - position.left and currentY - parentY - position.top
where currentX and currentY is the transform properties i get from provided.draggableProps.style.transform
there is also a +20in there to account for some margins in my case.

<Draggable
         key={index}
         draggableId={cel.contentId}
         index={index}>
                {(provided, snapshot) => {
                           let transform = provided.draggableProps.style.transform;
                           if(snapshot.isDragging){
                           let regex = new RegExp('translate\\((.*?)px, (.*?)px\\)');

                           //the transform property of parent style gotten through reference and passed in as a prop
                           let parentValues = regex.exec(this.props.parentTransform ||"")
                           let childValues = regex.exec(provided.draggableProps.style.transform || "");

                           //if both the parent (the nested list) and the child (item beeing dragged) has transform values, recalculate the child items transform to account for position fixed not working
                           if(childValues != null && parentValues != null){
                                   let x = (parseFloat(childValues[1], 10)) - ((parseFloat(parentValues[1], 10)) + (parseFloat(provided.draggableProps.style.left,10))) + 20;
                                   let y = (parseFloat(childValues[2], 10)) - ((parseFloat(parentValues[2], 10)) + (parseFloat(provided.draggableProps.style.top, 10)));
                                   transform = `translate(${x}px, ${y}px)`;
                          }
              } 
return (<div
        {...provided.draggableProps}
        {...this.props}
        ref={provided.innerRef}
        isDragging={snapshot.isDragging}
        style={{...provided.draggableProps.style, transform}}
        >(... _inner content removed for brevity_ )</div>)
}
</Draggable>

Thank you for your share. I have a question here how can I get/set parentTransform in props?

@renaudtertrais
Copy link

renaudtertrais commented Aug 5, 2020

I had the same issue. Following the great solution of @kasperpihl, I created a simple hook do do the trick:

Hook:

const useDraggableInPortal = () => {
    const self = useRef({}).current;

    useEffect(() => {
        const div = document.createElement('div');
        div.style.position = 'absolute';
        div.style.pointerEvents = 'none';
        div.style.top = '0';
        div.style.width = '100%';
        div.style.height = '100%';
        self.elt = div;
        document.body.appendChild(div);
        return () => {
            document.body.removeChild(div);
        };
    }, [self]);

    return (render) => (provided, ...args) => {
        const element = render(provided, ...args);
        if (provided.draggableProps.style.position === 'fixed') {
            return createPortal(element, self.elt);
        }
        return element;
    };
};

Usage:

Considering the following component:

const MyComponent = (props) => {
  return (
    <DragDropContext onDragEnd={/* ... */}>
      <Droppable droppableId="droppable">
        {({ innerRef, droppableProps, placeholder }) => (
          <div ref={innerRef} {...droppableProps}>
            {props.items.map((item, index) => (
              <Draggable key={item.id} draggableId={item.id} index={index}>
                {(provided) => (
                  <div
                    ref={provided.innerRef}
                    {...provided.draggableProps}
                    {...provided.dragHandleProps}
                  >
                    {item.title}
                  </div>
                )}
              </Draggable>
            )}
            {placeholder}
          </div>
        )}
      </Droppable>
    </DragDropContext>
  );
};

Just call the hook and use the returned function to wrap the children callback of <Draggable /> component:

const MyComponent = (props) => {
+ const renderDraggable = useDraggableInPortal();

  return (
    <DragDropContext onDragEnd={/* ... */}>
      <Droppable droppableId="droppable">
        {({ innerRef, droppableProps, placeholder }) => (
          <div ref={innerRef} {...droppableProps}>
            {props.items.map((item, index) => (
              <Draggable key={item.id} draggableId={item.id} index={index}>
+               {renderDraggable((provided) => (
                  <div
                    ref={provided.innerRef}
                    {...provided.draggableProps}
                    {...provided.dragHandleProps}
                  >
                    {item.title}
                  </div>
                ))}
              </Draggable>
+           ))}
            {placeholder}
          </div>
        )}
      </Droppable>
    </DragDropContext>
  );
};

Hope it'll help.

@ghost
Copy link

ghost commented Sep 1, 2020

I managed to get this to work with Portal from react-dom without changing styles etc.

I added a dom element into my html

<div id="draggable"></div>

Added the following styles

position: absolute;
pointer-events: none;
height: 100%;
width: 100%;

And then added this to the Draggable

import React, { PureComponent } from 'react';
import { createPortal } from 'react-dom';

const _dragEl = document.getElementById('draggable');

class DraggableGoal extends PureComponent {
  optionalPortal(styles, element) {
    if(styles.position === 'fixed') {
      return createPortal(
        element,
        _dragEl,
      );
    }
    return element;
  } #
  render() {
    const { item } = this.props;
    return (
      <Draggable>
        {(provided, snapshot) => {
          return (
            <div>

> {optionalPortal(provided.draggableProps.style, (

            <div
              ref={provided.innerRef}
              style={provided.draggableStyle}
              {...provided.dragHandleProps}
            >
              {item}
            </div>
          ))}
          {provided.placeholder}
        </div>
      );
    }}
  </Draggable>
);

}
}


Hope this helps :)

This works great, but just use:
{optionalPortal(provided.draggableProps.style, (

@Maxeeezy
Copy link

I face the same out-of-position problem.

When I try to implement it like @daljeetv I get an error
Unable to find draggable element with id: X
when clicking the item that I would like to move.

Do you guys have any ideas what the reason could be?

@ducktordanny
Copy link

Anyone who has still this problem I made a repository where I made an example for it and in the README you can also find a more simple solution. I found it from here in a comment and it works fine.

Note: I had another problem where the dragged div was disappear and I found out it was because of z-index and it dropped behind the parent div. So if e.g. you have a sidebar and its z-index is 999 you have to set the div of Draggable component above that (e.g. z-index: 1000;).

Here is my repo: https://github.com/DucktorDanny/react-beautiful-dnd-example

I hope I could help. :)

@Maxeeezy
Copy link

Maxeeezy commented Feb 1, 2021 via email

@nickav
Copy link
Author

nickav commented Mar 10, 2021

@duktorD @maxeezy - you could even do something as simple as:

const ref = React.useRef();

<DragDropContext
  onDragStart={() => {
    const el = ref?.current;
    if (!el) return;

    const bounds = el.getBoundingClientRect();

    el.style.position = 'fixed';
    el.style.top = bounds.top + 'px';
    el.style.left = bounds.left + 'px';
    el.style.transform = 'none';
  }}
  onDragEnd={() => {
    const el = ref?.current;
    if (!el) return;

    el.style.position = null;
    el.style.top = null;
    el.style.left = null;
    el.style.transform = null;
  }}
>
{/* ... */}
</DragDropContext>

it would only work as long as your component doesn't re-render during the drag i think

@bknill
Copy link

bknill commented Apr 16, 2021

I used @ducktorD 's lovely example.

Fixed the problem but now I have ugly items.

image

Does it seem snapshot might be included in a later version? with the is dragging props. I'm running 13.1.0

@montacerdk
Copy link

I had the same issue. Following the great solution of @kasperpihl, I created a simple hook do do the trick:

Hook:

const useDraggableInPortal = () => {
    const self = useRef({}).current;

    useEffect(() => {
        const div = document.createElement('div');
        div.style.position = 'absolute';
        div.style.pointerEvents = 'none';
        div.style.top = '0';
        div.style.width = '100%';
        div.style.height = '100%';
        self.elt = div;
        document.body.appendChild(div);
        return () => {
            document.body.removeChild(div);
        };
    }, [self]);

    return (render) => (provided, ...args) => {
        const element = render(provided, ...args);
        if (provided.draggableProps.style.position === 'fixed') {
            return createPortal(element, self.elt);
        }
        return element;
    };
};

Usage:

Considering the following component:

const MyComponent = (props) => {
  return (
    <DragDropContext onDragEnd={/* ... */}>
      <Droppable droppableId="droppable">
        {({ innerRef, droppableProps, placeholder }) => (
          <div ref={innerRef} {...droppableProps}>
            {props.items.map((item, index) => (
              <Draggable key={item.id} draggableId={item.id} index={index}>
                {(provided) => (
                  <div
                    ref={provided.innerRef}
                    {...provided.draggableProps}
                    {...provided.dragHandleProps}
                  >
                    {item.title}
                  </div>
                )}
              </Draggable>
            )}
            {placeholder}
          </div>
        )}
      </Droppable>
    </DragDropContext>
  );
};

Just call the hook and use the returned function to wrap the children callback of <Draggable /> component:

const MyComponent = (props) => {
+ const renderDraggable = useDraggableInPortal();

  return (
    <DragDropContext onDragEnd={/* ... */}>
      <Droppable droppableId="droppable">
        {({ innerRef, droppableProps, placeholder }) => (
          <div ref={innerRef} {...droppableProps}>
            {props.items.map((item, index) => (
              <Draggable key={item.id} draggableId={item.id} index={index}>
+               {renderDraggable((provided) => (
                  <div
                    ref={provided.innerRef}
                    {...provided.draggableProps}
                    {...provided.dragHandleProps}
                  >
                    {item.title}
                  </div>
                ))}
              </Draggable>
+           ))}
            {placeholder}
          </div>
        )}
      </Droppable>
    </DragDropContext>
  );
};

Hope it'll help.

For React TypeScript, the hook will look like :

import { DraggableProvided, DraggingStyle } from 'react-beautiful-dnd'
import { ReactElement, useEffect, useRef } from 'react'
import { createPortal } from 'react-dom'

export const useDraggableInPortal = () => {
  const element = useRef<HTMLDivElement>(document.createElement('div')).current

  useEffect(() => {
    if (element) {
      element.style.pointerEvents = 'none'
      element.style.position = 'absolute'
      element.style.height = '100%'
      element.style.width = '100%'
      element.style.top = '0'

      document.body.appendChild(element)

      return () => {
        document.body.removeChild(element)
      }
    }
  }, [element])

  return (render: (provided: DraggableProvided) => ReactElement) => (provided: DraggableProvided) => {
    const result = render(provided)
    const style = provided.draggableProps.style as DraggingStyle
    if (style.position === 'fixed') {
      return createPortal(result, element)
    }
    return result
  }
}

@thalysonalexr
Copy link

@renaudtertrais thanks so much bro!!! Congratulations! This works for me.
Thanks for types @montacerdk

@nomi2213
Copy link

Maybe it will help someone. I also had problems with positioning when dragging. I had the "fixed" and "transform" styles on the sidebar, but the problem was a different "will-change: transform" style. Everything worked without this style. My versions of the packages "react-beautiful-dnd": "13.1.0" and "@storybook/react": "6.5.9".

@larsnystrom
Copy link

I can't see anyone mentioning this yet, but there is actually some official documentation on how to deal with transform on parent: https://github.com/atlassian/react-beautiful-dnd/blob/HEAD/docs/guides/reparenting.md

This means that if you have a transform: * on one of the parents of a then the positioning logic will be incorrect while dragging. Lame!

The solution seems to be to use the renderClone prop on your <Droppable>. I just tried this myself and it works fine.

@feresr
Copy link

feresr commented Nov 13, 2022

At this stage having parent transforms are not supported

I wonder why even transform: translate(0px, 0px); causes issues?
It seems to re-apply the top/left properties again to the item being dragged.
Unticking that from css devtools and everything works again

@efeguerrero
Copy link

efeguerrero commented Jul 28, 2023

I was having the same issue while doing an animation as the component mounted and my fix was to simply remove the transform property after the animation was done.

Use onTransitionEnd={() => handleTransitionEnd()} and then just remove the transform property and it will be fixed.

  const handleTransitionEnd = () => {
    console.log('transition end');
    transitionDivRef.current?.removeAttribute('style');
  };

I removed whole style attribute because it was a div only used for the transition.

@zindler323
Copy link

I managed to get this to work with Portal from react-dom without changing styles etc.

I added a dom element into my html

<div id="draggable"></div>

Added the following styles

position: absolute;
pointer-events: none;
height: 100%;
width: 100%;

And then added this to the Draggable

import React, { PureComponent } from 'react';
import { createPortal } from 'react-dom';

const _dragEl = document.getElementById('draggable');

class DraggableGoal extends PureComponent {
  optionalPortal(styles, element) {
    if(styles.position === 'fixed') {
      return createPortal(
        element,
        _dragEl,
      );
    }
    return element;
  }
  render() {
    const { item } = this.props;
    return (
      <Draggable>
        {(provided, snapshot) => {
          return (
            <div>
              {this.optionalPortal(provided.draggableStyle, (
                <div
                  ref={provided.innerRef}
                  style={provided.draggableStyle}
                  {...provided.dragHandleProps}
                >
                  {item}
                </div>
              ))}
              {provided.placeholder}
            </div>
          );
        }}
      </Draggable>
    );
  }
}

Hope this helps :)

@kasperpihl 's portal solution helped me a lot.
And in consideration of the case where i use scoped className, i find another solution: calculating the relative position to the 'transform parent':

import { merge } from 'lodash';
import cls from './style.module.scss';

const MyComponent = (props) => {
  const viewportRef = useRef(null); // find the 'transform parent'
  
  return (<div ref={viewportRef} style={{ transform: 'translateX(0)' }}>
    <DragDropContext onDragEnd={/* ... */}>
      {/* ... */}
            {props.items.map((item, index) => (
              <Draggable key={item.id} draggableId={item.id} index={index}>
                {(provided, { isDragging }) => (
                  <div
                    {/* ... */}
                    className={cls.item}
                    style={merge(
                      provided.draggableProps.style,
                      /** relative position item to window - relative position parent to window = relative position item to parent */
                      isDragging ? {
                         left: provided.draggableProps.style.left - viewportRef.current.getBoundingClientRect().left,
                         top: provided.draggableProps.style.top - viewportRef.current.getBoundingClientRect().top,
                      } : {}
                    )}
                  >
                    {item.title}
                  </div>
                )}
              </Draggable>
            ))}
       {/* ... */}
    </DragDropContext>
  <div>);
};

@ankit-sapkota
Copy link

ankit-sapkota commented May 1, 2024

Has anyone had any luck if the parent is scaled instead of just shifted?

@bhcjohnnysiu
Copy link

Has anyone had any luck if the parent is scaled instead of just shifted?

same issue....

@rohitnegi-dev
Copy link

So the fix that will work for everybody is to use renderClone as described https://github.com/atlassian/react-beautiful-dnd/blob/master/docs/guides/reparenting.md and ya'll are good to go.

@linpan
Copy link

linpan commented Aug 15, 2024

So the fix that will work for everybody is to use renderClone as described https://github.com/atlassian/react-beautiful-dnd/blob/master/docs/guides/reparenting.md and ya'll are good to go.

404

@larsnystrom
Copy link

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests