如何在用 AnimatePresence 包装的 Next.js 页面中导航和滚动到具有 ID 的元素

How to Navigate and Scroll to an Element with ID in a Next.js Page wrapped with AnimatePresence

我正在使用 Framer Motion to animate Next.js page transitions. However using the using AnimatePresence 中断 hash link 导航,页面不再转到目标 id 元素。

页面转换是完美的,直到您想导航到页面上的苛刻 ID :(

// I have a link component setup like this
// index.tsx
<Link href="/about#the-team" scroll={false}>
  <a>The Team</a>
</Link>

// Targeting another page `about.tsx` with the id
// about.tsx

{/* ...many sections before.. */}
<section id="the-team">{content}</section>

我有一个自定义 _app.tsx,如下所示。

// _app.tsx
import { AppProps } from 'next/app';
import { useRouter } from 'next/router';
import { AnimatePresence } from 'framer-motion';

const MyApp = ({ Component, pageProps }: AppProps): JSX.Element => {
  const router = useRouter();
  return (
    <AnimatePresence exitBeforeEnter>
      <Component {...pageProps} key={router.route} />
    </AnimatePresence>
  );
};

export default MyApp;

我希望直接转到带有 id="the-team" 的部分,但它不起作用。使用散列 link 刷新页面显示它最初位于目标元素但很快跳到顶部。它是如此之快和容易错过。如何保留页面转换但仍然能够导航到哈希 ID?

罪魁祸首是 exitBeforeEnter 支持 AnimatePresence。删除道具修复了哈希 id 导航,但破坏了我的一些 use-case.

If set to true, AnimatePresence will only render one component at a time. The exiting component will finish its exit animation before the entering component is rendered. - framer-motion docs

我不能只删除 exitBeforeEnter 道具,因为我已经包含它来修复我遇到的一个错误,该错误在进入页面中的目标节点与退出页面的旧实例中的相同节点发生冲突.例如,退出页面中动画 svg header 上的 ref 逻辑与进入页面的 header svg 引用逻辑发生冲突。

为了两全其美,使用 onExitComplete "Fires when all exiting nodes have completed animating out",我给它传递了一个回调,检查来自 widow.location.hash 的散列,并使用 scrollIntoView 平滑滚动到 id 注意:onExitComplete 仅在 exitBeforeEnter prop 为 true.

时有效
// pages/_app.tsx
import { AppProps } from 'next/app';
import { useRouter } from 'next/router';
import { AnimatePresence } from 'framer-motion';

// The handler to smoothly scroll the element into view
const handExitComplete = (): void => {
  if (typeof window !== 'undefined') {
    // Get the hash from the url
    const hashId = window.location.hash;

    if (hashId) {
      // Use the hash to find the first element with that id
      const element = document.querySelector(hashId);

      if (element) {
        // Smooth scroll to that elment
        element.scrollIntoView({
          behavior: 'smooth',
          block: 'start',
          inline: 'nearest',
        });
      }
    }
  }
};

const MyApp = ({ Component, pageProps }: AppProps): JSX.Element => {
  const router = useRouter();
  return (
    <AnimatePresence exitBeforeEnter onExitComplete={handExitComplete}>
      <Component {...pageProps} key={router.route} />
    </AnimatePresence>
  );
};

export default MyApp;


直播CodeSandbox here.

PS:出于某种原因,沙盒预览中的 window.location.hash 始终为空字符串,破坏了哈希导航但打开了预览 in a separate browser tab 很有魅力。