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

Nextjs+Framer Motion惯性滚动页面转场问题求助

问题描述

我在Next.js中用Framer Motion实现惯性滚动,搭配Animate Presence做页面转场动画时遇到了几个问题:

  • 通过useScroll获取滚动进度,用弹簧物理效果做Y轴变换,页面导航时调用Animate Presence的onExitComplete回调执行window.scrollTo(0,0)重置滚动位置,但弹簧变换的特性导致滚动重置无法立即完成,新页面挂载时这个变换还在运行。
  • 在Next.js的Link组件里设置了scroll={false}禁用默认滚动到顶部的功能,想通过Animate Presence手动处理,但这个设置似乎不起作用。
  • 发现直接把scrollY传入变体对象的y属性能避免转场时的异常滚动,但如果用scrollY - 128这类表达式就会失效,当前变体代码如下:
const variants = {
    hidden: { opacity: 0, x: 0, y: 64 },
    enter: { opacity: 1, x: 0, y: 0 },
    exit: { opacity: 0, x: 0, y: scrollY },
};
解决方案

1. 修复滚动重置延迟问题

别依赖onExitComplete触发滚动重置,导航一开始就同步锁定滚动状态,同时给弹簧动画加onComplete回调确保滚动重置彻底完成:

  • 导航触发时(比如点击Link前),设个isNavigating状态标记,让当前页面的弹簧动画提前终止或切换成无物理效果的过渡。
  • 用requestAnimationFrame包裹window.scrollTo,确保在DOM更新前执行:
const handleNavigation = () => {
  setIsNavigating(true);
  requestAnimationFrame(() => {
    window.scrollTo({ top: 0, behavior: 'instant' });
  });
};
  • 在Framer Motion组件上,根据isNavigating切换动画配置,比如把弹簧换成tween:
<motion.div
  animate={isNavigating ? 'exit' : 'enter'}
  transition={isNavigating ? { duration: 0.2, ease: 'easeInOut' } : { type: 'spring', stiffness: 300 }}
/>

2. 让Next.js Link的scroll={false}生效

确保用的是Next.js自带的next/link,且正确嵌套子元素(比如a标签),同时检查有没有全局滚动监听逻辑覆盖这个设置:

import Link from 'next/link';

<Link href="/about" scroll={false} onClick={handleNavigation}>
  <a>About</a>
</Link>

如果还是无效,就在_app.js里全局拦截导航事件手动控制滚动:

import { useRouter } from 'next/router';

const MyApp = ({ Component, pageProps }) => {
  const router = useRouter();
  useEffect(() => {
    const handleRouteChangeStart = () => {
      window.scrollTo(0, 0);
    };
    router.events.on('routeChangeStart', handleRouteChangeStart);
    return () => {
      router.events.off('routeChangeStart', handleRouteChangeStart);
    };
  }, [router]);
  return <Component {...pageProps} />;
};

3. 解决变体中表达式失效的问题

变体里的动态表达式不能直接写死,要改用animate/style动态传递,或者用useMotionValue计算:

  • 方法一:在组件内计算动态y值,传递给animate
const { scrollY } = useScroll();
const exitY = useTransform(scrollY, (y) => y - 128);

<motion.div
  variants={variants}
  animate={{ y: isNavigating ? exitY : 0 }}
/>
  • 方法二:把变体改成函数形式,接收动态参数
const getVariants = (scrollY) => ({
  hidden: { opacity: 0, x: 0, y: 64 },
  enter: { opacity: 1, x: 0, y: 0 },
  exit: { opacity: 0, x: 0, y: scrollY - 128 },
});

// 使用时
const variants = getVariants(scrollY.get());

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.21 10:06:26