【React】スクロールで表示する固定メニューの作り方

# はじめに
この記事では、ReactとTypeScriptを使用して、ページを一定の位置までスクロールすると表示されるメニューの作り方をご紹介します。メニューは滑らかにフェードインおよびフェードアウトするようにCSSでアニメーションを追加します。

# 完成イメージ

![ezgif.com-crop.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3536873/2f50f302-6bcc-0c06-6f07-6d7a7ccbfe18.gif)

# ディレクトリ

```
scroll-menu
├── node_modules
├── public
├── src
│   ├── App.css
│   ├── App.tsx
│   ├── index.css
│   ├── index.tsx
│   ├── Menu.css
│   └── Menu.tsx
├── tailwind.config.js
├── package.json
└── tsconfig.json
```

# 作り方

## 1. プロジェクトのセットアップ
まず、TypeScriptを使用したReactプロジェクトを作成して、Tailwind CSSとdaisyUIをインストールします。

```
npx create-react-app scroll-menu --template typescript
cd scroll-menu
npm install -D tailwindcss 
npx tailwindcss init -p
npm install daisyui
```

## 2. Tailwind CSSとdaisyUIの設定

`tailwind.config.js` ファイルを以下のように設定します。

```ts
/** @type {import('tailwindcss').Config} */
module.exports = {
  content: ["./src/**/*.{js,jsx,ts,tsx}"],
  theme: {
    extend: {},
  },
  plugins: [require("daisyui")],
};

```

`src/index.css` ファイルにTailwind CSSのベーススタイルをインポートします。

```css
@tailwind base;
@tailwind components;
@tailwind utilities;
```

## 3. Menuコンポーネントの作成

次に、Menuコンポーネントを作成します。`src/Menu.tsx` ファイルを作成し、以下のコードを追加します。

```tsx
import React from 'react'
import "./Menu.css";

interface MenuProps {
  show: boolean;
}

const Menu: React.FC<MenuProps> = ({show}) => {
  return (
    <ul className={`menu bg-base-200 rounded-box w-56 ${show ? 'show' : ''}`}>
      <li>
        <h2 className="menu-title">Title</h2>
        <ul>
          <li><a>Item 1</a></li>
          <li><a>Item 2</a></li>
          <li><a>Item 3</a></li>
        </ul>
      </li>
    </ul>
  );
}

export default Menu
```

`show` については、この後に説明する**スクロールイベントの処理**で管理していて、親コンポーネントの `App.tsx` から子コンポーネントの `Menu.tsx` にPropsとして渡されてきます。

この行の `${show ? 'show' : ''}` で渡されてきた `show` プロパティの値に基づいて CSSの `show` クラスを動的に追加しています。 `true` の場合は `show` クラスが追加され、メニューが表示されます。 `false` の場合は `show` クラスが追加されず、メニューは非表示のままです。

`bg-base-200 rounded-box w-56` はdaisyUIが提供しているスタイルです。


```tsx
<ul className={menu bg-base-200 rounded-box w-56 ${show ? 'show' : ''}}>
```

## 4. CSSの設定
表示/非表示のアニメーションを追加するために、CSSファイルを作成して設定します。`src/Menu.css` ファイルを作成し、以下の内容を追加します。

daisyUIで設定している `menu` クラスに位置の調整と表示・非表示のスタイルを追加しています。

```css
.menu {
  position: fixed;
  top: 15px;
  right: 25px;
  opacity: 0;
  transition: opacity 0.5s ease, transform 0.5s ease;
}

.menu.show {
  opacity: 1;
} 
```

## 5. スクロールイベントの処理
次に、スクロールイベントを処理してメニューの表示/非表示を切り替えるコードを`src/App.tsx` に追加します。

```tsx
import React, { useEffect, useState } from 'react';
import './App.css';
import Menu from './Menu';

function App() {
  const [showMenu, setShowMenu] = useState(false)

  const handleScroll = () => {
    const scrollTop = window.scrollY;
    if (scrollTop > 50) { // 50px以上スクロールしたら表示
      setShowMenu(true);
    } else {
      setShowMenu(false)
    }
  };

  useEffect(() => {
    window.addEventListener('scroll', handleScroll);
    return () => {
      window.removeEventListener("scroll", handleScroll);
    };
  }, []);

  return (
    <div className="App">
      <header className="App-header">
        <h1 className="text-4xl font-bold text-black">Scroll Menu</h1>
      </header>
      <Menu show={showMenu} />
    </div>
  );
}

export default App;

```

`handleScroll` 関数でスクロールに位置をチェックして、`showMenu` の状態を更新します。今回の場合、**スクロールの位置が50px以上でメニューを表示**、**それ以外で非表示**の切り替えをしています。

```tsx
const handleScroll = () => {
    const scrollTop = window.scrollY;
    if (scrollTop > 50) { // 50px以上スクロールしたら表示
      setShowMenu(true);
    } else {
      setShowMenu(false)
    }
  };
```

以下は、コンポーネントが初めてレンダリングされたときにスクロールイベントリスナーを追加し、ユーザーがページをスクロールするたびに `handleScroll` 関数が実行されるようにしています。

また、`return () => {
      window.removeEventListener("scroll", handleScroll);
    };` でコンポーネントが破棄されるときにイベントリスナーを削除することで、不要なイベント処理を防いでいます。

```tsx
useEffect(() => {
    window.addEventListener('scroll', handleScroll);
    return () => {
      window.removeEventListener("scroll", handleScroll);
    };
  }, []);
```

親コンポーネントの `App.tsx` から子コンポーネントの `Menu.tsx` へPropsを渡しています。　`showMenu` は親コンポーネントで管理されているスクロールイベントの状態で、`true` または `false` になります。

```tsx
 <Menu show={showMenu} />
```

# おわりに
以上で、ReactとTypeScriptを使用してスクロール時に表示されるメニューの作り方を説明しました。この方法を使えば、ユーザーが特定の位置までスクロールしたときにメニューを表示するインタラクティブなWebアプリケーションを簡単に作成できます。必要に応じてカスタマイズして、自分のプロジェクトに取り入れてみてください。