Ask Question

How to animate page slide toggle with different height in framer-motion

I want to implement a page animation with framer-motion. The page should slide from right side and slide back to the right side when it gets toggled as you can see in the picture

Image nr: 0

The important part here is the height of the page container schould adapt the height and there should also be a smooth height transition. Since the page content is dynamic the height animation should not rely on fixed heights

Do you know any clean solutions for this?

Reactframer-motion

2444 views

AuthorΒ΄s AnkiCodes image

AnkiCodes

πŸ‘
1
πŸ‘€
1
Last edited on

1 Answer available

Best answer

This is obviously quit easy with framer-motion.

Lets start with the component state
const [page, setPage] = React.useState(1);
const refPage1 = React.useRef<HTMLDivElement>(null);
const refPage2 = React.useRef<HTMLDivElement>(null);

We need page state for toggling the pages for running the animation. Then 2 refs in order to detect the real height of the pages

Detect the actual heigh of the page which animates
const currentPageHeight = (page === 1 ? refPage1.current : refPage2.current)?.scrollHeight || "auto";

Important! the refs are will be null on first render! Therefore I applied '"auto"' as fallback.

Handling height animation
<motion.div
  animate={{
    height: currentPageHeight
  }}
  transition={{ type: "spring", bounce: 0.1, duration: 1 }}
>

As easy as that! Wooaallaaa πŸŽ‰Of course you can apply your preferred transition values instead.

Page 1 (nothing special here)
<div ref={refPage1} className="page" />
Page 2 - The sliding animation
<motion.div
  ref={refPage2}
  className="page page-blue"
  initial={false}
  animate={{
    x: page === 2 ? 0 : "100%"
  }}
  transition={{ type: "spring", bounce: 0, duration: 1 }}
/>

The "page-blue" is positioned absolute to the top and animates in the x-axis if page === 2.
Wait why initial={false}? This prevents the initial animation (which is obvious ^^) and of course I would recommend you to use the same transition obj as above. Propably it makes sense to have a const here πŸ˜‰

The end result
export default function App() {
  const [page, setPage] = React.useState(1);
  const refPage1 = React.useRef<HTMLDivElement>(null);
  const refPage2 = React.useRef<HTMLDivElement>(null);

  const currentPageHeight = (page === 1 ? refPage1.current : refPage2.current)?.scrollHeight || "auto";

  return (
    <div className="App">
      <div className="wrapper">
        <motion.div
          animate={{ height: currentPageHeight }}
          transition={{ type: "spring", bounce: 0.1, duration: 1 }}
        >
          <div ref={refPage1} className="page" />
          <motion.div
            ref={refPage2}
            className="page page-blue"
            initial={false}
            animate={{
              x: page === 2 ? 0 : "100%"
            }}
            transition={{ type: "spring", bounce: 0, duration: 1 }}
          />
        </motion.div>
      </div>
      <button onClick={() => setPage(page === 1 ? 2 : 1)}>Switch page</button>
    </div>
  );
}

Working example on CodeSandBox

πŸŽ‰
1
πŸš€
1