IntersectionObserver + scrollIntoView 實現電梯導航

来源:https://www.cnblogs.com/zsxblog/p/18346207
-Advertisement-
Play Games

電梯導航也被稱為錨點導航,當點擊錨點元素時,頁面內相應標記的元素滾動到視口。而且頁面內元素滾動時相應錨點也會高亮。電梯導航一般把錨點放在左右兩側,類似電梯一樣。常見的電梯導航效果如下,比如一些官方文檔中: 之前可能會用 getBoundingClientRect() 判斷元素是否在視口中來實現類似效 ...


電梯導航也被稱為錨點導航,當點擊錨點元素時,頁面內相應標記的元素滾動到視口。而且頁面內元素滾動時相應錨點也會高亮。電梯導航一般把錨點放在左右兩側,類似電梯一樣。常見的電梯導航效果如下,比如一些官方文檔中:

image

image

之前可能會用 getBoundingClientRect() 判斷元素是否在視口中來實現類似效果,但現在有更方便的方法了,那就是 IntersectionObserver + scrollIntoView,輕鬆實現電梯導航。

scrollIntoView() 介紹

scrollIntoView() 方法會滾動元素的父容器,使元素出現在可視區域。預設是立即滾動,沒有動畫效果。

如果要添加動畫效果,可以這麼做:

scrollIntoView({
  behavior: 'smooth'  // instant 為立即滾動
})

它還有兩個可選參數 blockinline

block 表示元素出現在視口時垂直方向與父容器的對齊方式,inline 表示元素出現在視口時水平方向與父容器的對齊方式。

他們同樣都有四個值可選 startcenterend 、nearest。預設為 start;

scrollIntoView({
  behavior: 'smooth',
  block:'center',
  inline:'center',
})

對於 block

  • start  將元素的頂部和滾動容器的頂部對齊。

  • center  將元素的中心和滾動容器的中心垂直對齊。

  • end  將元素的底部和滾動容器的底部對齊。

對於 inline

  • start 將元素的左側和滾動容器的左側對齊。

  • center  將元素的中心和滾動容器的中心水平對齊。

  • end  將元素的右側和容器的右側對齊。

nearest 不論是垂直方向還是水平方向,只要出現在視口任務就完成了。可以理解為以最小移動量讓元素出現在視口,(慵懶移動)。如果元素已經完全出現在視口中,則不會發生變化。

通過下麵動圖來感受這個變化,下麵滾動容器中有四行五列,包含了從字母 AT。點擊 出現在視口 的按鈕會取三個下拉框的值作為參數來調用 scrollIntoView() 方法。

image

再來看看設置為 nearest 後的滾動情況

image

當字母 G 在視口內時,調用方法滾動容器不會發生變化。當 G 不完全在視口內,則會滾動到完全出現在視口內為止。

在這裡可以查看這個完整例子 scrollIntoView 可選項參數實踐(codepen)

而且 scrollIntoView 相容性也很好

image

IntersectionObserver 介紹

Intersection Observer API(交叉觀察器 API) 提供了一種非同步檢測目標元素與祖先元素或頂級文檔的視口相交情況變化的方法。也就是能判斷元素是否在視口中,並且能監聽元素在視口中出現的可見部分的比例,從而可以執行我們自定義的邏輯。

由於是非同步,也就不會阻塞主線程,性能自然比之前的頻繁執行 getBoundingClientRect() 判斷元素是否在視口中要好。

創建一個 IntersectionObserver

let options = {
  root: document.querySelector(selector),
  rootMargin: "0",
  threshold: 1.0,
};

let observer = new IntersectionObserver(callback, options);

let target = document.querySelector(selector);
observer.observe(target); //監聽目標元素

通過調用 IntersectionObserver 構造函數可以創建一個交叉觀察器,構造函數接收兩個參數,一個回調函數和一個可選項。上面例子中,當元素完全出現(100%)在視口中時會調用回調函數。

可選項

  • root 用作視口的元素,必須是目標的祖先。預設為瀏覽器視口。

  • rootMargin 根周圍的邊距,也就是可以限制根元素檢測視口的大小。值的方向大小和平常用的 margin 一樣,例如 "10px 20px 30px 40px"(上、右、下、左)。只不過正數是增大根元素檢測範圍,負數是減小檢測範圍。

