skills/hubeiqiao/skills/framer-motion-sticky-scroll-layout

framer-motion-sticky-scroll-layout

Installation
SKILL.md

Framer Motion Sticky Scroll Section Layout

Problem

When building scroll-driven sections with CSS position: sticky and Framer Motion animated slides (absolute-positioned children that fade in/out based on scroll progress), the children render incorrectly — often stacking, overflowing, or appearing at wrong positions. Framer Motion logs: "Please ensure that the container has a non-static position."

Context / Trigger Conditions

  • Building a scroll-pinned section with multiple "slides" that transition as user scrolls
  • Using position: sticky with a tall scroll runway (e.g., height: 300vh)
  • Child slides use position: absolute; inset: 0 to stack on top of each other
  • Framer Motion animate toggles opacity and y based on useScroll + useTransform
  • Console warning about non-static position on the container

Solution

Correct structure:

<div ref={scrollRef} className="relative" style={{ height: '300vh' }}>
  {/* Sticky viewport */}
  <div className="sticky top-0 h-screen overflow-hidden">
    {/* CRITICAL: This relative wrapper is the positioning context */}
    <div className="relative w-full h-full">

      {/* Slide 1 */}
      <motion.div
        className="absolute inset-0 flex items-center"
        animate={{ opacity: currentSlide === 0 ? 1 : 0 }}
      >
        <div className="max-w-6xl mx-auto w-full px-8">
          {/* Content */}
        </div>
      </motion.div>

      {/* Slide 2 */}
      <motion.div
        className="absolute inset-0 flex items-center justify-center"
        animate={{ opacity: currentSlide === 1 ? 1 : 0 }}
      >
        {/* Content */}
      </motion.div>

    </div>
  </div>
</div>

Key rules:

  1. The relative w-full h-full div MUST be a direct child of the sticky container
  2. All slides use absolute inset-0 to fill this relative container
  3. Content padding (px-8, max-w-6xl) goes INSIDE each slide, not on the container
  4. Use flex items-center on each absolute slide for vertical centering
  5. Each slide gets its own layout (grid, flex, centered) — they don't share a parent layout

Common mistake — putting content wrapper between sticky and slides:

// WRONG: max-w-6xl wrapper between sticky and absolute slides
<div className="sticky top-0 h-screen">
  <div className="max-w-6xl mx-auto w-full px-8">
    <motion.div style={{ position: 'absolute', inset: 0 }}>
      {/* This breaks because absolute child escapes the static parent */}
    </motion.div>
  </div>
</div>

Verification

  • Console warning about non-static position disappears
  • All slides render centered vertically in the viewport
  • Slides transition smoothly as scroll progresses through the runway
  • Content has proper padding from viewport edges on all slides

Notes

  • The overflow-hidden on the sticky div prevents scroll-driven content from bleeding
  • For useScroll with target, the ref goes on the outer runway div, not the sticky div
  • Map scroll progress to slide index: useTransform(scrollYProgress, [0, 0.33, 0.34, 0.66, 0.67, 1], [0, 0, 1, 1, 2, 2])
  • On mobile, skip sticky entirely — use a simple stacked layout with whileInView animations instead
  • Use useState + useEffect with motionValue.on('change') to convert Framer Motion values to React state for conditional rendering
Weekly Installs
1
First Seen
7 days ago