👻

React + tailwind css で画面表示されたら下の方からシュッと出てくるやつを実装

2023/07/25に公開

概要

React + tailwindcss のプロジェクトで、LPとかでよく見る「要素が画面内に入ったらシュッとフェードインする」挙動を実現したいと思いました。ググって引っかかってくる知見は jQuery をつかおうみたいな記事ばかりだったので、個人開発で現在使用している[1] Next.js + tailwindcss での構成でいい感じにできた実装を共有します。どなたかの参考になれば幸いです。

Tl;dr

以下の実装で AwesomeHogeComponent が画面に表示されたら下からシュッとフェードインする挙動を実現できるFadeInBottomコンポーネントを作成できました。

const AwesomeFadeInComponent: React.FC = () => {
  return (
    <FadeInBottom>
      <AwesomeHogeComponent />
    </FadeInBottom>
  );
};

使用例

以下のページで使用しています。
#パッチワーク2023 出演者情報 | patchwork official site

実装手順

tailwind.config.js にフェードインアニメーションのクラス名を定義する

まず、フェードインアニメーションを tailwindcss のカスタムクラスとして追加します。
tailwind.config.js に記載できるフェードインのアニメーション自体は Animasta [2] を使って作成しました。ここで作成した気に入ったエフェクトを tailwind.config.js に追加します。

https://animista.net/

module.exports = {
  // 中略
  theme: {
    extend: {
      // 中略

      // Animista で好みに生成したものを貼り付ける
      //FreeBSD-licensed CSS animation by Animista
      animation: {
        "fade-in-bottom": "fade-in-bottom 0.6s ease-out   both",
      },
      keyframes: {
        "fade-in-bottom": {
          "0%": {
            transform: "translateY(50px)",
            opacity: "0",
          },
          to: {
            transform: "translateY(0)",
            opacity: "1",
          },
        },
      },
    },
  },
};

react-intersection-observer を導入する

目的の動きを実現させるには、画面内に入ったタイミングで処理を発火させる必要があります。そのような処理が可能なライブラリとして react-intersection-observer [3] がありましたので、これをインストールします。

https://github.com/thebuilder/react-intersection-observer

yarn add react-intersection-observer

コンポーネントにクラス名を付与する Wrapper コンポーネントを作成する

最後に、コンポーネントが画面内に表示されたら受け取ったコンポーネントにアニメーションを加える Wrapper コンポーネントを作成します。

実装したものが以下です。この FadeInBottom で任意のコンポーネントを囲むとそのコンポーネントにが画面内に表示されたら下の方からシュッと出てくるアニメーションが付与されます。

import React from "react";
import { useInView } from "react-intersection-observer";

type Props = {
  children: React.ReactNode;
};

/**
 * 子要素を下からフェードインさせる
 *
 * @example
 * ```tsx
 * <FadeInBottom>
 *  <div>フェードインする要素</div>
 * </FadeInBottom>
 * ```
 */
export const FadeInBottom: React.FC<Props> = ({ children }) => {
  const { ref, inView } = useInView({
    // ref要素が現れてから50px過ぎたら
    rootMargin: "-50px",
    // 最初の一度だけ実行
    triggerOnce: true,
  });

  const fadeInClassName = inView ? "animate-fade-in-bottom" : "opacity-0";

  const wrappedChildren = React.Children.map(children, child => {
    if (React.isValidElement(child)) {
      const className = [child.props.className, fadeInClassName]
        .filter(el => el)
        .join(" ");

      return React.cloneElement(child as React.ReactElement, {
        ref,
        className,
      });
    } else {
      return child;
    }
  });

  return <>{wrappedChildren}</>;
};

画面内に表示されたら処理を発火する

画面内に表示されたら処理を発火する動きには、前の行程でインストールした react-intersection-observer を使用します。useInView をインポートして ref, inView を受け取ります。refを対象となるコンポーネントに渡すと、inView にはそのコンポーネントが画面内に現れた時に true になる boolean 値が入ってきます。この boolean 値を利用して画面内表示契機での処理を実現できます。

フェードイン処理

前の工程で tailwind.config.js にフェードインアニメーションの設定を追加しましたが、この設定の場合コンポーネントの className に animate-fade-in-bottom が付与すると下からフェードインする挙動になります。このクラスとコンポーネントを透明にする opacity-0 、前述の inView を組み合わせることで、画面内に表示されたらフェードインされるアニメーションを実現できます。具体的には、目的の要素に inView が false のときは opacity-0, true のときは animate-fade-in-bottom を付与します。
またこの時、useInView には triggerOnce を設定しているので一度表示されたものはその後 opacity-0 に戻らない(一度表示されたコンポーネントはリロードしない限り非表示には戻らない)ようになっています。

終わりに

囲うだけで楽にシュッとエフェクトを実現できるようになりました。調子に乗ってやりすぎるとパフォーマンスに影響あるかも。知らんけど。

脚注
  1. #patchwork2022 の運営側を色々システム化したあれこれ - Qiita で紹介している、ホームページで使用しています ↩︎

  2. Tailwindcssで超簡単にアニメーションをつける ↩︎

  3. Reactでお手軽にIntersection Observerを使う ↩︎

GitHubで編集を提案
CureApp テックブログ

Discussion