📒

『note』の記事にあるような、動くナビゲーションバーを作成

2024/04/23に公開

このように、スクロールに応じて動く。

スクロールダウンすると姿を隠し、かつ、少しのスクロールアップでは再表示されず、それでいて一気にスクロールアップすると再表示してくれる、絶妙な塩梅。

気になるので作ってみました。
(React、Tailwind CSS)

目標

Navbar.tsx
${showNav ? "top-16" : "top-[66px]"}

propsで渡したshowNavをスイッチさせる。(2pxのボーダーボトム含めてるので、マジックナンバー(数値ベタ打ち)登場しています。)

該当するコード部分

Menu.tsx
const [showNav, setShowNav] = useState(true);
const lastScrollY = useRef(0); // useRefを使用してlastScrollYを初期化

useEffect(() => {
    lastScrollY.current = window.scrollY; // コンポーネントがマウントされた後にwindow.scrollYを設定

    const handleScroll = () => {
        const currentScrollY = window.scrollY;
      // スクロールの方向を判定
        const isScrollingUp = currentScrollY < lastScrollY.current;
        const isScrollingDown = currentScrollY > lastScrollY.current;

      // トップの時はナビゲーションバーを表示
      if (currentScrollY === 0) {
        setShowNav(true);
      }

      // スクロールダウンでナビゲーションバーを非表示
      else if (isScrollingDown) {
        setShowNav(false);
      }

      // スクロールアップでかつ一定の距離を超えた場合にナビゲーションバーを表示
      else if (isScrollingUp && lastScrollY.current - currentScrollY > 100) {
        setShowNav(true);
      }

      lastScrollY.current = currentScrollY;
    };

     const throttledHandleScroll = throttle(handleScroll, 100);

     window.addEventListener("scroll", throttledHandleScroll);

    return () => {
      window.removeEventListener("scroll", throttledHandleScroll);
      throttledHandleScroll.cancel(); // クリーンアップ時にthrottleを解除
    };
  }, []);

コードの流れを確認

実装したいと思い立ち、早速Chat-GPT4(CV:津田健次郎)に相談。

「note.comの記事のようなナビゲーションバーが作りたいです」

「こんな感じでどうでしょうか」

示されたコードを見てみると、何やらcurrentScrollYlastScrollYの変数が宣言されている。

それらを識別して、スクロールの差分を算出しようとしている模様。

「なるほど。やはりそうなりますよね。ちょうど自分もそうしようと考えていたところです(嘘)」

追加で質問しつつ、最初に出てきたコードは不要な変数宣言などもあったため、その都度修正しました。

hooksの使用

const [showNav, setShowNav] = useState(true);
const lastScrollY = useRef(0); // useRefを使用してlastScrollYを初期化

showNavをuseStateで管理。
スクロールの値が欲しいので、useRef使用。

useEffectの中身

useEffect(() => {
 lastScrollY.current = window.scrollY; // コンポーネントがマウントされた後にwindow.scrollYを設定

 const handleScroll = () => {
 const currentScrollY = window.scrollY;
 // スクロールの方向を判定
 const isScrollingUp = currentScrollY < lastScrollY.current;
 const isScrollingDown = currentScrollY > lastScrollY.current;

最初にchatGPTに提示されたコードではwindowが外で宣言されていたため、effect内に収めました。

isScrollingUpisScrollingDownでスクロール方向を検知する。

!isScrollingUpでもいいのか? ん?どうなるんだっけ? ===とかどうなる…?)

集合論に自信がなかったので、愚直にisScrollingDownの宣言も追加。

条件分岐

     // トップの時はナビゲーションバーを表示
      if (currentScrollY === 0) {
        setShowNav(true);
      }

      // スクロールダウンでナビゲーションバーを非表示
      else if (isScrollingDown) {
        setShowNav(false);
      }

      // スクロールアップでかつ一定の距離を超えた場合にナビゲーションバーを表示
      else if (isScrollingUp && lastScrollY.current - currentScrollY > 100) {
        setShowNav(true);
      }

    // lastScrollY.current を更新
      lastScrollY.current = currentScrollY;

ナビゲーションバーの表示・非表示の条件を追加していく。

ページがボトムのときなども、レイアウトシフト対策などをしたほうがよさそうな場合もありそうですがどうなんでしょうか。

lastScrollY.current - currentScrollY > 100が再表示の肝のようです。noteの実際の差分は分かりませんが、100にしています(お好みで数字を増やしたり減らしたりで感度が変わります。数値のバランスによっては痙攣するような表示具合になりました)

throttleでイベントの数を間引く

参考記事
https://www.gaji.jp/blog/2024/02/13/18388/

高頻度のスクロールイベントをそのままにするのはアンチパターンなようなので、適宜throttoleかdebounceを入れて、イベントの数を減らす必要がある。

debounceはイベントを遅延、throttleは一定時間内のイベントを間引く。

throttleを採用。(老舗のモジュールって感じですが、最終アップデートがずっと前なのが少し気になります……現行でメンテナンスさているものってあるのでしょうか。自作? 詳しい方がいらしたら是非おすすめを教えてください。)

lodashをまるごと取り込むとバンドルサイズが大きくなるらしいので、throttleのみをインストール。

npm install lodash.throttle

TypeScriptの場合throttleの型が必要なので

npm i @types/lodash.throttle

handleScrollにthrottleを適用させる。

const throttledHandleScroll = throttle(handleScroll, 100);

window.addEventListener("scroll", throttledHandleScroll);

 return () => {
  window.removeEventListener("scroll", throttledHandleScroll);
  throttledHandleScroll.cancel(); // クリーンアップ時にthrottleを解除
 };
}, []);

chatGPTがthrottledHandleScroll.cancel()の部分をやけに推すので、そのまま採用。
throttleが採用されているコードを調べみても、あったりなかったりでした。あったほうがいいんですかね。詳しい方がいたら是非教えてください。

propsを渡す

ナビゲーションバーのコンポーネントにshowNavを渡したら完成です。

振り返り

useEffectの記述も長いし、なんらかのアンチパターンを踏んでいるかもしれませんが、一応動きます。詳しい方がいらしたら是非アドバイスください。

自力でスクロールアップ、スクロールダウンに応じて表示をコントロールするところまではできましたが、chatGPTに相談することで、もう一歩踏み込んでロジックを理解することができました。

示されたコードを噛み砕きながら読んでいくのもまた面白いですね。

以上です。ありがとうございました。

Discussion