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

如何解决framer-motion循环内使用useTransform违反Hook规则的问题

修复循环内使用React Hook的framer-motion动画逻辑

原代码在animations.map循环内调用useViewportScroll和useTransform,违反了React Hook的调用规则(Hook必须在组件/自定义Hook的顶层执行,不能在循环、条件判断或嵌套函数内),导致应用异常。以下是合规的改写方案:

import { SanityAnimation } from '@sonos-inc/web-lib-types'
import { MotionProps, useViewportScroll, useMemo } from 'framer-motion'

export const useSanityScrollAnimations = (
  scrollStart: number,
  scrollEnd: number,
  animations?: Array<SanityAnimation & { startOffset?: number }>
): MotionProps['style'] => {
  try {
    // 顶层调用Hook,严格遵循React Hook规则
    const { scrollYProgress } = useViewportScroll()

    return useMemo(() => {
      if (!animations?.length) return {}

      return Object.fromEntries(
        animations.map((a) => {
          const { animation, direction, value } = a
          // 计算动画的起始值与结束值
          const from = direction === 'from' ? value : 0
          const to = direction === 'to' ? value : 0

          // 用MotionValue的pipe方法替代useTransform Hook,实现值转换
          const rawValue = scrollYProgress.pipe(
            [scrollStart, scrollEnd],
            [from, to]
          )
          const percent = rawValue.pipe((val) => `${val ?? 0}%`)

          return [animation, percent]
        })
      )
    }, [animations, scrollStart, scrollEnd, scrollYProgress])
  } catch (e) {
    console.error('Error in useSanityScrollAnimations:\n', e)
    return {}
  }
}

关键改动说明

  1. Hook移至顶层:将useViewportScroll放在自定义Hook的最外层调用,确保每次渲染时Hook调用顺序一致
  2. 用pipe替代useTransform:framer-motion的MotionValue内置pipe方法,可实现与useTransform相同的响应式值转换,且无需作为Hook调用,能安全用于循环逻辑
  3. 添加useMemo缓存:避免每次渲染都重新生成动画样式对象,只有当依赖项(动画列表、滚动范围、滚动进度)变化时才重新计算,优化性能

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.08 01:25:12