この記事は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
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
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/router
の withRouter
を保持する Context を宣言し、 Transition
コンポーネントを呼び出します。
Transition
コンポーネントでは router
の値を react-spring
の useTransition
に渡すことで Transition を実装しています。
CodeSandbox に全体のソースがあるので試したい方はそちらからご確認ください。
この実装だとページ全体ではなく、コンテンツだけあるいはサイドバーだけ Transition させることも可能で、その場合は _app.jsx ではなくコンテンツのラッパーコンポーネントやサイドバーで PageTransition
を呼び出すことで出来ると思うのでお試しください。
それでは、よいReactライフを。