You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何让Framer Motion仅在元素进入视口时执行滚动动画

问题分析

你移除else分支后动画仅触发一次的原因是:第一次元素进入视口时,control执行了visible动画,元素停留在动画后的状态;当元素离开视口,isInView变为false但你未做任何操作,control仍保持visible状态;后续元素再次进入时,调用control.start("visible")不会触发新的动画——因为Framer Motion不会重复执行当前已处于激活状态的动画。

以下是两种符合需求的解决方案:

方案1:动画仅触发一次(第一次进入后永久保持状态)

适合只需要元素首次进入视口时执行一次动画,后续进出不再变化的场景。

修改后的Hook代码:

import { useAnimation, useInView } from "framer-motion"
import { useEffect, useRef } from "react"

export const useAnimateOnScroll = (inView) => {
  const parentRef = useRef(null)
  const control = useAnimation()

  // 开启once: true,isInView仅在元素首次进入视口时变为true,后续保持不变
  const isInView = useInView(parentRef, {
    once: true,
    amount: inView,
  })

  useEffect(() => {
    if (isInView) {
      control.start("visible")
    }
    // 移除else分支,不处理元素离开的逻辑
  }, [isInView, control])

  return {
    parentRef,
    control
  }
}

方案2:每次进入视口都触发动画(离开时保持当前状态)

适合需要元素每次进入视口都重新播放动画,离开时不执行反向动画的场景。

修改后的Hook代码:

import { useAnimation, useInView } from "framer-motion"
import { useEffect, useRef, useState } from "react"

export const useAnimateOnScroll = (inView) => {
  const parentRef = useRef(null)
  const control = useAnimation()
  // 标记元素是否处于视口外,用于判断是否需要重置动画状态
  const [isOutOfView, setIsOutOfView] = useState(false)

  const isInView = useInView(parentRef, {
    once: false,
    amount: inView,
  })

  useEffect(() => {
    if (isInView) {
      if (isOutOfView) {
        // 先无过渡重置到初始状态,再播放动画
        control.set("hidden")
        control.start("visible")
        setIsOutOfView(false)
      } else {
        // 首次进入时直接播放动画
        control.start("visible")
      }
    } else {
      // 元素离开时仅标记状态,不执行反向动画
      setIsOutOfView(true)
    }
  }, [isInView, control, isOutOfView])

  return {
    parentRef,
    control
  }
}

示例使用方式

配合Framer Motion的variants定义动画效果:

import { motion } from "framer-motion"
import { useAnimateOnScroll } from "./useAnimateOnScroll"

const AnimatedSection = () => {
  const { parentRef, control } = useAnimateOnScroll(0.5)

  const animationVariants = {
    hidden: { opacity: 0, y: 30 },
    visible: { opacity: 1, y: 0, transition: { duration: 0.7 } }
  }

  return (
    <motion.div
      ref={parentRef}
      variants={animationVariants}
      initial="hidden"
      animate={control}
      className="section-content"
    >
      滚动到此处触发动画
    </motion.div>
  )
}

内容的提问来源于stack exchange,提问作者first

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.20 05:13:12