戴兜

戴兜的小屋

Coding the world.
github
bilibili
twitter

スクロール時にヘッダーを自動的に隠す

image

これは掘金のウェブ版のヘッダーで、スクロールバーが下に滑ると、メインヘッダーが隠れ、サブヘッダーがページの上部に吸着します。

image

スタイル#

まず、2 つの div を使ってこの 2 つのヘッダーを模擬します。

<template>
    <div class="main-header-wrapper">
      <div class="main-header"></div>
    </div>
  <div class="sub-header-wrapper">
    <div class="sub-header"></div>
  </div>
  <div v-for="i of 100" :key="i">スクロールテスト</div>
</template>

<style scoped>
  .main-header-wrapper {
    height: 50px;
  }
  
  .main-header {
    height: 50px;
    width: 100%;
    background: #ff0000;
    position: fixed;
    top: 0;
  }
  
  .sub-header-wrapper {
    height: 40px;
  }
  
  .sub-header {
    height: 40px;
    width: 100%;
    background: #00ff00;
    position: fixed;
    top: 50px;
  }
</style>

image

ここでは絶対位置を使用して 2 つのヘッダーをページの上部に固定しています。この 2 つの div はそれぞれmain-headersub-headerです。彼らはそれぞれコンテナmain-header-wrappersub-header-wrapperで包まれており、ヘッダーが文書フローから外れることによって本文要素を隠すのを防ぐためです。

簡略化#

スクロール方向の検出を一旦脇に置き、まずはこのような効果を実現しましょう:2 つのヘッダーにクラス名hiddenを追加すると、メインヘッダーが隠れ、サブヘッダーが吸着します。ここでは transform を使って実現できます。

効果を示すために、hiddenクラス名を追加および削除するためのボタンを追加しました。

    <script setup>
      import { ref } from 'vue';
      
      const isHidden = ref(false);
    </script>
    
    <template>
        <div class="main-header-wrapper">
          <div class="main-header" :class="{'hidden':isHidden}"></div>
        </div>
      <div class="sub-header-wrapper">
        <div class="sub-header" :class="{'hidden':isHidden}"></div>
      </div>
      <div v-for="i of 100" :key="i">スクロールテスト</div>
      <button style="position:fixed;bottom: 10px; right: 10px;" @click="isHidden = !isHidden">
        切り替え
      </button>
    </template>
    
    <style>
      html, body {
        margin: 0;
        padding: 0;
      }
    </style>
    
    <style scoped>
      .main-header-wrapper {
        height: 50px;
      }
      
      .main-header {
        height: 50px;
        width: 100%;
        background: #ff0000;
        position: fixed;
        top: 0;
      }
      
      .sub-header-wrapper {
        height: 40px;
      }
      
      .sub-header {
        height: 40px;
        width: 100%;
        background: #00ff00;
        position: fixed;
        top: 50px;
      }
      
      .main-header, .sub-header {
        transition: transform .15s ease-in-out;
        transform: translateY(0px);
      }
      
      .main-header.hidden, .sub-header.hidden {
        transform: translateY(-50px);
      }
    </style>

サンプルを見る

選択子.main-header.hidden.sub-header.hiddenを使用して、hidden クラス名を持つヘッダー要素を選択し、transform プロパティをtranslateY(-50px)(つまり上に 50px オフセット)に設定します。同時に要素に transition を設定し、イージング効果を提供します。

これで、ヘッダーの表示と非表示が実現されました。次はスクロール方向の検出です。

実装#

vueUse のuseScrolluseDebounceFnを使用して、一部のコードを簡略化しました。

    <script setup>
      import { ref, watch } from 'vue';
      import { useScroll, useDebounceFn } from '@vueuse/core'
      
      const isHidden = ref(false);
      
      const { directions, isScrolling, arrivedState } = useScroll(document)
      
      const checkHeaderStatus = useDebounceFn(
        (top, bottom, topArrived) => {
          if (topArrived) {
            isHidden.value = false
            return
          }
          if (top) {
            isHidden.value = false
          } else if (bottom) {
            isHidden.value = true
          }
        },
        100
      )
      watch(directions, () => {
        if (isScrolling.value) {
          checkHeaderStatus(directions.top, directions.bottom, arrivedState.top)
        }
      })
    </script>

useScroll は、反応的なスクロール方向、スクロール状態(上部 / 下部に到達)およびスクロール中かどうかを出力できます。

関数が返す directions は反応的なオブジェクトであり、これを監視します。この変数が変化すると、スクロール方向が変わったことを示します。

高頻度のトリガー(例えば、上下に繰り返しスクロールすること)によってヘッダーが乱れるのを防ぐために、防抖関数を使用して制限しました。その中のcheckHeaderStatus関数内の

    if (top) {
      isHidden.value = false
    } else if (bottom) {
      isHidden.value = true
    }

が重要です。上にスクロールするとヘッダーが表示され、下にスクロールするとヘッダーが隠れます。

このコードの上に、意味不明なコードがあります:

    if (topArrived) {
      isHidden.value = false
      return
    }

スクロールバーが上部に到達したとき、ヘッダーを直接表示します。これは、iOS デバイスの Safari ブラウザのバウンス効果による誤判定を避けるためです。

だから、なぜ Safari のバウンス効果もスクロールイベントをトリガーするのか!!!さらには y 値が負になることもある。

サンプルを見る

ここにはもう一つの問題があります。上部にスクロールしたときだけを検出し、下部にスクロールしたときは検出していません。これは、良い方法を思いつかなかったからです。

前者は y 値が 0(または 0 未満)であるかどうかを判断することで実現できますが、後者は通常、文書の高さ - ビューポートの高さがスクロールバーの y 値と一致するはずです。しかし、Safari では一致しない可能性があります。Safari では、アドレスバーが収縮すると、上記の公式が成り立ちますが、アドレスバーが展開されている状態では、両者はアドレスバーの高さの差が生じます。そして、アドレスバーが展開されているかどうかを判断する方法は見つかりませんでした。

もし、下部にスクロールしたかどうかを判断する方法を知っている方がいれば、ぜひ教えてください。

読み込み中...
文章は、創作者によって署名され、ブロックチェーンに安全に保存されています。