フェードインが完了したらスクロールの監視を終了する方法を教えていただけないでしょうか。

投稿者: Anonymous

前提・実現したいこと

コーディング歴は半年で、js はプラグインに頼るばかりであまり経験がありません。
よろしくお願いいたします。

下記の一連の動作を js または jQuery で作る方法を教えていただきたいです。

  1. スクロールで画像がフェードイン
  2. フェードインした画像のみスクロールの監視が終了
  3. その他の画像はフェードインするまでスクロールの監視が継続
  4. 最終的に、すべての画像のフェードインが完了すると
    スクロールによる監視は完全に終了

発生している問題

上記2番目の「フェードインした画像のみスクロールの監視が終了」
という部分の実装ができません。

該当のソースコード

【html】

<p><img class="fade" src="" alt=""></p>
<p><img class="fade" src="" alt=""></p>
<p><img class="fade" src="" alt=""></p>
<p><img id=“js-stop” class="fade" src="" alt=""></p> <!-- これがフェードインすると監視終了 -->

【css】

.fade {
  opacity : 0;
  transition: opacity 2s;
}

.fade-in {
  opacity: 1;
}

【JavaScript】

  //  スクロール量を検知し、フェードインする
  function animation() {
    $('.fade').each(function () {
      var target = $(this).offset().top;
      var scroll = $(window).scrollTop() - 200;
      var windowHeight = $(window).height();
      if (scroll > target - windowHeight) {
        $(this).addClass('fade-in');
      }
    });
  }

  //  #js-stop(4つ目の画像)がフェードインすると監視終了
  animation();
  var stop = document.getElementById('js-stop');
  $(window).scroll(function () {
    if (stop.classList.contains('fade-in') == true) {
      return false;
    } else {
      animation();
    }
  });

試したこと

「.fade-in が付与されたら、その要素だけは監視を終了する」
という解釈になると思うのですが、具体的な方法に検討がつきません。

考えた挙句に上記の「#js-stop(最後の画像)に .fade-in が付与されたら終了する」という処理になりました。

以上が質問です。
よろしくお願いいたします。

解決

一連の動作の説明は実装するのに必要十分です。この説明のとおりに実際にコーディングに落とし込むには少しだけテクニックが必要です。
知っておくと便利な方法として、JavaScriptにおいて「HTMLの要素の参照使い回す」ことです。

どういうことかというと、document.getElementByIddocument.querySelectorなどで取得できるHTMLの要素の参照を変数上で保持し続け、これに対して値の取得や変更を行うことでJavaScript中のHTMLの表現方法が柔軟になります(※)。

実際に書いて動いたものを以下に載せます。

// 一度だけ`.fade`を格納する
const fadeElements = $(".fade");
// すでに`.fade-in`が追加された要素の参照を格納する配列
const fadeInEndElements = [];
// `.fade`の個数
const fadeInCount = fadeElements.length;

function animation() {
  // すべての`.fade`要素に対して`.fade-in`のclassNameが追加された場合
  if (fadeInEndElements.length === fadeInCount) {
    // すべての要素に対して`.fade-in`が追加されたので、登録していたイベントを解除する
    $(window).off("scroll", animation);
    // すべて完了している場合はこれ以上の処理を実行しない
    return;
  }
  fadeElements.each(function() {
    // `.fade-in`が付いていない要素かどうか確認する
    if (fadeInEndElements.includes(this)) {
      // すでに`.fade-in`が付いている場合はこれ以上の処理をしない // ここが発生している問題の解決ポイントです
      return;
    }
    const target = $(this).offset().top;
    const scroll = $(window).scrollTop() - 200;
    const windowHeight = $(window).height();
    if (scroll > target - windowHeight) {
      // .fade-in を追加
      $(this).addClass("fade-in");
      // .fade-in が追加された要素の参照(this)を格納する
      fadeInEndElements.push(this);
    }
  });
}

// スクロールイベントに対して、animationのイベント関数を登録する
$(window).scroll(animation);

fadeInEndElements.push(this);と書いている部分が今回の肝となる部分なのでじっくりと確認してみてください。

※ ただし、この方法にはHTML要素が他者から変更された場合に参照がなくなるという問題がありますが、おそらくこの様な場面に遭遇するときには、実装力をより上げなければならない状況だと思います。今回の質問の範囲ではおそらく無いと考えられますので、無視していただいて結構です。

回答者: Anonymous

Leave a Reply

Your email address will not be published. Required fields are marked *