Framer Motionを使ったアニメーション例

Reactでポートフォリオ作成時に「Framer Motion」を使ってアニメーションをつけたので、それをこの記事で紹介します。

この記事で紹介するアニメーション例は下記になります。

1. 画面表示時にテキストを順番に表示させるアニメーション
2. スクロール時に要素を拡大・縮小するアニメーション
3. 現在表示している要素に応じたヘッダーリンクの色を変えるアニメーション

## Framer Motionとは
Framer MotionはReactで使用できるアニメーションライブラリです。　　
下記の公式ドキュメントでも例が見れますが、簡単な記述で複雑なアニメーションを制御することができます。

https://www.framer.com/motion/

## Framer Motionの使い方
ここでは簡単に始め方を紹介します。
詳細が知りたい場合は、公式ドキュメントを確認してください。
※Framer Motion には React 18 以降が必要です。

Framer Motionは下記のコマンドを実行するとインストールできます。

```
npm install framer-motion
```

インストールが完了したらFramer Motionを使用したいJSXファイルやTSXファイルに下記のインポート文を追加します。

```javascript
import { motion } from "framer-motion"
```

上記のインポートした`motion`をアニメーションさせたい要素のタグに付けます。
例えば特定の`div`タグにアニメーションを追加したい場合は、その`div`を下記のように書き換えます。

```javascript
<motion.div />
```

このように`motion.XXX`とした要素にプロパティを追加していくことでアニメーションをさせることができます。