比如設置一個可以滾動的 div 容器為根元素,寬高都為1000px。 此時設置 rootMargin:0 表示根元素檢測視口大小就是當前根元素可視區域大小,也就是 1000px * 1000px。設置 rootMargin:25% 0 25% 0 表示上下邊距為 25%,那麼檢測視口大小就是 1000px * 500px。

  • threshold 一個數字或一個數字數組,表示目標出現在視口中達到多少百分比時,觀察器的回調就應該執行。如果只想在能見度超過 50% 時檢測,可以使用 0.5 的值。如果希望每次能見度超過 25% 時都執行回調,則需要指定數組 [0, 0.25, 0.5, 0.75, 1]。預設值為 0(這意味著只要有一個像素可見,回調就會運行)。值為 1.0 意味著在每個像素都可見之前,閾值不會被認為已通過。

回調函數

當目標元素匹配了可選項中的配置後,會觸發我們定義的回調函數

let options = {
  root: document.querySelector(selector),
  rootMargin: "0",
  threshold: 1.0,
};

let observer = new IntersectionObserver(function (entries) {
      entries.forEach(entry => {
        
      })
    }, options);

entries 表示被監聽目標元素組成的數組,數組裡面每個 entry 都有下列一些值

  • entry.boundingClientRect 返回目標元素的邊界信息,值和 getBoundingClientRect() 形式一樣。

  • entry.intersectionRatio 目標元素和根元素交叉的比例,也就是出現在檢測區域的比例。

  • entry.intersectionRect 返回根和目標元素的相交區域的邊界信息,值和 getBoundingClientRect() 形式一樣。

  • entry.isIntersecting 返回true或者fasle,表示是否出現在根元素檢測區域內

  • entry.rootBounds 返回根元素的邊界信息,值和 getBoundingClientRect() 形式一樣。

  • entry.target 返回出現在根元素檢測區域內的目標元素

  • entry.time 返回從交叉觀察器被創建到目標元素出現在檢測區域內的時間戳

比如,要檢測目標元素有75%出現在檢測區域中就可以這樣做:

entries.forEach(entry => {
    if(entry.isIntersecting && entry.intersectionRatio>0.75){
         
    }
})

監聽目標元素

創建一個觀察器後,對一個或多個目標元素進行觀察。

let target = document.querySelector(selector);
observer.observe(target);

document.querySelectorAll('div').forEach(el => {
    observer.observe(el)
})

IntersectionObserver 的相容性也很好:

image

掌握了 IntersectionObserver + scrollIntoView 的用法,實現電梯導航就簡單了。

簡單寫一個電梯導航的 htmlcss

<div class="a" style="background:aqua;">第一章</div>
<div class="b" style="background: blueviolet;">第二章</div>
<div class="c" style="background: chartreuse;">第三章</div>
<div class="d" style="background: darkgoldenrod;">第四章</div>
<div class="e" style="background: firebrick;">第五章</div>
<div class="f" style="background: gold;">第六章</div>
<div class="g" style="background: hotpink;">第七章</div>
<ul class="rightBox">
    <li class="aLi">第一章</li>
    <li class="bLi">第二章</li>
    <li class="cLi">第三章</li>
    <li class="dLi">第四章</li>
    <li class="eLi">第五章</li>
    <li class="fLi">第六章</li>
    <li class="gLi">第七章</li>
</ul>
html,
body {
  width: 100%;
  height: 100%;
  background-color: #fff;
}

ul,li{list-style: none;}

body {
  padding: 20px 0;
}

div{
  width: 60%;
  height: 70%;
  border-radius: 10px;
  margin-left: auto;
  margin-right: auto;
  opacity: 0.4;
  display: flex;
  justify-content: center;
  align-items: center;
  font-size: 30px;
  font-weight: bold;
  color: #000;
}

div+div {
  margin-top: 20px;
}

.rightBox {
  position: fixed;
  right: 20px;
  top: 50%;
  color: teal;
  transform: translatey(-50%);
}

li {
  cursor: pointer;
  box-sizing: border-box;
  border: 1px solid #fff;
  border-radius: 4px;
  padding: 8px 12px;
}

li:hover {
  background: #f5d2c4;
}

.active {
  background: #f5d2c4;
}

預覽如下:

image


第一步:點擊右邊的導航菜單,利用 scrollIntoView 方法使內容區域對應的元素出現在可視區域中。

    let rightBox = document.querySelector('.rightBox')
    rightBox.addEventListener('click', function (e) {
      let target = e.target || e.srcElement;
      if (target && !target.classList.contains('rightBox')) {
        document.querySelector('.' + target.className.replace('Li', '')).scrollIntoView({
          behavior: 'smooth',
          block: 'center'
        })
      }
    }, false)

image


