戴兜

戴兜的小屋

Coding the world.
github
bilibili
twitter

實現滾動時Header自動隱藏

image

這是掘金網頁版的頭部,當滾動條向下滑動時,主 header 會隱藏,次級 header 會吸在頁面頂部。

image

樣式#

首先,我們先通過兩個 div 來模擬這兩個 header

<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

這裡使用絕對定位來將兩個 header 固定在頁面頂部,這兩個 div 分別是main-headersub-header。他們兩個分別用容器main-header-wrappersub-header-wrapper包裹,是為了防止由於 header 脫離文檔流導致遮住正文元素。

簡化#

不妨把滾動方向的檢測放到一邊,先實現這樣的效果:為兩個 header 加上類名hidden的時候,主 header 隱藏,次級 header 吸頂。這裡可以直接用 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 類名的 header 元素,將它們的 transform 屬性設置為translateY(-50px)(即向上偏移 50px)。同時為元素設置 transition,提供緩動效果。

這樣,header 的顯示和隱藏就實現了,接下來就是檢測滾動方向。

實現#

使用 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 是一個 reactive 對象,我們對其進行監聽,當該變量發生變化時,說明滾動方向發生了改變。

為了防止高頻觸發(例如反復上下滾動)導致 header 亂動,使用防抖函數限制了一下,其中checkHeaderStatus函數內的

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

是關鍵,當向上滾動時,顯示 header;向下滾動時,隱藏 header。

你可能會發現在這段代碼的上面,還有一段不明所以的代碼:

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

當滾動條到頂部時,直接顯示 header,這是為了避免 iOS 設備中 safari 瀏覽器橡皮筋效果導致的誤判

所以為什麼 safari 橡皮筋效果也會觸發 scroll 事件啊啊啊啊啊!!!甚至 y 值能到負值

查看示例

這裡還有一個問題,只檢測了滾動到頂部,而沒有檢測滾動到底部。這是因為我沒想到什麼很好的方法去檢測。

前者,可以通過判斷 y 值是否為 0(或小於 0)來實現;而後者,正常來說,滾動到底部時,文檔高度 - 視窗高度應該和滾動條的 y 值是一致的。但是在 safari 裡可能不一致,在 safari 中,當地址欄收縮時,上文的公式成立;但是當地址欄是展開狀態時,兩者會一個相差地址欄的高度。而地址欄是否展開我沒找到判斷的方法。

如果有知道如何判斷是否滾動到底部的,希望能夠和我分享分享

載入中......
此文章數據所有權由區塊鏈加密技術和智能合約保障僅歸創作者所有。