How to animate an element position to the beginning of the row when all other elements are removed from the DOM?

Viewed 22

Edit

Currently, this is more or less how I realize this functinality, but this uses Javascript, it's not a CSS-only solution, which is what I'd like to have.

heuristic-cori-4gy5mo

import * as React from "react";
import styled from "@emotion/styled";
import { css, keyframes } from "@emotion/react";

const Container = styled.div`
  display: flex;
  height: 100%;
  transition: 0.3s ease-in-out;
`;

const translate = ({ transitionFrom, beginningOfRowPosition }) => keyframes`
  from {
    transform: translate(${transitionFrom.x}px, ${transitionFrom.y}px);
  }
  to {
    transform: translate(${beginningOfRowPosition.x}px, ${beginningOfRowPosition.y}px);
  }
`;
const Button = styled.button<{
  beginningOfRowPosition?: { x: number; y: number };
  transitionFrom?: { x: number; y: number };
}>`
  ${({ beginningOfRowPosition, transitionFrom }) =>
    beginningOfRowPosition && transitionFrom
      ? css`
          transform: translate(
            ${beginningOfRowPosition.x}px,
            ${beginningOfRowPosition.y}px
          );
          animation: ${translate({
              transitionFrom,
              beginningOfRowPosition
            })}
            3s ease-in-out;
        `
      : ""}
`;

export default function App() {
  const buttonsRef = React.useRef<HTMLButtonElement[]>([]);
  const paramsSet = React.useRef(false);
  const [selectedButton, setSelectedButton] = React.useState<number | null>(
    null
  );

  const [buttonWidth, setButtonWidth] = React.useState(0);
  const [beginningOfRowPosition, setBeginningOfRowPosition] = React.useState({
    x: 0,
    y: 0
  });
  const [transitionFrom, setTransitionFrom] = React.useState({
    x: 0,
    y: 0
  });

  const handleSelectButton = React.useCallback(
    (index) => () => {
      setSelectedButton(index);
      console.log(buttonsRef.current);
      const button = buttonsRef.current[index];
      setTransitionFrom({
        x: button.getBoundingClientRect().x,
        y: button.getBoundingClientRect().y
      });
    },
    []
  );

  const handleRef = React.useCallback(
    (index) => (ref: HTMLButtonElement) => {
      if (!ref) return;
      buttonsRef.current.push(ref);

      if (paramsSet.current) return;

      if (index === 0) {
        const buttonWidth =
          Math.ceil(parseInt(getComputedStyle(ref).width.split("px")[0])) + 1;

        paramsSet.current = true;
        setButtonWidth(buttonWidth);

        const { x, y } = ref.getBoundingClientRect();
        setBeginningOfRowPosition({ x, y });
      }
    },
    []
  );

  const buttons = Array.from({ length: 5 });

  return (
    <Container style={{}}>
      {selectedButton ? (
        <Button
          beginningOfRowPosition={beginningOfRowPosition}
          transitionFrom={transitionFrom}
          style={{ width: buttonWidth }}
        >
          button {selectedButton}
        </Button>
      ) : (
        buttons.map((button, index) => (
          <Button ref={handleRef(index)} onClick={handleSelectButton(index)}>
            button {index}
          </Button>
        ))
      )}
    </Container>
  );
}

Original question

I'm trying to figure out how to animate the position of a button element, making it translate to the beginning of the row once it is selected and other button element nodes are removed from the DOM.

Here's an example:

import * as React from "react";

export default function App() {
  const [selectedButton, setSelectedButton] = React.useState<number | null>(
    null
  );

  const [buttonWidth, setButtonWidth] = React.useState(0);

  const handleSelectButton = React.useCallback(
    (index) => () => {
      setSelectedButton(index);
    },
    []
  );

  const handleRef = React.useCallback(
    (index) => (ref: HTMLButtonElement) => {
      if (!ref) return;

      if (index === 0) {
        const buttonWidth = getComputedStyle(ref).width;
        setButtonWidth(parseInt(buttonWidth.split("px")[0]));
      }
    },
    []
  );

  const buttons = Array.from({ length: 5 });

  const buttonStyle = {
    flexGrow: 1,
    transition: "0.3s ease-in-out"
  };
  return (
    <div
      style={{
        display: "flex",
        height: "100%",
        transition: "0.3s ease-in-out"
      }}
    >
      {selectedButton ? (
        <button style={{ ...buttonStyle, maxWidth: buttonWidth }}>
          button {selectedButton}
        </button>
      ) : (
        buttons.map((button, index) => (
          <button
            ref={handleRef(index)}
            className="button"
            style={{ ...buttonStyle, width: "max-content" }}
            onClick={handleSelectButton(index)}
          >
            button {index}
          </button>
        ))
      )}
    </div>
  );
}

I've tried playing around with most flex properties, because I'm aware nearly all of them are animatable, however once I select a button, it just jumps/renders to the beginning of the row, instead of transition from its current position to the beginning of the row.

The only other thing that I could think of is to click a specific button, get its position with getBoundingClientRect() and then transition the transform property to the beginning of the row.

What can I do here?

0 Answers
Related