Intersection Observerでスクロールアニメーションをやってみた

動きのあるサイトは見てて楽しくていいですね。
スマホの縦スクロールを生かしたサイトなど憧れます。
Qiitaの横にある目次もそうですが、スクロールに連動して変化するものはよく見るのでやってみたいなと思いました。

# 作ったもの
文字や画像がフェードインするやつをやってみました。

今回作ったのは[こちら](https://scroll-animation-test.web.app/)

下にスクロールするとエレベーターが下がります。
1階下がる毎に1人乗って、1階に着いたら全員降りるというのを表現してみました。
説明しないとよくわかんなくなりました。

参考にさせていただいたのは[こちら](https://coliss.com/articles/build-websites/operation/javascript/animate-on-scroll-using-intersectionobserver.html)

# Intersection Observerを使いました
従来はJavaScriptの scroll というイベントを利用することが多かったようです。
しかし画面サイズが変わったら再計算しないといけず、スクロールやリサイズのたびに呼び出されるため、パフォーマンスへの悪影響が懸念されるとのこと。
Intersection Observer は要素と要素の交差を検知するAPIだそうで、これを利用すればパフォーマンスの問題が解消でき、SEO対策としてgoogleでも推奨されているそうです。

# HTML
```HAML:
<div class="wrapper">
        <div class="card">
            <div class="image animate" data-animate="animImage 2s"><span class="text animate" data-animate="animFloorNumber 2s ">4</span></div>
            <p class="animate" data-animate="animOut 2s "><img class="human" src="image/figure_stand_side.png"></p>
        </div>
    </div>
    <div class="wrapper">
        <div class="card">
            <div class="image animate" data-animate="animImage 2s"><span class="text animate" data-animate="animFloorNumber 2s ">3</span></div>
            <p class="animate" data-animate="animOut 2s "><img class="human" src="image/figure_stand_side.png"></p>
        </div>
    </div>
    <div class="wrapper">
        <div class="card">
            <div class="image animate" data-animate="animImage 2s"><span class="text animate" data-animate="animFloorNumber 2s ">2</span></div>
            <p class="animate" data-animate="animOut 2s "><img class="human" src="image/figure_stand_side.png"></p>
        </div>
    </div>
    <div class="wrapper">
        <div class="card">
            <div class="image animate" data-animate="animImage 2s"><span class="text animate" data-animate="animFloorNumber 2s ">1</span></div>
            <p class="animate" data-animate="animIn 2s ">
              <img class="human_2" src="image/figure_stand_side_3.png">
            </p>
        </div>
    </div>
```

# CSS
```css:
.wrapper {
    min-height: 100vh;
    display: flex;
    align-items: center;
    justify-content: space-around;
    background-color: #5dafb8;
    color:white;
    font-family: Verdana, Geneva, Tahoma, sans-serif;
}

.card {
    height: 50vh;
    border-radius: 13px;
    box-shadow: 20px 40px 33px rgba(0,0,0,0.3);
    padding: 2rem;
    width: 35vh;
    background-color:  #6cc2ce;

}

.image {
    width: 35vh;
    height: 20vh;
    background-image: linear-gradient(70deg, RoyalBlue   , DarkTurquoise );
    background-size: cover;
    background-position: center center;
    box-shadow:  10px 15px 15px 6px #3891b4;
    border-radius: 15px;

}

.text {
	font-size:	50px;
	color:		#fff;
	position:	absolute;
  margin: 40px 110px;
}

.human {
  width: 50%;
  margin-left: 100%;
}

.human_2 {
  width: 50%;
  margin-left: 120%;
  transform: scale(-1, 1);
}

.card h2 {
    animation: animOut 2s infinite;
}

.card p {
    animation: animIn 2s infinite;
}

.card .image {
    animation: animImage 2s infinite;
}

@keyframes animImage {
    0% {
      -webkit-transform: scale3d(1, 1, 1);
              transform: scale3d(1, 1, 1);
    }
    30% {
      -webkit-transform: scale3d(1.25, 0.75, 1);
              transform: scale3d(1.25, 0.75, 1);
    }
    40% {
      -webkit-transform: scale3d(0.75, 1.25, 1);
              transform: scale3d(0.75, 1.25, 1);
    }
    50% {
      -webkit-transform: scale3d(1.15, 0.85, 1);
              transform: scale3d(1.15, 0.85, 1);
    }
    65% {
      -webkit-transform: scale3d(0.95, 1.05, 1);
              transform: scale3d(0.95, 1.05, 1);
    }
    75% {
      -webkit-transform: scale3d(1.05, 0.95, 1);
              transform: scale3d(1.05, 0.95, 1);
    }
    100% {
      -webkit-transform: scale3d(1, 1, 1);
              transform: scale3d(1, 1, 1);
    }
  }

@keyframes animIn {
    from {
            transform: translateX(-50px);
            opacity: 0;
    }
    to  {
            transform: translateX(0);
            opacity: 1;
   }
 }

 @keyframes animOut {
     from {
             transform: translateX(50px);
             opacity: 1;
     }
     to  {
             transform: translateX(0);
             opacity: 0;
    }
  }

  @keyframes animFloorNumber {
      from {
              opacity: 0;
      }
      to  {
              opacity: 1;
     }
   }
```

# JavaScript
```javascript:
const callback = (entries) => {
  entries.forEach(
    (entry) => {
      if (entry.isIntersecting) {
        console.log("The element is intersecting >");
        entry.target.style.animation =
        entry.target.dataset.animate;
      } else {
        entry.target.style.animation="none";
      }
    }
  );
}

let observer = new IntersectionObserver(callback);

const animationItems = document.querySelectorAll('.animate');

animationItems.forEach(item => {
  observer.observe(item)
})
```

# まとめ
いろいろ応用して面白いことができそうだなと思いました。
コンセプトをしっかり決めてサイトを作るのを来年の目標にしたいです。