## テキストを順番に表示させるアニメーション
この項目では順番に文字が表示されるアニメーションの付け方について記載します。
イメージとしては下記の動画のものになります。
（雪が舞うようなアニメーションに関しては[particles.js](https://vincentgarreau.com/particles.js/)を使用しているので割愛。）

![ezgif.com-video-to-gif.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3115415/b33cf66e-9434-c6f7-641e-e797544e4ed0.gif)

まずは`Home.tsx`のコンポーネントを作成し`App.tsx`に読み込ませた後、下記のインポートを`Home.tsx`に行います。

```javascript:Home.tsx
import { useEffect } from "react";
import { motion, useAnimationControls } from "framer-motion";
```

`useEffect`はページを表示した初回のみアニメーションさせるため読み込みます。
ここでは`motion`の他に`useAnimationControls`も読み込みます。

### useAnimationControls
`useAnimationControls`は1つ、または複数のアニメーションの開始と停止を制御できる関数です。
 この関数を使うことによってアニメーションの開始タイミングをずらし、イメージのようなアニメーションができます。

使い方は`useAnimationControls()`を呼び出してアニメーションコントロールを作成します。

```javascript:Home.tsx
export const Home = () => {
    const controls = useAnimationControls();
}
```

### アニメーションさせる要素を作成
次にアニメーションさせる要素を記述していきます。

```javascript:Home.tsx
export const Home = () => {
    const controls = useAnimationControls();
    return(
            <div className="home">
                <div className="home__container">
                    <h1>
                        <span>text1</span>
                        {" "}
                        <span>text2</span>
                    </h1>
                    <h2>
                        <span>text3</span>
                        {" "}
                        <span>text4</span>
                    </h2>
                </div>
            </div>
        )
}
```

（CSSの記載は省略してますが）今は下図のような表示になっています。
<img width="600" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3115415/6ea5d0ad-026c-5c7c-faac-7bd2ce2c80b5.png" />

`<span>`を`<motion.span>`に変更し、その中に下記の記述を行います。

```javascript:Home.tsx
return(
        <div className="common__container">
            <div className="home">
                <h1>
                    <motion.span
                        custom={0}
                        initial="init"
                        animate={controls}
                        variants={textAnimation}
                    >
                    text1
                    </motion.span>
                    {" "}
                    <motion.span
                        custom={1}
                        initial="init"
                        animate={controls}
                        variants={textAnimation}
                    >
                    text2
                    </motion.span>
                </h1>
                <h2>
                    <motion.span
                        custom={2}
                        initial="init"
                        animate={controls}
                        variants={textAnimation}
                    >
                    text3
                    </motion.span>
                    {" "}
                    <motion.span
                        custom={3}
                        initial="init"
                        animate={controls}
                        variants={textAnimation}
                    >
                    text4
                    </motion.span>
                </h2>
            </div>
        </div>
    )
```

`<motion.span>`の中の属性は下記の意味があります。
* `custom`：`useAnimationControls()`で使用する数字です。（後述）
* `initial`：その要素の初期の状態を設定できます。直接スタイルを指定することも、後述の`variants`で設定したオブジェクトのkey名を指定することもできます。
* `animate`：ここに設定した内容に向かってアニメーションを行います。直接スタイルを指定することや、オブジェクトもしくは作成したコントローラーを指定することもできます。
* `variants`：ここにオブジェクトを設定すると`initial`や`animate`に直接スタイルを書かずとも、設定したオブジェクト内のkey名を記載するだけで、設定するスタイルの内容を反映することができます。

### variantsに設定するオブジェクトの作成
次にvariantsに設定するオブジェクトを作成します。

```javascript:Home.tsx
const controls = useAnimationControls();

const textAnimation = {
    init: {
        color: "transparent",
        textShadow: "0 0 100px #333, 0 0 100px #333",
        opacity: 0,
    },
};

return(
    ~省略~
)
```

`<motion.span>`のvariantsには`textAnimation`が既に設定されており、initialにその中の`init`が記載されているため`<motion.span>`の初期値として下記のスタイルが適用されます。

```javascript
color: "transparent",
textShadow: "0 0 100px #333, 0 0 100px #333",
opacity: 0,
```

### useEffect内にアニメーションの内容を記載
最後にuseEffectの中にアニメーションの内容を記載していきます。

```javascript:Home.tsx
const controls = useAnimationControls();

const textAnimation = {
    init: {
        color: "transparent",
        textShadow: "0 0 100px #333, 0 0 100px #333",
        opacity: 0,
    },
};

useEffect(() => {
    controls.start((i) => ({
        textShadow: [
            "0 0 90px #333, 0 0 90px #333",
            "0 0 3px #333, 0 0 3px #333",
            "0 0 0 #333",
        ],
        opacity: [0, 1, 1],
        transition: {
            ease: "linear",
            duration: 3,
            delay: i * 0.1,
        },
    }));
}, []);

return(
    ~省略~
)
```

`controls.start`を実行することでアニメーションがスタートします。
引数の`(i)`には`<motion.span>`内に記述した`custom`の数字が小さい順に入ってき、`custom`が小さい要素順にアニメーションが開始されます。
感覚としてはfor文に近しく、transitionのdelayにおいて`i * 0.1`としているので0.1sずつ遅れてアニメーションされます。

また、`animate`のプロパティには配列で値を渡すことによってキーフレームのようにすることができます。
ですので、上記の例では3秒間かけて「表示なし→ぼやけたテキスト→くっきりとしたテキスト」というようにアニメーションしています。
現状では下記のようになっています。

![ezgif.com-crop.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3115415/b8974760-28de-431a-79cd-4e266bc82fee.gif)


## スクロール時に要素を拡大・縮小するアニメーション
ここでは横にスクロールする時に要素を拡大・縮小するアニメーションの付け方について記載します。
イメージとしては下記の動画のものになります。

![ezgif.com-video-to-gif (1).gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3115415/7ad0cdb5-b8e1-f0a6-957e-16e761f05734.gif)

まずは横スクロールできるように`Element.tsx`コンポーネントとスタイルを追加します。

```javascript:App.tsx
import './style.scss'
import { Home } from "./components/Home";
import { Element } from './components/Element'; // 追加

const App = () => {

  return (
    <>
      <div className='common__screen'>　// 追加
        <div className='common__screen__container'>　// 追加
          <Home />
          <Element />　// 追加
        </div>
      </div>
    </>
  )
}

export default App
```

```javascript:Element.tsx
export const Element = () => {
    return(
        <div className="common__container">
            <div className="element">
                <h2>text5</h2>
            </div>
        </div>
    )
}
```

横スクロールさせるためのスタイルは下記のように当てておきます。

```scss:common.scss
.common {
    &__screen {
        position: relative;
        width: 100vw;
        height: 100vh;
        overflow: auto;
        scroll-behavior: smooth;

        &__container {
            width: 200vw;
            height: 100%;
            display: flex;
        }
    }

    &__container {
        width: 100%;
        height: 100vh;
    }
}
```

ここまでで下記のような挙動になります。
![ezgif.com-video-to-gif (3).gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3115415/dcfd67ce-b489-330f-e1a0-9f2f0a042181.gif)

ここから下記の手順でイメージのアニメーションを付けていきます。

1. マウスホイールでページが横にスライドするように変更
2. `Home`コンポーネントと`Element`コンポーネントにuseRefを設定する
3. `Intersection Observer API`を使って画面に表示されているコンポーネントがどれか判定する
4. Framer Motionでスライド時に要素が縮小するようにアニメーションをつける

### 1.マウスホイールでページが横にスライドするように変更
この部分は[こちら](https://qiita.com/nemutas/items/88e3f513bafe6149e84a)の記事を参考に作成しています。

下記がコードになります。

```javascript:App.tsx
import './style.scss'
import { Home } from "./components/Home";
import { Element } from './components/Element';
import { useEffect, useRef } from 'react'; // 追加

const App = () => {
  const screenRef = useRef<HTMLDivElement>(null);　// 追加

  // useEffectを追加
  useEffect(() => {
    screenRef.current!.onwheel = (event) => {
      event.preventDefault();
      let delta = (event.deltaY / Math.abs(event.deltaY)) * window.innerWidth;

      if (delta > 0) {
        delta += Math.ceil(screenRef.current!.scrollLeft);
        delta = Math.floor(delta / window.innerWidth) * window.innerWidth;
      } else {
        delta += Math.floor(screenRef.current!.scrollLeft);
        delta = Math.ceil(delta / window.innerWidth) * window.innerWidth;
      }
      screenRef.current!.scrollLeft = delta;
    }
  }, []);

  return (
   <>
      <div ref={screenRef} className='common__screen'> // refを追加
        <div className='common__screen__container'>
          <Home />
          <Element />
        </div>
      </div>
    </>
  )
}

export default App
```

記事と変えている部分としては、if文の最初の`screenRef.current!.scrollLeft`を`Math.ceil`や`Math.floor`で囲っている部分です。
これはブラウザの横幅によっては、`screenRef.current!.scrollLeft`でとれる値に若干の増減があり、`delta / window.innerWidth`を行ったときに値が整数でないとスクロールしないことがあったためです。

### 2.HomeコンポーネントとElementコンポーネントにuseRefを設定する
3.で`Intersection Observer API`を使って`Home`コンポーネントと`Element`コンポーネントを取得するためにuseRefを設定していきます。

```javascript:App.tsx
const App = () => {
  const screenRef = useRef<HTMLDivElement>(null);
  const homeRef = useRef<HTMLDivElement>(null); // 追加
  const elementRef = useRef<HTMLDivElement>(null); // 追加

  ~省略~

  return (
    <>
      <div ref={screenRef} className='common__screen'>
        <div className='common__screen__container'>
          <Home ref={homeRef}/>　 // refのpropsを追加
          <Element ref={elementRef}/>　 // refのpropsを追加
        </div>
      </div>
    </>
  )
}

export default App
```

次にpropsとして設定した`ref`を各コンポーネントで受け取っていきます。

```javascript:Home.tsx
import { forwardRef, useEffect } from "react"; // 追加
import { motion, useAnimationControls } from "framer-motion";

export const Home = forwardRef<HTMLDivElement>(
    ({}, ref) => { // forwardRefと{}、refを追加

    ~省略~

    return(
        <div ref={ref} className="common__container"> // refを追加
            ~省略~
        </div>
    )
})
```

```javascript:Element.tsx
import { forwardRef } from "react" // 追加

export const Element = forwardRef<HTMLDivElement>(
    ({}, ref) => { // forwardRefと{}、refを追加
        return(
            <div ref={ref} className="common__container"> // refを追加
                ~省略~
            </div>
        )
    }
)
```

### 3.Intersection Observer APIを使って画面に表示されているコンポーネントがどれか判定する
#### カスタムhookを作成
`hooks/useIntersectionObserver.ts`というファイルを作ってその中に下記のコードを記載します。
これは配列として受け取った拡縮させたい要素を監視するhookになります。

※`Intersection Observer API`の使い方はこの記事では割愛します。

```javascript:useIntersectionObserver.ts
import { RefObject, useEffect } from "react";

export const useIntersectionObserver = (
        refs: RefObject<HTMLElement>[],
        callback: (entries: IntersectionObserverEntry[]) => void,
        options?: IntersectionObserverInit
    ): void => {
    useEffect(() => {
        const observer = new IntersectionObserver(callback, options)

        refs.forEach((ref) => {
            if (ref.current) {
                observer.observe(ref.current)
            }
        })
        return () => {
            refs.forEach((ref) => {
                if (ref.current) {
                    observer.unobserve(ref.current)
                }
            })
        }
    }, [])
}
```

#### IntersectionObserver()で実行するcallback関数を作成

次にApp.tsxにcallback関数とそこで使うstateを作成します。

```javascript:App.tsx
import './style.scss'
import { Home } from "./components/Home";
import { Element } from './components/Element';
import { useEffect, useRef, useState } from 'react'; // useStateを追加

const App = () => {
  const [isIntersecting, setIsIntersecting] = useState<boolean>(true); // 追加

  // callback関数を作成
  const callback = (entries: IntersectionObserverEntry[]) => {
    entries.forEach((entry) => {
      setIsIntersecting(entry.isIntersecting);
    });
  };

  ~省略~
 
  return (
    ~省略~
  )
}

export default App
```

ここでは監視している要素が次の項目で作成する`useIntersectionObserver`の条件に一致している場合`isIntersecting`を`true`にします。
後にこの`isIntersecting`が`true`になっていると要素を拡大表示、`false`になっていると縮小表示というようにアニメーションさせていきます。

#### useIntersectionObserverをApp.tsxで呼び出す

次に`useIntersectionObserver`をApp.tsxで呼び出し、上記で作成したcallback関数を使って、拡縮したい要素が画面の98%以上表示されていたら`isIntersecting`を`true`に、そうでなければ`false`にするように設定していきます。

```javascript:App.tsx
import './style.scss'
import { Home } from "./components/Home";
import { Element } from './components/Element';
import { useEffect, useRef, useState } from 'react';
import { useIntersectionObserver } from "./hooks/useIntersectionObserver"; // 追加

const App = () => {

  ~省略~

  // useIntersectionObserverを実行
  useIntersectionObserver(
    [elementRef, homeRef],
    callback,
    { root: null, rootMargin: "0px", threshold: 0.98 }
  );
 
  return (
    ~省略~
  )
}

export default App
```

ここで注意すべきことは`useIntersectionObserver`の引数に`ref`を設定するとき、ページ表示時に一番最初に表示されるコンポーネント（ここでは`Home`コンポーネント）の`ref`は配列の一番最後に設定することです。

ページ表示時、配列の引数に設定した全てのコンポーネントが一度読み込まれ、その読み込み順も配列の先頭から最後にかけてに行なっていくため、仮に下記コードブロックのように一番最初に表示される`Home`コンポーネントを最初に持ってくると

1. 画面の98％以上を占める`Home`コンポーネントを読み込み + `isIntersecting`の初期値がtrueのため`isIntersecting`は`true`
2. 画面の98％を占めない`Elementコンポーネント`を読み込んだため`isIntersecting`を`false`に変更

という流れで最終的に`isIntersecting`の値が`false`になってしまいます。

```javascript
useIntersectionObserver(
    [homeRef, elementRef], //　ここの順番が大事
    callback,
    { root: null, rootMargin: "0px", threshold: 0.98 }
);
```

すると、特にスクロールしていないのに最初から`Home`コンポーネントが縮小された状態で表示されるようになってしまいます。

ここまでで、 `Home`コンポーネントか、`Element`コンポーネントの要素が画面の98%以上に表示されている時に`isIntersecting`が`true`になるようになりました。

### 4.Framer Motionでスライド時に要素が縮小するようにアニメーションをつける

#### コンポーネントを拡縮するコンポーネントを作成

ここでいよいよ各コンポーネントを拡縮するコンポーネントを作成します。
アニメーション自体は「テキストを順番に表示するアニメーション」でやっていることそう変わりません。

```javascript:SlideScaleChange.tsx
import { FC, ReactNode, useEffect } from 'react';
import { motion, useAnimation } from "framer-motion";

type Props = {
    children: ReactNode;
    isIntersecting: boolean;
}

export const SlideScaleChange: FC<Props> = ({ children, isIntersecting }) => {
    // アニメーションの開始と停止を制御できるuseAnimation関数を作成
    const control = useAnimation();

    // variants用のオブジェクトを作成
    const scaleChange = {
        reduction: {
            height: '80%',
            width: '80%',
            fontSize: "1.4vh",
        },
        enlargement: {
            height: '100%',
            width: '100%',
            fontSize: "2vh",
        }
    }

    // isIntersectingがfalseのとき、要素のwidthとheightを80%にして
    // font-sizeを1.4vhに変更する
    useEffect(() => {
        if(!isIntersecting) {
            control.start("reduction");
        } else {
            control.start("enlargement");
        }
    }, [isIntersecting])

    return(
        <motion.div initial='enlargement' animate={control} variants={ scaleChange }>{ children }</motion.div>
    );
}
```

#### isIntersectingを各コンポーネントに渡す

`SlideScaleChange`で`isIntersecting`を使用するために各コンポーネントに`isIntersecting`を渡していきます。

```javascript:App.tsx
const App = () => {

  ~省略　~

  return (
    <>
      <div ref={screenRef} className='common__screen'>
        <div className='common__screen__container'>
          // isIntersectingを各コンポーネントに渡す
          <Home ref={homeRef}　isIntersecting={isIntersecting}/>
          <Element ref={elementRef}　isIntersecting={isIntersecting}/>
        </div>
      </div>
    </>
  )
}

export default App
```
次に各コンポーネントで`isIntersecting`を受け取り、`SlideScaleChange`にさらに受け渡していきます。

```javascript:Home.tsx
import { forwardRef, useEffect } from "react";
import { motion, useAnimationControls } from "framer-motion";
import { SlideScaleChange } from "./SlideScaleChange"; // 追加
import { ContainerProps } from "../types/Props";　// 追加

export const Home = forwardRef<HTMLDivElement, ContainerProps>(
    ({isIntersecting}, ref) => {　// ContainerProps型とisIntersectingを追加

    ~省略~

    return(
        <div ref={ref} className="common__container">
            // SlideScaleChangeコンポーネントにisIntersectingを渡し、拡縮させたい要素をchildrenに置く
            <SlideScaleChange isIntersecting={isIntersecting}>
                <div className="home">
                    ~省略~
                </div>
            </SlideScaleChange>
        </div>
    )
})
```


```javascript:Element.tsx
import { forwardRef } from "react"
import { SlideScaleChange } from "./SlideScaleChange"; // 追加
import { ContainerProps } from "../types/Props"; // 追加

export const Element = forwardRef<HTMLDivElement, ContainerProps>(
    ({isIntersecting}, ref) => { // ContainerProps型とisIntersectingを追加
        return(
            <div ref={ref} className="common__container">
                // SlideScaleChangeコンポーネントにisIntersectingを渡し、拡縮させたい要素をchildrenに置く
                <SlideScaleChange isIntersecting={isIntersecting}>
                    <div className="element">
                        <h2>text5</h2>
                    </div>
                </SlideScaleChange>
            </div>
        )
    }
)
```

上記で追加した`ContainerProps`型は`src/types/Props.ts`というファイルを作成してその中で下記のように定義しています。

```javascript:Props.ts
export type ContainerProps = {
    isIntersecting: boolean;
}
```

最後に`Home`コンポーネントと`Element`コンポーネントが縮小した時の見た目ようにわかりやすいように`common.scss`にスタイルを追加します。

```sass:common.scss
.common {

    ~省略~

    &__container {
        width: 100%;
        height: 100vh;
        /* 以下を追加 */
        display: flex;
        justify-content: center;
        align-items: center;　
        background-color: #7d7d7d;　
    }
}
```
これでスクロール時に要素を拡大・縮小するアニメーションの作成が完了しました。
ここまでで下記のように動作します。

![ezgif.com-video-to-gif (4).gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3115415/e59a5afe-2ccc-e701-1212-049f21a33134.gif)

## 現在表示している要素に応じたヘッダーリンクの色を変えるアニメーション

ここでは下記の動画の上部にあるヘッダーのアニメーションについて紹介します。
現在表示されているコンポーネントに対応したヘッダーリンクのテキストと背景色が変わるようになります。

![ezgif.com-video-to-gif (1).gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3115415/7ad0cdb5-b8e1-f0a6-957e-16e761f05734.gif)

### ヘッダーを作成する

初めにヘッダーリンクを作成します。
これまで作成してきた`Home`コンポーネントと`Element`コンポーネントに対応したヘッダーを作成します。

まず、`Header`コンポーネントを`src/component`配下に作成します。

```javascript:Header.tsx
export const Header = () => {

    const navLists: { text: string; link: string; class: string }[] = [
        { text: 'Home', link: '#home', class: 'home' },
        { text: 'Element', link: '#element', class: 'element' }
    ]

    return(
    <header className="header">
        <nav className="header__nav">
            <ul className="header__nav__wrapper">
                {navLists.map((item, index) => {
                    return (
                        <li key={index} className="header__nav__list">
                            <a href={item.link} className="header__nav__list__link">
                                <span>{item.text}</span>
                                <span></span>
                            </a>
                        </li>
                    )
                })}
            </ul>
        </nav>
    </header>
    )
}
```

`navLists`という配列を作ってそれから`map`でヘッダーリンクを作っています。
現在は`navLists`の`class`は使用していませんが、アニメーションをつける段階で使用しますので一旦そのままにしておいてください。
また、`a`タグの中に空の`span`タグがありますが、こちらもアニメーション作成時に使用します。

cssは`src/styles/header.scss`を作成して下記のようにしております。

```sass:header.scss
.header {
    display: flex;
    position: fixed;
    z-index: 0;
    justify-content: center;
    width: 100%;
    height: 4rem;

    &__nav {
        display: flex;
        justify-content: center;
        width: 100%;

        &__wrapper {
            display: flex;
            justify-content: center;
            width: 100%;
            height: 100%;
            max-width: 1080px;
        }

        &__list {
            position: relative;
            width: 100%;
            overflow: hidden;
            &:hover {
                background-color: rgba(0, 0, 0, 0.1);
            }

            &__link {
                display: block;
                text-align: center;
                line-height: 4;
                height: 100%;
            }
        }
    }
}
```

ヘッダーリンクを押したときに対応したコンポーネントへスクロールさせるために各コンポーネントに`id`をつけます。

```javascript:Home.tsx
export const Home = forwardRef<HTMLDivElement, ContainerProps>(
    ({isIntersecting}, ref) => {

    ~省略~

    return(
        // id="home"を追加
        <div id="home" ref={ref} className="common__container">
            ~省略~
        </div>
    )
})
```

```javascript:Element.tsx
export const Element = forwardRef<HTMLDivElement, ContainerProps>(
    ({isIntersecting}, ref) => {
        return(
            // id="element"を追加
            <div id="element" ref={ref} className="common__container">
                ~省略~
            </div>
        )
    }
)
```

最後に`App.tsx`で`Header`コンポーネントを読み込みます。

```javascript:App.tsx
const App = () => {

  ~省略~

  return (
    <>
      <div ref={screenRef} className='common__screen'>
        <div className='common__screen__container'>
          <Header /> // 追加
          <Home ref={homeRef} isIntersecting={isIntersecting}/>
          <Element ref={elementRef} isIntersecting={isIntersecting}/>
        </div>
      </div>
    </>
  )
}

export default App
```

また、現在`Home`コンポーネントと`Element`コンポーネントは拡大表示時に100vhの高さになっているため、縮小した時に上の方の余白のみがヘッダー分少なく見え、違和感が出ます。

![ezgif.com-video-to-gif (5).gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3115415/a997badc-dd82-15a3-836f-85e4e1777f82.gif)

そのため`Home`コンポーネントと`Element`コンポーネントをヘッダー分下げるため下記のようにスタイルを追加します。

```sass:common.scss
.common {

    ~省略~

    &__container {
        width: 100%;
        height: calc(100vh - 4rem); // 変更
        display: flex;
        justify-content: center;
        align-items: center;
        background-color: #7d7d7d;
        margin-top: 4rem;　// 追加
    }
}
```

これでヘッダーの作成自体は完了になります。
次からアニメーションをつけていきます。

### ヘッダーにアニメーションをつける

ヘッダーにアニメーションをつけるために下記のように実装していきます。

1. 現在画面上に表示されているコンポーネント名が何かを取得する
2. 現在表示中のコンポーネント名と対応したヘッダーリンクを結びつける
3. アニメーションをつける

#### 画面に表示中のコンポーネント名取得する
画面に表示中のコンポーネント名を取得するには前項で作成した`Intersection Observer API`を使用します。
まずは現在の表示中のコンポーネント名を保存するStateを作成します。

```javascript:App.tsx
const App = () => {
  const [isIntersecting, setIsIntersecting] = useState<boolean>(true);
  const [currentTab, setCurrentTab] = useState<string>("home"); // 追加 

  ~省略~

export default App
```

次に`Intersection Observer API`のcallback関数に下記の処理を追加します。
ヘッダーを作成時に`Home`コンポーネントと`Element`コンポーネントにアンカーリンクさせるための`id`を追加しましたが、現在表示中のコンポーネント（=`isIntersecting`が`true`になっているコンポーネント）の`id`を取得することによって、何のコンポーネントが表示されているのかがわかります。

```javascript:App.tsx
const App = () => {
  const [isIntersecting, setIsIntersecting] = useState<boolean>(true);
  const [currentTab, setCurrentTab] = useState<string>("home");

  const callback = (entries: IntersectionObserverEntry[]) => {
    entries.forEach((entry) => {
      setIsIntersecting(entry.isIntersecting);
      // ↓を追加。表示されている要素のidをcurrentTabに保存する
      if (entry.isIntersecting) {
        setCurrentTab(entry.target.id);
      }
    });
  };
}

export default App
```

#### 表示中のコンポーネント名と対応したヘッダーリンクを結びつける

上記で取得したコンポーネント名と`isIntersecting`を`Header`コンポーネントに送ります。
（`isIntersecting`はここでは使用しませんがアニメーションをつける項目で使用します。）

```javascript:App.tsx
const App = () => {

  ~省略~

  return (
    <>
      <div ref={screenRef} className='common__screen'>
        <div className='common__screen__container'>
          <Header currentTab={currentTab} isIntersecting={isIntersecting}/> // 追加
          <Home ref={homeRef} isIntersecting={isIntersecting}/>
          <Element ref={elementRef} isIntersecting={isIntersecting}/>
        </div>
      </div>
    </>
  )
}

export default App
```

```javascript:Header.tsx
import { FC } from "react"; // 追加
import { HeaderProps } from '../types/Props'; // 追加

// 型とPropsを追加
export const Header: FC<HeaderProps> = (currentTab, isIntersecting ) => {

    ~省略~

}
```

`HeaderProps`の型は`src/types/Props.ts`に追加します。

```javascript:Props.ts
export type HeaderProps = {
    currentTab: string,
    isIntersecting: boolean;
}
```

ここまでできたら、Framer Motionから`motion`をインポートして、`a`タグ内の`span`を`motion.span`に変更し、スタイルをつけていきます。

```javascript:Header.tsx
export const Header: FC<HeaderProps> = ( {currentTab, isIntersecting} ) => {

    ~省略~

    return(

        ~省略~

        return (
            <li key={index} className="header__nav__list">
                <a href={item.link} className="header__nav__list__link">
                    // motion.spanに変更し、スタイルを設定
                    <motion.span
                        className="header__nav__list__link__text"
                        style={ currentTab === item.class ? { color: "#fff" } : { color: "#000" }}
                        >
                        {item.text}
                    </motion.span>
                    <motion.span
                        style={ currentTab === item.class ? { display: "inline-block", backgroundColor: "#000", width: "100%", height: "100%", y: "-100%",} : { display: "inline-block", backgroundColor: "#000", width: "100%", height: "100%", y: "100%"}}
                    ></motion.span>
                </a>
            </li>
        )
    )
}
```

ここでは現在表示中のコンポーネント名と`navLists`で設定したclassの値が一致しているかどうかで下記の挙動をとるようにしています。

* コンポーネント名とclassの値が一致していない場合
    * 最初の`span`タグのテキストの色を白に設定
    * 2番目の`span`タグの背景を黒にし、要素の高さ分位置を下にさげる（header__nav__listに設定した`overflow: hidden;`で表示は見えなくなっています）

* コンポーネント名とclassの値が一致した場合
    * 最初の`span`タグのテキストの色を黒に設定
    * 2番目の`span`タグの高さをもとの位置に戻す

すると下記のような挙動になります。

![ezgif.com-video-to-gif (6).gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3115415/4b7c9c7c-a004-7f45-d6d0-e9c632bb223d.gif)


#### アニメーションをつける

上記でつけた変化をアニメーションさせるように変更します。
まずは`variants`に設定するオブジェクトを作成していきます。

```javascript:Header.tsx
export const Header: FC<HeaderProps> = ( {currentTab, isIntersecting} ) => {

    const colorChange = {
        inactive: {
            color: '#000',
        },
        active: {
            color: '#fff',
        }
    }

    const tabChange = {
        inactive: {
            y: '100%',
            transition: {
                duration: .2,
            },
        },
        active: {
            y: '-100%',
            transition: {
                duration: .2,
            },
        }
    }

    ~省略~

    return(

        ~省略~

    )
}
```

`colorChange`は最初の`span`タグのテキストの色を変更し、`tabChange`は0.2秒かけて2番目の`span`タグを上下に移動させる記述です。

次にこれらを`motion.span`に設定し、実際にアニメーションさせます。

```javascript:Header.tsx
export const Header: FC<HeaderProps> = ( {currentTab, isIntersecting} ) => {

    const colorChange = {
        inactive: {
            color: '#000',
        },
        active: {
            color: '#fff',
        }
    }

    const tabChange = {
        inactive: {
            y: '100%',
            transition: {
                duration: .2,
            },
        },
        active: {
            y: '-100%',
            transition: {
                duration: .2,
            },
        }
    }

    ~省略~

    return(

        ~省略~

        return (
            <li key={index} className="header__nav__list">
                <a href={item.link} className="header__nav__list__link">
                    <motion.span
                        className="header__nav__list__link__text"
                        style={ currentTab === item.class ? { color: "#fff" } : { color: "#000" }}
                        animate={!isIntersecting ? "inactive" : currentTab === item.class ? "active" : "inactive"} // 追加
                        variants={colorChange} // 追加
                        >{item.text}
                    </motion.span>
                    <motion.span
                        style={ currentTab === item.class ? { display: "inline-block", backgroundColor: "#000", width: "100%", height: "100%", y: "-100%",} : { display: "inline-block", backgroundColor: "#000", width: "100%", height: "100%", y: "100%"}}
                        animate={!isIntersecting ? "inactive" : currentTab === item.class ? "active" : "inactive"} // 追加
                        variants={tabChange} // 追加
                    ></motion.span>
                </a>
            </li>
        )
}
```

最初の`motion.span`ではスクロール中、もしくは現在の表示コンポーネント名と違うヘッダーリンクの場合はテキストの色を黒にし、スクロールをしておらず、表示コンポーネント名と同じヘッダーリンクのテキストの色を白になるようにしています。
次の`motion.span`もスクロール中、もしくは現在の表示コンポーネント名と違うヘッダーリンクの場合は表示位置を要素の高さ分下げ、スクロールをしておらず、表示コンポーネント名と同じヘッダーリンクだった場合は要素をもとの位置に戻すようにしています。
これで下記のようにスクロールに合わせてヘッダーにアニメーションがつくようになりました。

![ezgif.com-video-to-gif.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3115415/f752a73d-901f-55f6-0c7f-56b0bd8e5a00.gif)

以上でFramer Motionを使ったアニメーション例は終了になります。