第二步:頁面容器滾動時,當目標元素出現在檢測區域內則聯動改變對應導航的樣式。

這裡 threshold 被設置為 1,也就是當目標元素完全顯示在可視區域時執行回調,改變導航菜單的樣式。

let observer = new IntersectionObserver(function (entries) {
  entries.forEach(entry => {
    let target = document.querySelector('.' + entry.target.className + 'Li')
    if (entry.isIntersecting) { // 出現在檢測區域內
      document.querySelectorAll('li').forEach(el => {
        if(el.classList.contains('active')){
          el.classList.remove('active')
        }
      })
      if (!target.classList.contains('active')) {
        target.classList.add('active')
      }
    }
  })
}, {
  threshold: 1
})

document.querySelectorAll('div').forEach(el => {
  observer.observe(el)
})

效果如下:

image


基本要求達到了,不過在滾動過程中,還有些問題。比如連續兩個元素來回切換時,第二個元素比第一個元素在檢測區域顯示的比例更高,雖然沒達到 100%,這時候導航菜單顯示還是第一個元素的。見下圖:

image


所以這裡可以控制的更細,兩個元素之間誰顯示的比例更高時就高亮誰的導航菜單。

let observer = new IntersectionObserver(function (entries) {
    entries.forEach(entry => {
        if (entry.isIntersecting && entry.intersectionRatio > 0.65) {
            
        }
    })
}, {
    threshold: [0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8]
})

這裡設置了 threshold: [0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8],當目標元素出現在檢測區域的比例達到 20%,30%,40%,50%,60%,70%,80% 的時候會執行回調函數,在回調函數里,目標元素可見並且在檢測區域顯示的比例達到 65% 時高亮導航菜單。這樣效果就好些了:

image

在這裡可以查看這個完整例子 IntersectionObserver + scrollIntoView 實現電梯導航

當然,具體還是看實際元素塊大小和業務需求來定。

如有幫助,幫忙點點贊,感謝~


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • title: 掌握 Nuxt 3 的頁面元數據:使用 definePageMeta 進行自定義配置 date: 2024/8/11 updated: 2024/8/11 author: cmdragon excerpt: 摘要:本文詳細介紹Nuxt 3框架中definePageMeta的使用方法,包 ...
  • title: 使用 defineNuxtRouteMiddleware 創建路由中間件 date: 2024/8/10 updated: 2024/8/10 author: cmdragon excerpt: 本篇文章介紹瞭如何使用 defineNuxtRouteMiddleware 創建和應用路由 ...
  • 摘要:目前 OpenTiny HUICharts 已經成功落地在華為內部100多個產品中,持續提升了用戶的可視化體驗。 本文分享自華為雲社區《OpenTiny HUICharts 正式開源發佈,一個簡單、易上手的圖表組件庫》,作者: OpenTiny。 引言 大家好! 我們非常高興地跟大家宣佈,今天 ...
  • title: 使用 defineNuxtComponent`定義 Vue 組件 date: 2024/8/9 updated: 2024/8/9 author: cmdragon excerpt: 摘要:本文介紹了在Nuxt 3中使用defineNuxtComponent輔助函數定義類型安全的Vue ...
  • 我們是袋鼠雲數棧 UED 團隊,致力於打造優秀的一站式數據中台產品。我們始終保持工匠精神,探索前端道路,為社區積累並傳播經驗價值。 本文作者:佳嵐 可編輯表格在數棧產品中是一種比較常見的表單數據交互方式,一般都支持動態的新增、刪除、排序等基礎功能。 交互分類 可編輯表格一般為兩種交互形式: 實時保存 ...
  • title: 使用 createError 創建錯誤對象的詳細指南 date: 2024/8/8 updated: 2024/8/8 author: cmdragon excerpt: 摘要:本文介紹了createError函數在Nuxt應用開發中的使用方法,用於創建帶有附加元數據的錯誤對象,以提升 ...
  • title: 清除 Nuxt 狀態緩存:clearNuxtState date: 2024/8/7 updated: 2024/8/7 author: cmdragon excerpt: 摘要:本文介紹了Nuxt.js框架中clearNuxtState方法的使用,該方法用於清除useState管理的 ...
  • 前言 在一些特殊的場景中(比如低代碼、減少小程式包體積、類似於APP的熱更新),我們需要從服務端動態載入.vue文件,然後將動態載入的遠程vue組件渲染到我們的項目中。今天這篇文章我將帶你學會,在vue3中如何去動態載入遠程組件。 歐陽寫了一本開源電子書vue3編譯原理揭秘,這本書初中級前端能看懂。 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...