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.
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?