Next.js/React滑块点击后动画延迟1-3秒,疑useState所致求优化方案
问题分析与解决方案
核心问题
- 闭包陷阱:点击事件中访问的
isLong和pathIndex是组件当前渲染周期的旧值,React的useState更新是异步的,这会导致动画逻辑判断错误,进而引发延迟或异常。 - 无用状态:
color状态未被使用,却会触发组件重渲染,增加不必要的性能开销。 - Motion状态与React状态不同步:
progress(Framer Motion的useMotionValue)和pathIndex(React状态)未绑定同步,页面初始化或状态更新时可能出现状态不一致,导致动画触发延迟。
优化方案
1. 用useRef替代useState存储状态
useRef的current属性是实时可变的,不会被闭包捕获旧值,能避免异步更新带来的状态不一致问题,同时减少组件因useState更新触发的重渲染。
2. 移除无用状态
删掉未使用的color状态,减少组件重渲染次数。
3. 同步Motion状态与逻辑判断
直接通过progress的当前值或ref存储的状态来控制动画逻辑,确保状态实时生效。
修改后的代码
import { motion, useMotionValue, animate, useTransform } from 'framer-motion' import * as React from 'react' import { useRef, useEffect } from 'react' import { getIndex, useFlubber } from "./use-flubber"; import { paths } from './paths.jsx' const colors = [ '#000000', '#000000' ] export default function AnimationBar(){ // 用useRef替代useState,避免闭包陷阱 const isLongRef = useRef(false); const pathIndexRef = useRef(1); const progress = useMotionValue(pathIndexRef.current); const fill = useTransform(progress, paths.map(getIndex), colors); const path = useFlubber(progress, paths); // 初始化时同步progress和pathIndex useEffect(() => { progress.set(pathIndexRef.current); }, []); return( <div className="justify-center mx-auto"> <motion.svg width="300" height="100" viewBox="0 0 300 100" initial="hidden" animate="visible" className="justify-center mx-auto"> <motion.line x1="50" y1="30" x2="250" y2="30" stroke="#570df8" variants={{ short: { x2: 50 }, long: { x2: 200 } }} animate={isLongRef.current ? "long" : "short"} /> <motion.g transform="translate(40 18)" clipPath="url(#_clipPath_rwZJXlpW1ubtVOGd58AkztYmsTFUvvCx)" > <motion.path d={path} fill={fill} stroke="none" /> </motion.g> <motion.text x={75} y={40} fontSize={24} fill={fill} animate={isLongRef.current ? "black" : "white"} > {isLongRef.current ? "Stop" : "Start"} </motion.text> <motion.line x1="50" y1="30" x2="50" y2="30" stroke="black" opacity="0" onClick={() => { // 实时更新ref的值 isLongRef.current = !isLongRef.current; const currentPathIndex = pathIndexRef.current; const animation = animate(progress, currentPathIndex, { duration: 0.3, ease: "easeInOut", onComplete: () => { if (!isLongRef.current) { progress.set(0); pathIndexRef.current = 1; } else { pathIndexRef.current = 0; } } }); return () => animation.stop(); }} /> </motion.svg> </div> ) }
额外优化建议
- 检查
use-flubber自定义hook的实现,确保其内部没有性能瓶颈(比如频繁的计算或不必要的重渲染)。 - 页面初始化时的动画延迟,可以尝试将
initial="hidden"改为initial={false},避免初始动画的额外开销,或者调整Framer Motion的初始配置。
内容的提问来源于stack exchange,提问作者Codeitfast
相关产品推荐
相关产品推荐

