LoginSignup
38
26

More than 3 years have passed since last update.

Next.js で Page Transition を実装する

Last updated at Posted at 2019-12-15

この記事はNext.js Advent Calendar 2019 16日目の記事です。

Qiitaに書くのは久々の @_Ria0130です。

SPAを作っていても Transition を実装したことない方結構いらっしゃるんじゃないでしょうか?
ページ遷移と Transition の組み合わせって結構難しくって、クロスフェードとかをしようとすると遷移前と遷移後の要素をどちらも描画したままスタイルを適応する必要があります。

React など VirtualDOM を扱うライブラリを採用しているとDOMの反映を自動でしてくれて楽な反面、要素のコントロールが自分でできないので少し複雑です。

React で有名な Transition ライブラリだと React Transition Group があるのですが、今回は UIT INSID Eep.29 で紹介されていて気になっていた react-spring を使って実装する方法を紹介していきます。

_app.jsx

_app.jsx
import React from "react";
import App, { Container } from "next/app";
import { PageTransition } from "../components/PageTransition";
import { GlobalStyle } from "../components/GlobalStyle";

export default class extends App {
  static async getInitialProps({ Component, ctx }) {
    let pageProps = {};

    if (Component.getInitialProps) {
      pageProps = await Component.getInitialProps(ctx);
    }

    return { pageProps };
  }

  render() {
    const { Component: SsrComponent, pageProps: ssrPageProps } = this.props;

    return (
      <Container>
        <GlobalStyle />
        <PageTransition>
          {({ Component, pageProps }) => {
            return Component ? (
              <Component {...pageProps} />
            ) : (
              <SsrComponent {...ssrPageProps} />
            );
          }}
        </PageTransition>
      </Container>
    );
  }
}

_app.jsx では次で説明する <PageTransition> でページのコンポーネントを囲みます。

PageTransition.jsx

PageTransition.jsx
import React, { useContext } from "react";
import styled from "styled-components";
import { useTransition, animated } from "react-spring";

import { withRouter } from "next/router";

const Context = React.createContext();

const Provider = ({ router, children }) => (
  <Context.Provider value={router}>{children}</Context.Provider>
);

const useRouter = () => useContext(Context);
const RouterContextProvider = withRouter(Provider);

const Transition = ({ children, ...props }) => {
  const router = useRouter();
  const transitions = useTransition(router, router => router.pathname, {
    from: { opacity: 0 },
    enter: { opacity: 1 },
    leave: {
      position: "absolute",
      top: 0,
      right: 0,
      bottom: 0,
      left: 0,
      opacity: 0
    }
  });

  return (
    <>
      {transitions.map(({ item, props: style, key }) => {
        const { Component, props } = item.components[item.pathname] || {};

        return (
          <Page key={key} style={style}>
            {children(
              item ? { Component, pageProps: props && props.pageProps } : {}
            )}
          </Page>
        );
      })}
    </>
  );
};

export const PageTransition = ({ children, ...props }) => {
  return (
    <RouterContextProvider>
      <Transition {...props}>{children}</Transition>
    </RouterContextProvider>
  );
};

const Page = styled(animated.main)`
  min-height: 100%;
  height: 0;
  display: flex;
  justify-content: center;
  align-items: center;
  flex-direction: column;
`;

PageTransition.jsx ではコンポーネントが複数あって Context も宣言してしまってるのですが、なるべく PageTransition を使う側で _app.jsx のようにシンプルになるようまとめて宣言しています。

PageTransition コンポーネントでは next/routerwithRouter を保持する Context を宣言し、 Transition コンポーネントを呼び出します。
Transition コンポーネントでは router の値を react-springuseTransition に渡すことで Transition を実装しています。

CodeSandbox に全体のソースがあるので試したい方はそちらからご確認ください。

この実装だとページ全体ではなく、コンテンツだけあるいはサイドバーだけ Transition させることも可能で、その場合は _app.jsx ではなくコンテンツのラッパーコンポーネントやサイドバーで PageTransition を呼び出すことで出来ると思うのでお試しください。

それでは、よいReactライフを。

38
26
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
38
26