Search code examples
javascriptreactjsanimationdrag-and-drop

react drag,drop, and connect arrow(or anything else) with animation between elements


after few days of trying with no success, I came here (again..), to all of you experts.

first of all: live demo! because we all love live demos.

Codesandbox

https://codesandbox.io/s/drag-with-not-animation-b3eh7?file=/src/App.js

I'm trying to make interactive draggable and dropable arrows between containers(means - there is a connector to a box, you can drag your mouse from one of the containers to another box and an arrow between them will be created).

  • implementation 1: I can get an animation of a draggable arrow while dragging - but the onDrop event does not fire.
  • implementation 2: in the second implementation I can make the drop effect happen but not the animation.

NEVER THEM BOTH! HELP! ;<

more detailed explanations inside the code.

what I've already tried:

  • react-dnd - did not work also because it's based on the same DOM event system the native browser drag & drop API based on(or maybe(and probably) I did it wrong?).

Solution

  • Here you go :

    To draw a Xarrow you need a starting point and ending point

    • Start point : will always be dragging from
    • End Point : Where you are dropping

    Here We have 2 refs ,

    • ref0 : Starting drag point ( which will be the box )
    • ref1 : Draggable point ( Will be the draggable point )

    Here is the code that needs to be changed, please do read the comments also, that will make the flow clear.

    const ConnectPointsWrapper = ({ boxId, handler, ref0 }) => {
        const ref1 = useRef();
    
        const [position, setPosition] = useState({});
        const [beingDragged, setBeingDragged] = useState(false);
        return (
            <React.Fragment>
                <div
                    className="connectPoint"
                    ref={ref1} // <---- referencing the point
                    style={{
                        ...connectPointStyle,
                        ...connectPointOffset[handler],
                        ...position // <----- Updating the position as we drags
                    }}
                    draggable
                    onDragStart={e => {
                        setBeingDragged(true);
                        e.dataTransfer.setData("arrow", boxId);
                    }}
                    onDrag={e => {
                        setPosition({ // <---- Setting up the position to draw line as we drags
                            position: "fixed",
                            left: e.clientX,
                            top: e.clientY,
                            transform: "none",
                            opacity: 0
                        });
                    }}
                    onDragEnd={e => {
                        setPosition({});
                        setBeingDragged(false);
                    }}
                />
                {beingDragged ? <Xarrow start={ref0} end={ref1} /> : null} // <---- this will draw the arrow b/w ref0 and ref1
            </React.Fragment>
        );
    };
    
    const Box = ({ text, handler, addArrow, boxId }) => {
        const ref0 = useRef(); 
        return (
            <div
                id={boxId}
                style={boxStyle}
                ref={ref0} // <---- referencing the box it self
                onDragOver={e => e.preventDefault()}
                onDrop={e => {
                    if (e.dataTransfer.getData("arrow") === boxId) {
                        console.log(e.dataTransfer.getData("arrow"), boxId);
                    } else {
                        const refs = { start: e.dataTransfer.getData("arrow"), end: boxId };
                        addArrow(refs);
                    }
                }}
            >
                {text}
                <ConnectPointsWrapper {...{ boxId, handler, ref0 }} /> // <---- Passing the `ref0` to `ConnectPointsWrapper` to draw line from point
            </div>
        );
    };
    

    WORKING DEMO :

    Edit #SO-drag-anim-done


    NOTE :

    I was trying to set style just via ref1 and not with setPosition, you can check the below code snippet for that,

    <div
            className="connectPoint"
            style={{
              ...connectPointStyle,
              ...connectPointOffset[handler]
            }}
            draggable
            onDragStart={e => {
              setBeingDragged(true);
              e.dataTransfer.setData("arrow", boxId);
            }}
            onDrag={e => {
              setPosition({}); // <---- just to force re-rendering, to draw arrow with updated value
              ref1.current.style.position = "fixed";
              ref1.current.style.left = e.clientX + "px";
              ref1.current.style.top = e.clientY + "px";
              ref1.current.style.transform = "none";
              ref1.current.style.opacity = 0;
            }}
            ref={ref1}
            onDragEnd={e => {
              ref1.current.style.position = "absolute";
              ref1.current.style.left = connectPointOffset[handler].left;
              ref1.current.style.top = connectPointOffset[handler].top;
              ref1.current.style.transform = connectPointOffset[handler].transform;
              ref1.current.style.opacity = 0.5;
              setBeingDragged(false);
            }}
          />

    WORKING DEMO : ( this is just another way )

    Edit
#SO-drag-anim-optimization


    EDIT :

    WORKING DEMO : ( With draggable box also )

    Edit drag-anim-with-draggable