🌈

画面遷移時に虹を架ける with framer-motion

2024/07/13に公開

はじめに

最近、UXを強化するにあたりframer-motionを使うことが割とあり画面遷移時に🌈を架ける方法を師匠に教えてもらったので備忘録も兼ねてその方法について残そうと思います。

結論

画面遷移時に以下のgif(gifなので動作がもっさりしてますが実際はもう少しキビキビ動きます)の様に画面遷移時に🌈が架かるようになります。

動画がもう少し簡単に貼れる様になると嬉しいなぁと思ったり、わかりやすくイメージしてもらうために今回gif変換したので

環境

Library version
Next.js 14.2.5
tailwind.css 3.4.1
framer-motion 11.3.2

何をしているのか

その1

ページ遷移時にフワッと消えて浮き上がってくるMotionを追加

PageTransition.tsx
'use client';

import { AnimatePresence, motion } from 'framer-motion';
import { usePathname } from 'next/navigation';
import { type ReactNode } from 'react';

type PageTransitionProps = {
  children: ReactNode;
};
const PageTransition = ({ children }: PageTransitionProps) => {
  const pathname = usePathname();

  return (
    <AnimatePresence>
      <div key={pathname}>
        <motion.div
          initial={{ opacity: 1 }}
          animate={{
            opacity: 0,
            transition: { delay: 1, duration: 0.4, ease: 'easeInOut' },
          }}
          className="pointer-events-none fixed top-0 h-screen w-screen bg-primary"
        />
        {children}
      </div>
    </AnimatePresence>
  );
};
export default PageTransition;

その2

StairTransition.ts

虹(Stair)のcomponentをwrapし虹の通過後に浮き上がるようにmotionを入れています。
※最終的には虹色にするつもりはないのでこの変数名にしてます。(変えるの面倒なので)

'use client';

import { AnimatePresence, motion } from 'framer-motion';
import { usePathname } from 'next/navigation';

import Stairs from './Stair';

const StairTransition = () => {
  const pathname = usePathname();
  return (
    <>
      <AnimatePresence mode="wait">
        <div key={pathname}>
          <div className="pointer-events-none fixed inset-x-0 top-0 z-40 flex h-screen w-screen">
            <Stairs />
          </div>
        </div>
        <motion.div
          className="pointer-events-none fixed top-0 h-screen w-screen bg-primary"
          initial={{ opacity: 1 }}
          animate={{
            opacity: 0,
            transition: { delay: 1, duration: 0.4, ease: 'easeInOut' },
          }}
        />
      </AnimatePresence>
    </>
  );
};

export default StairTransition;

この部分はなくても動くのですがより虹の通過後、ページがフワッと浮き出るようにしたい場合は必要です(実装では入れています)

<motion.div
  className="pointer-events-none fixed top-0 h-screen w-screen bg-primary"
  initial={{ opacity: 1 }}
  animate={{
   opacity: 0,
   transition: { delay: 1, duration: 0.4, ease: 'easeInOut' },
  }}
/>

その3

虹🌈が階段状になるようにメインとなるcomponentを準備します。
※最終的には虹色にするつもりはないのでこの変数名にしてます。(変えるの面倒なので)

Stair.ts
import { motion } from 'framer-motion';

import { cn } from '@/lib/utils';

const stairAnimation = {
  initial: {
    top: '0%',
  },
  animate: {
    top: '100%',
  },
  exit: {
    top: ['100%', '0%'],
  },
};

const RAINBOW_STEP = 7;

const RAINBOW_COLORS = [
  'bg-red-400',
  'bg-orange-400',
  'bg-yellow-400',
  'bg-green-400',
  'bg-blue-400',
  'bg-indigo-400',
  'bg-purple-400',
];

const reverseIndex = (index: number) => {
  const totalStep = RAINBOW_STEP;
  return totalStep - index - 1;
};

const Stairs = () => {
  return (
    <>
      {[...Array(RAINBOW_STEP)].map((_, i) => (
        <motion.div
          key={i}
          variants={stairAnimation}
          initial="initial"
          animate="animate"
          exit="exit"
          transition={{
            duration: 0.4,
            ease: 'easeInOut',
            delay: reverseIndex(i) * 0.1,
          }}
          className={cn('relative size-full', RAINBOW_COLORS[i])}
        />
      ))}
    </>
  );
};
export default Stairs;

その4

ページ遷移時にこれらのmotionを発火させられるようにlayout.tsに盛り込んでいきます。
今回はRootLayoutに入れていますが、context毎にグルーピングしたlayoutでも同様のことは可能です。
※fontについては別にこの通りにしなくても大丈夫です。ただの好みです

layout.ts
import clsx from 'clsx';
import type { Metadata } from 'next';
import { JetBrains_Mono, Noto_Sans_JP } from 'next/font/google';

import './globals.css';
import Header from '@/components/Header';
import PageTransition from '@/components/PageTransition';
import StairTransition from '@/components/StairTransition';

const jetbrainsMono = JetBrains_Mono({
  subsets: ['latin'],
  weight: ['100', '200', '300', '400', '500', '600', '700', '800'],
  variable: '--font-jetbrainsMono',
});

const notoSansJP = Noto_Sans_JP({
  subsets: ['latin'],
  variable: '--font-noto-sans-jp',
});

export const metadata: Metadata = {
  title: 'Create Next App',
  description: 'Generated by create next app',
};

export default function RootLayout({
  children,
}: Readonly<{
  children: React.ReactNode;
}>) {
  return (
    <html lang="jp">
      <body className={clsx(jetbrainsMono.variable, notoSansJP.variable)}>
        <Header />
        <StairTransition />
        <PageTransition>{children}</PageTransition>
      </body>
    </html>
  );
}

まとめ

そろそろ自己紹介のポートフォリオでもちゃんと作るかといろいろ参考にしながら作っていく中で本来は一色での画面遷移(カッコいいので黒でやろうとしてる)を実装したあとにどうせならネタ用にと虹色に変えて実装し直してみました。ちょっと奇抜過ぎるので自分のサイトではこの配色は採用はしないつもりですw(あくまで技術のネタということで)

余談

https://weathernews.jp/s/topics/201710/310135/

調べてみると虹そのものが国によって異なるという訳ではなく同じらしいのですが、色分けを細かくしているかどうかの違いが各国によって色々あるみたいですね

とはいえ2色はざっくりしすぎやろwwとは思いますが文化の違いは面白いです🌈

補足

同じ要領で最終的には遷移時のmotionはこんな感じにしました🙇

Discussion