スクロールに連動してアニメーション

作成したコード

スクロールに連動してフェードイン表示などを実行するときのコードです。

HTML
<div class="p-animationTest js-loadAnime --fadeIn">
  <div class="p-animationTest__section">
    <strong class="js-loadAnime --fadeUp" data-before="animation">fade up</strong>
  </div>
  <div class="p-animationTest__section">
    <strong class="js-scrollAnime --fadeIn" data-before="animation">fade in</strong>
  </div>
  <div class="p-animationTest__section">
    <strong class="js-scrollAnime --zoomIn" data-before="animation">zoom in</strong>
  </div>
  <div class="p-animationTest__section">
    <strong class="js-scrollAnime --blur" data-before="animation">blur</strong>
  </div>
  <div class="p-animationTest__section">
    <strong class="js-scrollAnime --slideIn" data-before="animation">slide in</strong>
  </div>
</div>
CSS(Scss)
/*======================
animations
======================*/ 
$animation-function: ease;
$animation-time: 500ms;

// フェードイン
@keyframes fadeInAnime {
  0% {
    opacity: 0;
  }
  100% {
    opacity: 1;
  }
}
@mixin fadeIn($delay: 100ms) {
  opacity: 0;
  &.is-animated  {
    animation-name: fadeInAnime;
    animation-fill-mode: forwards;
    animation-duration: $animation-time;
    animation-iteration-count: 1;
    animation-timing-function: $animation-function;
    animation-delay: $delay;
    animation-direction: normal;
  }
}

// フェードインアップ
@keyframes fadeUpAnime {
  0% {
    opacity: 0;
    transform: translateY(100px);
  }
  100% {
    opacity: 1;
    transform: translateY(0);
  }
}
@mixin fadeUp($delay: 100ms) {
  opacity: 0;
  &.is-animated  {
    animation-name: fadeUpAnime;
    animation-fill-mode: forwards;
    animation-duration: $animation-time;
    animation-iteration-count: 1;
    animation-timing-function: $animation-function;
    animation-delay: $delay;
    animation-direction: normal;
  }
}

// ズームイン
@keyframes zoomInAnime {
  0% {
    opacity: 0;
    transform: scale(0.6);
  }
  100% {
    opacity: 1;
    transform: scale(1);
  }
}
@mixin zoomIn($delay: 100ms) {
  opacity: 0;
  &.is-animated  {
    animation-name: zoomInAnime;
    animation-fill-mode: forwards;
    animation-duration: $animation-time;
    animation-iteration-count: 1;
    animation-timing-function: $animation-function;
    animation-delay: $delay;
    animation-direction: normal;
  }
}

// ブラー
@keyframes blurAnime {
  0% {
    filter: blur(10px);
    opacity: 0;
  }
  100% {
    filter: blur(0);
    opacity: 1;
  }
}
@mixin blur($delay: 100ms) {
  opacity: 0;
  &.is-animated  {
    animation-name: blurAnime;
    animation-fill-mode: forwards;
    animation-duration: $animation-time;
    animation-iteration-count: 1;
    animation-timing-function: $animation-function;
    animation-delay: $delay;
    animation-direction: normal;
  }
}

// スライドイン
@keyframes slideInAnime {
  0% {
    opacity: 0;
    transform: translateX(-100px);
  }
  100% {
    opacity: 1;
    transform: translateX(0);
  }
}
@mixin slideIn($delay: 100ms) {
  opacity: 0;
  &.is-animated  {
    animation-name: slideInAnime;
    animation-fill-mode: forwards;
    animation-duration: $animation-time;
    animation-iteration-count: 1;
    animation-timing-function: $animation-function;
    animation-delay: $delay;
    animation-direction: normal;
  }
}


/*======================
animations(JSクラス付与)
======================*/ 
// ロードしたらアニメーション
.js-loadAnime {
  &.--fadeIn {
    @include fadeIn(0);
  }
  &.--fadeUp {
    @include fadeUp(0);
  }
  &.--zoomIn {
    @include zoomIn(0);
  }
  &.--blur {
    @include blur(0);
  }
  &.--slideIn {
    @include slideIn(0);
  }
}

// 画面に表示されたらアニメーション
.js-scrollAnime {
  &.--fadeIn {
    @include fadeIn(0);
  }
  &.--fadeUp {
    @include fadeUp(0);
  }
  &.--zoomIn {
    @include zoomIn(0);
  }
  &.--blur {
    @include blur(0);
  }
  &.--slideIn {
    @include slideIn(0);
  }
}


