バニラJavaScriptのスクロール方向を検出する方法


私は間抜けな考えを試してみたかった.私はあなたがスクロールするとき、それがスクロールするとき、それが落ちるとき、ウェブサイトのロゴをアニメーション化したかったです、そして、あなた自身がスクロールするとき、それ自体.特に、私はロゴが頭であることを望みました、あるいは、漫画的な観察をしてください.これは私の最初の試みです.
あなたはどう思いますか.
ユーザーがスクロールしている方向を検出するために、より実用的なケースがあります.それらを見てみましょう、そして我々はいくつかのコードを歩くことができます.

ユースケース


スクロール方向を検出するための一般的なユースケースは、下にスクロールするときにナビゲーションオプションを非表示にするが、ユーザーがスクロールするときに表示されます.これはユーザがどこかほかの場所へ行きたがっていることを示す.JavaScriptでマインドを読むAPIがないので、これは最高の推測です!😉
私は、一部の人々が「スマートなnavバー」としてこの行動パターンに続く主要なナビゲーションを参照するのを見ました.私が見た主な苦情は、「スマートNav」のピーク・ブーの性質は、あなたがモバイル・デバイスにいるとき、あなたが読んでいるいくつかのテキストをカバーすることができるということです.
私のテイクは、代わりにスマート“戻る戻る”ボタンを表示する方が良いです.このボタンは、メインナビゲーションにアクセスできるページの上部に表示されます.私は次のポストでこれをカバーします.以下が例です.
時々それはテクニックを取るし、それを適用する異常な場所を探すのは楽しいことができます.たぶん、これはあなたのアイデアをスパークされます!
私のケースでは、それはこれを探索するためのパスを私に設定するgoofyアイデアだった.
コードを読みましょう.

暗号


主な成分はWindow ページを表示するブラウザウィンドウとスクロールバーのイベントリスナーについて、スクロールバーの変更に対応できるようにするインターフェイスですscroll events ).
我々は、プロパティを相談することができます window.pageYOffset ピクセル数を知るには、ページが現在垂直方向にスクロールされます.これは window.scrollY . があるslightly better browser support for pageYOffset scrollY (Internet Explorer 9 +)しかし、あなたが遠くにブラウザを心配していない場合は、いずれかを使用することができます.
前のスクロール位置の値が現在のスクロール位置の値より小さい場合、ユーザーはスクロールダウンします.関数を作成して呼び出しますisScrollingDown . ブール値を返します.
let previousScrollPosition = 0;

const isScrollingDown = () => {
  let goingDown = false;

  let scrollPosition = window.pageYOffset;

  if (scrollPosition > previousScrollPosition) {
    goingDown = true;
  }

  previousScrollPosition = scrollPosition;

  return goingDown;
};
現在、我々はスクロール方向が何であるかについて話すことができます.これに反応しましょう!
我々のトップ見出しのスタイルを変える方法h1 ) スクロール方向を反映するには?
我々は、それは常に画面の上部に表示されますので、粘着性の見出しを行います.JavaScriptコードでは、スクロール方向に基づいてスタイルを変更するクラスを追加します.
我々がスクロールするとき、我々はスクリーンショットの下で以下の通り、赤い背景で見出しテキストに「UP」を追加します.

私たちが下にスクロールすると、我々は、スクリーンショットの下に次のように緑色の背景と見出しテキストに“アップ”を追加します.

私たちはスタイルを上下することができます ::after psuedo element :
h1 {
  position: sticky;
  top: 0;
  text-align: center;
  background-color: white;
}

.scroll-down::after {
  content: " down";
  background-color: green;
}

.scroll-up::after {
  content: " up";
  background-color: red;
}
ハンドラ関数を作成しますhandleScroll それは“スクロールダウン”または“スクロールアップ”クラスを見出しに我々が行っている方向と一致するように追加されます.
let mainHeading = document.querySelector("h1");

const handleScroll = () => {
  if (isScrollingDown()) {
    mainHeading.classList.add("scroll-down");
    mainHeading.classList.remove("scroll-up");
  } else {
    mainHeading.classList.add("scroll-up");
    mainHeading.classList.remove("scroll-down");
  }
};

window.addEventListener("scroll", handleScroll);
試してみてください!
ただし、スクロールイベントリスナーで作業するたびに、パフォーマンスは何らかの考慮を必要とするものです.

性能考察


たった今、ユーザーが数ピクセルでページをスクロールするたびに、我々はハンドラ機能を呼びます.これは、ページをすばやくスクロールする場合、ハンドラ関数が毎秒何度も呼び出されることを意味します.これは不要です、我々はそのようなパーシエーションを必要としません.
我々は、コールの数を制限するためにスロットル機能を使用することができますすなわち、1回のx時間あたりのアクションを行う.関連する概念は、議論され、時には人々が混乱を得る.This article explains both conceptsan interactive ball machine to demonstrate throttling .

