-
Hi, I'm creating an online card game and I'm trying to animate my cards, so that when one is selected, they all collapse down into the middle: To do this, I wrote some simple code: // Card.tsx
<motion.button
initial={{
marginLeft: zIndex === 10 ? undefined : '-3rem', // zIndex 10 = first card
// ... code for animating y ommitted
}}
animate={{
marginLeft: wonCard ? (zIndex === 10 ? undefined : '-7.5rem') : undefined,
// ... rest of code for scaling and opacity fading omitted
}}
>
// ... rest of card code Basically, each card (except for the first one) is designed to overlap with the card to its left. Then, when a card is selected, I update the The problem I'm running into is that I want the cards to be a different size and with a different overlap, depending on the screen size. My first idea was to use // hooks.tsx
type WindowDimensions = {
width: number | undefined;
height: number | undefined;
};
export function useWindowDimensions() {
function getWindowDimensions() {
return {
width: window.innerWidth,
height: window.innerHeight,
};
}
const [windowDimensions, setWindowDimensions] = useState<WindowDimensions>({
width: undefined,
height: undefined,
});
useLayoutEffect(() => {
setWindowDimensions(getWindowDimensions());
function handleResize() {
setWindowDimensions(getWindowDimensions());
}
window.addEventListener('resize', handleResize);
return () => {
window.removeEventListener('resize', handleResize);
};
}, []);
return windowDimensions;
} Then inside my card component, I get the width and use it to update the // Card.tsx
const { width } = useWindowDimensions();
function getInitialWidth(width: number | undefined) {
if (width === undefined) return undefined;
if (width < 480) {
return '-2rem'
} else {
return '-3rem'
}
}
function getToWidth(width: number | undefined) {
if (width === undefined) return undefined;
if (width < 480) {
return '-4rem'
} else {
return '-7.5rem'
}
}
return (
<motion.button
initial={{
marginLeft: zIndex === 10 ? undefined : getInitialWidth(width),
}}
animate={
marginLeft: wonCard ? (zIndex === 10 ? undefined : getToWidth(width)) : undefined,
}}
>
) The problem is my window dimensions hook has to wait until the first render before getting the width. So the card initially renders with To get around this, I thought about using the The only other solution I could think of was using the // Card.tsx
const { width } = useWindowDimensions();
const [scope, animate] = useAnimate();
function getInitialWidth(width: number | undefined) { // ... }
function getToWidth(width: number | undefined) { // ... }
useEffect(() => {
if (wonCard) {
animate(scope.current, {
marginLeft: zIndex === 10 ? undefined : getToWidth(width),
});
} else {
animate(scope.current, {
marginLeft: zIndex === 10 ? undefined : getInitialWidth(width),
});
}
}, [width]);
return (
<motion.button
ref={scope}
>
) But then I end up getting this effect, where it's initially rendered with no overlap and quickly changes to an overlap. So my question is: is there a way to animate Any help would be appreciated. |
Beta Was this translation helpful? Give feedback.
Replies: 2 comments 1 reply
-
Is it possible to use CSS @media to style the initial view and then animate from there? |
Beta Was this translation helpful? Give feedback.
-
OK, I figured out two solutions:
// hooks.tsx
import { useEffect, useState } from 'react';
export function useScreenWidth() {
const [width, setWidth] = useState<undefined | number>(undefined);
useEffect(() => {
function handleResize() {
setWidth(window.innerWidth);
}
handleResize();
window.addEventListener('resize', handleResize);
return () => {
window.removeEventListener('resize', handleResize);
};
}, []);
return width;
} // App.tsx
export default function Home() {
const width = useScreenWidth();
const [canShow, setCanShow] = useState(false);
useEffect(() => {
if (width !== undefined && !canShow) {
setCanShow(true);
}
}, [width, canShow]);
return (
<>
{canShow && (
// ... rest of your markup here
)}
</>
)
}
Idea from this video: https://www.youtube.com/watch?v=xSuxsfn13xg
I'm using TailwindCSS so I used Tailwind's arbitrary values to do the CSS variables, but you could also define them in a regular |
Beta Was this translation helpful? Give feedback.
OK, I figured out two solutions: