【個人開発】Next.jsでのWebサイト制作 #2　共通機能（React hooks × css animateによるアニメーション機能）の実装

Next.jsでのWebサイト制作 #2では、共通機能（React hooks × css animateによるアニメーション機能）の実装を紹介をしたいと思います。

#1はこちらから読めます。
↓↓↓

https://qiita.com/Naoto_eng_7010/items/fbf3bd221abcc632f728

## アニメーションの共通化
今回制作したWebサイトでは、以下２つのアニメーションをあらゆる部分で共通使用しています。

* 画面更新後の秒数をトリガーに発火するアニメーション
* スクロールをトリガーに発火するアニメーション

今回はこれらのアニメーションの実装方法を紹介します。

また、前回のレイアウト同様、上記アニメーションは共通機能になるため、app/componentsの直下にAnimation.tsxを作成し、実装しました。


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3817219/874d8c3a-6490-bb27-44d2-3cbeb6860071.png)

## 画面更新後の秒数をトリガーに発火するアニメーション

まずは、画面更新後の秒数をトリガーに発火するアニメーション機能の紹介です。

↓イメージ
![タイトルなし.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3817219/010727aa-3b76-7e29-883f-d898557944aa.gif)

上記イメージの通り、トップ画面を読み込んでから数秒後に、メイン画像に被せるようにメッセージが左からスライドインして出現します。

### 実装方法

```tsx
// time:何秒後に出現させるか,  direction：要素の移動方向
export const TimeFadeIn = ({ children, time, direction }: any) => {
  // 要素の出現を管理
  const [inView, setInView] = useState(false);

  useEffect(() => {
    // コンポーネントがマウントされた後、time秒後に inViewをtrue に設定
    const timer = setTimeout(() => {
      setInView(true);
    }, time);

    // コンポーネントがアンマウントされた際にタイマーをクリア
    return () => clearTimeout(timer);
  }, []);

  return (
    // inViewがtrueになったらopacity-100となり表示される
    <div
      className={`${inView ? "opacity-100" : `opacity-0 ${direction === "right" ? "translate-x-[50%]" : "translate-x-[-50%]"}`} 
      duration-[1s]`}
    >
      {children}
    </div>
  );
};
```

上記の通り、useState, useEffect, setTimeoutを組み合わせることで、
コンポーネントがマウントされてから、引数で与えられた秒数がたった際に、stateがtrueになり、それに応じてJSX内のTailwindが動的に変化するよう実装しました。


Tailwindではopacityとtranslateを動的に変化させています。


引数のdirectionは要素の移動方向を判断するためのものであり、rightの場合は"translate-x-[50%]"として右に移動 、それ以外の場合は "translate-x-[-50%]"として左に移動するようにしています。


上記により、「inView」がtrueになったら、「1秒かけて、要素を右移動しながら出現させる。」という動きを実現しています。

## スクロールをトリガーに発火するアニメーション

続いて、スクロールをトリガーに発火するアニメーション機能の紹介です。

↓イメージ
![タイトルなし.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3817219/48c8c555-3bcd-a0b8-d137-e49387f04ea7.gif)

GIFだと分かりづらいかと思いますので、ぜひ実際にHPにアクセスして試してみてください。
https://ty-official-hp-14.vercel.app/

上記の通り、画面をスクロールした際に、さまざまなアニメーションとともに要素を出現させる機能を実装しました。

### 実装方法

```tsx
// animation:アニメーションの種類,  rootMargin:どのくらいスクロールした時に発火させるか
export const Animation = ({
  children,
  animation,
  rootMargin,
}: {
  children: any;
  animation: string;
  rootMargin: string;
}) => {
  // ref要素(以下のdivタグ)がrootMarginに来た時点で、inViewがtrueになる
  const { ref, inView } = useInView({
    // ここに位置を設定
    rootMargin: rootMargin,
    // 最初の一度だけ実行
    triggerOnce: true,
  });

  return (
    <div
      ref={ref}
      className={`${
        // inViewがtrueになったら、引数で受け取ったアニメーションを使用して要素を表示
        inView ? "animate__animated animate__" + animation : "opacity-0"
      } `}
    >
      {children}
    </div>
  );
};
```

上記の通り、css animateとreact-intersection-observerのuseInViewを組み合わせることで、スクロール状況を監視＆検知し、アニメーションとともに要素を出現させるという動きを実現しています。

こちらについては、以下の記事で実装方法を詳しく説明しておりますので、ここでの説明は割愛させていただきます。

https://qiita.com/Naoto_eng_7010/items/f07f53dc7fd3725dec67

記事内のサンプルコードにおける、rootMargin：に設定する数値と、対象要素のclassNameに割り当てるanimateの種類を引数で受け取るようにして、汎用性を高めたものが上記のコードになります。

## 最後に
今回は、プロジェクト全体で使用している共通機能について、紹介しました。

次回はトップページの内容について、紹介したいと思います。
トップページは、three.jsで3Dモデルを表示したりと、紹介したい内容がたくさんありますので、ぜひ次回もお読みいただければ幸いです！

ありがとうございました。

p.s. 
未熟エンジニアなもので、間違った技術の使い方等、たくさんしていると思います。
そんな時はコメントで教えていただけるととても嬉しいです。