礼儀Debounce Vs Throttle: A Definitive Guide
The throttle 関数は少なくとも2つの引数を受け入れるべきである:func , これはスロットルする関数であり、wait , これは、次の実行まで待つ時間(ミリ秒単位)です.この関数はthrottled関数を返す.
つまり、私たちはスクロール機能を制限して、毎秒10倍の最大値とします.我々は100ミリ秒を提供することができますwait . また、受け入れる実装もありますleading and trailing 最初の(先頭)関数と最後の(末尾)関数コールを制御するパラメータ.この場合、直ちに応答するように、最初の関数呼び出しを実行します.
あなたがつかむことを望むならばthrottle 他の場所からの関数では、次の場所に1つを見つけることができます.

  • Lodash and underscore 人気のあるユーティリティライブラリです.
  • あなたがちょうどコピーして、コードの1つのブロックをペーストしたいならば、私が見つけた最も完全で信頼できるバージョンはunderscore 図書館.私は、それを含めましたappendix section .

  • This GitHub Gist いくつかの異なるバージョン現代のJSを使用しています.いくつかの議論がここにあります、何人かの人々は、スロットル機能が正確に何であるかのわずかに異なったinterpetationsを持ちます!だから、注意してください.
  • 私はlodashライブラリをthrottle 関数.チェックできますthe function description in the docs 使い方を見る.インタフェースです.
    _.throttle(func, [wait=0], [options={}])
    
    最初の2つのパラメタラーを渡すことにより、throttled関数を作成します.
  • 私たちのスクロールハンドラ関数は、func ),
  • と100ミリ秒の待ち時間wait ).
  • デフォルトでは、これは関数が実行される最初の時間を実行しますleading オプションが真である).
    Slottled関数をスクロールイベントリスナーに渡します.
    const scrollThrottle = _.throttle(handleScroll, 100);
    window.addEventListener("scroll", scrollThrottle);
    
    そして、ここでは修正されたバージョンのthrottling :
    あなたは、ユーザーエクスペリエンスを見ることができますが、舞台裏でパフォーマンスが良いです.あなたは別の値を再生することができますwait あなたのユースケースに最適な結果が何かを確認するには.

    代わりに交差オブザーバーを使用できますか?


    あなたが家族でないならばIntersection Observer API , ターゲット要素が見えるかどうかを検出する方法です.要素がビューポート(または別の指定された要素)と交差するかどうかを調べます.我々のユースケースでは、スクロール方向について知るために、何かの目的のためにそれを使用しています.
    質問に対する短い答えは、ページレベルのスクロール(簡単な方法で)の交差点オブザーバーAPIを使用できないことです.スクロールが行われているかどうかを知ることができる方法は、要素が表示されて表示されないときに、要素のどれだけの要素が見えるかによって引き起こされるコールバックを通してフィードバックを得ることですthreshold オプション.以来body は、ルート要素に対して常に目に見えるようになり、可視化された量はほんの少し変化するだけで、単一のコールバックしか取得できない.
    私は交差点オブザーバーで経験したので、多分、この行動を働かせるために賢いハックがあります.何かあるならば、知らせてください.
    あなたがターゲットの要素のスクロール方向を取得することができますが表示されます.This stackoverflow question カバー.

    付録


    これがスロットル機能ですthe ESM (Development) version of underscore ちょうど1つの変化で、それは至る所で走りますnow() with Date.now() ):
    /* Source: https://underscorejs.org/underscore-esm.js
    
    During a given window of time. Normally, the throttled function will run
    as much as it can, without ever going more than once per `wait` duration;
    but if you'd like to disable the execution on the leading edge, pass
    `{leading: false}`. To disable execution on the trailing edge, ditto.
    */
    function throttle(func, wait, options) {
      var timeout, context, args, result;
      var previous = 0;
      if (!options) options = {};
    
      var later = function () {
        previous = options.leading === false ? 0 : Date.now();
        timeout = null;
        result = func.apply(context, args);
        if (!timeout) context = args = null;
      };
    
      var throttled = function () {
        var _now = Date.now();
        if (!previous && options.leading === false) previous = _now;
        var remaining = wait - (_now - previous);
        context = this;
        args = arguments;
        if (remaining <= 0 || remaining > wait) {
          if (timeout) {
            clearTimeout(timeout);
            timeout = null;
          }
          previous = _now;
          result = func.apply(context, args);
          if (!timeout) context = args = null;
        } else if (!timeout && options.trailing !== false) {
          timeout = setTimeout(later, remaining);
        }
        return result;
      };
    
      throttled.cancel = function () {
        clearTimeout(timeout);
        previous = 0;
        timeout = context = args = null;
      };
    
      return throttled;
    }