如何解决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 {} } }
关键改动说明
- Hook移至顶层:将
useViewportScroll放在自定义Hook的最外层调用,确保每次渲染时Hook调用顺序一致 - 用
pipe替代useTransform:framer-motion的MotionValue内置pipe方法,可实现与useTransform相同的响应式值转换,且无需作为Hook调用,能安全用于循环逻辑 - 添加
useMemo缓存:避免每次渲染都重新生成动画样式对象,只有当依赖项(动画列表、滚动范围、滚动进度)变化时才重新计算,优化性能
内容的提问来源于stack exchange,提问作者Szilard Lazar
相关产品推荐
相关产品推荐

