如何结合React Intersection Observer与Framer Motion useTransform实现视口滚动触发的多区块复用动画
Hey there! I’ve worked through your scenario and I’ll walk you through how to combine React Intersection Observer with Framer Motion’s useTransform for smooth scroll-triggered animations across your page sections.
Core Idea
The goal is to use React Intersection Observer to detect when a section enters (or moves within) the viewport, then feed that visibility state or scroll progress into Framer Motion’s useTransform to drive your animations. Let’s break this down into two common use cases: one-time enter animations, and continuous scroll-driven animations.
1. One-Time Enter Animation (Triggered on First View)
This is perfect for sections that animate once when they first scroll into view. Here’s how to adjust your component:
First, make sure you’re importing the right hooks from both libraries:
import { motion, useTransform, useAnimation } from "framer-motion"; import { useInView } from "react-intersection-observer";
Then create a reusable Section component that ties everything together:
const Section = ({ children }) => { // Track visibility of the section const [ref, inView] = useInView({ triggerOnce: true, // Animate only once threshold: 0.1 // Trigger when 10% of the section is in view }); // Map visibility state to animation values with useTransform const opacity = useTransform(inView, [false, true], [0, 1]); const yPosition = useTransform(inView, [false, true], [50, 0]); return ( <motion.section ref={ref} style={{ opacity, y: yPosition }} transition={{ duration: 0.6, ease: "easeOut" }} className="section" > {children} </motion.section> ); };
How this works:
useInViewgives us arefto attach to our section, and aninViewboolean that tells us if the section is visible.useTransformtakes theinViewstate and maps it to our desired animation properties: when the section is hidden (false), it’s transparent (opacity: 0) and shifted down (y: 50); when visible (true), it fades in and slides up to its natural position.- The
transitionprop controls the timing and easing of the animation for a polished feel.
2. Continuous Scroll-Driven Animation (Animate as You Scroll)
If you want animations that respond to how much of the section is visible (e.g., scaling or fading as the user scrolls through the section), we can use the intersectionRatio from the Intersection Observer entry:
const Section = ({ children }) => { const [ref, inView, entry] = useInView({ triggerOnce: false, // Keep tracking as the user scrolls threshold: 0 // Start tracking as soon as any part of the section enters view }); // Get the ratio of the section that's visible (0 = none, 1 = fully visible) const scrollProgress = entry?.intersectionRatio || 0; // Map scroll progress to animation values const scale = useTransform(scrollProgress, [0, 1], [0.8, 1]); const opacity = useTransform(scrollProgress, [0, 0.2, 1], [0, 0.5, 1]); return ( <motion.section ref={ref} style={{ opacity, scale }} className="section" > {children} </motion.section> ); };
How this works:
- The
entryobject fromuseInViewincludesintersectionRatio, a value between 0 and 1 representing how much of the element is in the viewport. useTransformuses this ratio to create smooth transitions: as the section scrolls into view, it scales from 80% to 100% and fades from fully transparent to opaque (with a midpoint at 20% visibility).
Key Tips
- Reusability: Wrap this logic into a reusable component like
AnimatedSectionso you can apply the same animation to all your page sections without repeating code. - Customization: Adjust the
thresholdinuseInViewto control when the animation starts (e.g.,0.5triggers when half the section is visible). - Variants Alternative: If you prefer using Framer Motion’s variants, you can pair
useAnimationcontrols with theinViewstate instead ofuseTransform:const controls = useAnimation(); useEffect(() => { controls.start(inView ? "visible" : "hidden"); }, [controls, inView]); return ( <motion.section ref={ref} variants={{ hidden: { opacity: 0, y: 50 }, visible: { opacity: 1, y: 0 } }} animate={controls} initial="hidden" transition={{ duration: 0.6 }} > {children} </motion.section> );
内容的提问来源于stack exchange,提问作者DanBilly




