Repository navigation
Animate layout exit/fade-out when conditional element is unmounted #3885
Replies: 2 comments 1 reply
|
The fade comes from two things: Keep the underline mounted on the last section while on home, and fade a plain wrapper instead: const isHome = activeSectionId === 'home'
const prevRef = useRef(activeSectionId)
const lastRef = useRef(isHome ? null : activeSectionId)
const fromHome = prevRef.current === 'home'
const underlineAt = isHome ? lastRef.current : activeSectionId
useEffect(() => {
prevRef.current = activeSectionId
if (!isHome) lastRef.current = activeSectionId
}, [activeSectionId])
// inside the map, no AnimatePresence needed
{sectionId === underlineAt && (
<motion.span
initial={fromHome ? { opacity: 0 } : false}
animate={{ opacity: isHome ? 0 : 1 }}
transition={{ duration: 0.2 }}
>
<motion.span
layoutId='active-section'
transition={fromHome ? { duration: 0 } : { type: 'spring', stiffness: 250, damping: 60 }}
className='absolute left-1/2 -translate-x-1/2 bg-accent-emerald h-0.5 bottom-1 w-[80%] pointer-events-none'
/>
</motion.span>
)}Section to section slides at full opacity with one underline; to home fades out in place; from home fades in place without sliding. Checked on motion 14.0.0, including section changes 400ms apart. |
|
I think the key here is to separate the opacity animation from the shared-layout animation. There are two independent things happening in your current implementation: initial={{ opacity: 0 }} runs every time a new underline is mounted. So even during a normal section-to-section transition, the new underline starts transparent. layoutId automatically performs a shared-element transition. When the old and new elements coexist, Motion can crossfade them as part of that transition. This means you're effectively asking the same element to handle two different animation responsibilities: moving between navigation items and fading in/out based on the special home state. I'd recommend the nested-wrapper approach rather than trying to coordinate both behaviors with AnimatePresence. The idea Keep the layoutId element responsible only for position and movement. Put it inside a regular motion.span that handles opacity. Remember the last non-home section so the underline can remain mounted at its previous location while the active section is home. The important part looks like this: const isHome = activeSectionId === "home" const previousRef = useRef(activeSectionId) const fromHome = previousRef.current === "home" const underlineAt = isHome useEffect(() => { if (!isHome) { Then, inside your navigation item: {sectionId === underlineAt && ( </motion.span> Why this works Section A → Section B The new wrapper uses initial={false}, so it doesn't introduce an unwanted fade. The inner element handles the shared-layout transition and slides to the new position. Section B → Home Instead of unmounting the underline, underlineAt continues pointing to the last active section. Its wrapper simply animates to opacity: 0, allowing it to fade out at its existing position. Home → Section C The underline is mounted at the new section. Since fromHome is true, its wrapper starts at zero opacity and fades in. The inner layout transition has zero duration, so it doesn't unnecessarily travel from the previous section. The main advantage is that opacity and layout no longer compete for control of the same animated element. You can also adjust the fade duration independently from the spring transition. References: Motion — Layout animations and shared layout I'd start with this approach because it removes the need for AnimatePresence entirely in this particular case. The underline doesn't actually need to be unmounted when entering the home state; it just needs to become invisible while retaining its last position. |
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Hi! I'm building a navbar with section IDs. Underline should display currently active (in view) section and move between section IDs, or fade out/in. "home" element is not displayed. My goal is to handle three states:
My current code kinda works. But when underline moves between sections I see fade-in animation. I need it only when going from "home" to another section. I tried tweaking a lot but it's getting messy - one thing fixed, another breaks. Sounds simple but I can't figure it out.
How can I achieve what I want?
All reactions