/*======================
contents
======================*/ 
.p-animationTest {
  &__section {
    display: flex;
    justify-content: center;
    align-items: center;
    width: 100%;
    height: max(568px, 100dvh);
    padding: 1.5em;
    background: lightblue;
    &:nth-child(odd) {
      background: skyblue;
    }
    &:not(:last-child) {
      border-bottom: 1px solid #FFF;
    }
    strong {
      font-family: 'Arial Black', sans-serif;
      font-size: 80px;
      text-transform: capitalize;
      mix-blend-mode: overlay;
      &::before {
        content: attr(data-before);
        display: block;
        width: fit-content;
        margin: 0 auto 0.2em;
        font-size: 0.2em;
        letter-spacing: 0.1em;
        color: #555;
      }
    }
    &:first-child strong.js-loadAnime.--fadeUp.is-animated {
      animation-delay: $animation-time + 100ms;
    }
  }
}
JavaScript
// const
const viewHeight = window.innerHeight;

// ロードしたらアニメーション
var loadAnimeTargets = document.querySelectorAll('.js-loadAnime');
window.addEventListener('load', function () {
  for (let target of loadAnimeTargets) {
    target.classList.add('is-animated');
  }
});

// 画面に表示されたらアニメーション
var scrollAnimeTargets = document.querySelectorAll('.js-scrollAnime');
window.addEventListener('scroll', function () {
  var scroll = window.scrollY;
  for (let target of scrollAnimeTargets) {
    var targetPos = target.getBoundingClientRect().top + scroll + 200;
    if (scroll > targetPos - viewHeight) {
      target.classList.add('is-animated');
    }
  }
});

以下のページで動作確認できます。

コードの解説

HTMLでの記述

ロード完了時にアニメーションさせたい要素にクラスを付与します。

ロードしたらアニメーションをする場合のクラス
js-loadAnime
画面に表示されたらアニメーションをする場合のクラス
js-scrollAnime

アニメーションの種類は–fadeUpなどのモディファイアクラスを付与して指定します。

今回作成したアニメーションの種類は以下の5種類です。

  • フェードイン(–fadeIn)
  • フェードインアップ(–fadeInUp)
  • ズームイン(–zoomIn)
  • ブラー(–blur)
  • スライドイン(–slideIn)

CSSでの記述

各アニメーションの記述は@keyframesと@mixinで管理しています。

アニメーションの周期や時間を変更したい場合は以下の変数を編集。

$animation-function: ease; // 周期
$animation-time: 500ms; // 時間

アニメーションのタイミングについては、指定したい要素ごとに個別で指定していくようにしています。

    &:first-child strong.js-loadAnime.--fadeUp.is-animated {
      animation-delay: $animation-time + 100ms;
    }

JavaScriptでの記述

ロードもしくはスクロールで表示された際に、HTMLで予め指定したクラスにis-animatedクラスを付与することで、CSSで指定したアニメーションが動作する仕組みです。

個別に特別なアニメーションを付与したい場合は、該当箇所の固有のクラス名に大して別途アニメーションを指定することも可能です。

スクロール時のアニメーションが発動する位置からのオフセットを変更したい場合

以下の記述の「200」がアニメーションが発動する位置からのオフセットになりますので、こちらでis-animatedクラスが付与される位置を調整してください。

var targetPos = target.getBoundingClientRect().top + scroll + 200;

解説付きのJSコード

// ウィンドウの高さを取得
const viewHeight = window.innerHeight;

// ページの読み込みが完了したら、loadAnimeTargetsに対してis-animatedクラスを追加します
var loadAnimeTargets = document.querySelectorAll('.js-loadAnime');
window.addEventListener('load', function () {
  for (let target of loadAnimeTargets) {
    target.classList.add('is-animated');
  }
});

// スクロール位置が、scrollAnimeTargetsが表示される位置を超えた場合にis-animatedクラスを追加します
var scrollAnimeTargets = document.querySelectorAll('.js-scrollAnime');

window.addEventListener('scroll', function () { // ページがスクロールされるたびに実行する

  var scroll = window.scrollY; // 現在のスクロール位置

  for (let target of scrollAnimeTargets) {

    // アニメーションの対象要素の表示位置を計算
    /*
     getBoundingClientRect():対象要素の位置やサイズ情報を取得
     topプロパティ:要素の上端からの距離
     scroll:現在のスクロール位置
     200:アニメーションが発動する位置からのオフセット
    */
    var targetPos = target.getBoundingClientRect().top + scroll + 200;

    // スクロール位置がアニメーションの対象要素が表示される位置を超えたか判定
    if (scroll > targetPos - viewHeight) {
      target.classList.add('is-animated');
    }
  }
});