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
  • 示例項目結構 在 Visual Studio 中創建一個 WinForms 應用程式後,項目結構如下所示: MyWinFormsApp/ │ ├───Properties/ │ └───Settings.settings │ ├───bin/ │ ├───Debug/ │ └───Release/ ...
  • [STAThread] 特性用於需要與 COM 組件交互的應用程式,尤其是依賴單線程模型(如 Windows Forms 應用程式)的組件。在 STA 模式下,線程擁有自己的消息迴圈,這對於處理用戶界面和某些 COM 組件是必要的。 [STAThread] static void Main(stri ...
  • 在WinForm中使用全局異常捕獲處理 在WinForm應用程式中,全局異常捕獲是確保程式穩定性的關鍵。通過在Program類的Main方法中設置全局異常處理,可以有效地捕獲並處理未預見的異常,從而避免程式崩潰。 註冊全局異常事件 [STAThread] static void Main() { / ...
  • 前言 給大家推薦一款開源的 Winform 控制項庫,可以幫助我們開發更加美觀、漂亮的 WinForm 界面。 項目介紹 SunnyUI.NET 是一個基於 .NET Framework 4.0+、.NET 6、.NET 7 和 .NET 8 的 WinForm 開源控制項庫,同時也提供了工具類庫、擴展 ...
  • 說明 該文章是屬於OverallAuth2.0系列文章,每周更新一篇該系列文章(從0到1完成系統開發)。 該系統文章,我會儘量說的非常詳細,做到不管新手、老手都能看懂。 說明:OverallAuth2.0 是一個簡單、易懂、功能強大的許可權+可視化流程管理系統。 有興趣的朋友,請關註我吧(*^▽^*) ...
  • 一、下載安裝 1.下載git 必須先下載並安裝git,再TortoiseGit下載安裝 git安裝參考教程:https://blog.csdn.net/mukes/article/details/115693833 2.TortoiseGit下載與安裝 TortoiseGit,Git客戶端,32/6 ...
  • 前言 在項目開發過程中,理解數據結構和演算法如同掌握蓋房子的秘訣。演算法不僅能幫助我們編寫高效、優質的代碼,還能解決項目中遇到的各種難題。 給大家推薦一個支持C#的開源免費、新手友好的數據結構與演算法入門教程:Hello演算法。 項目介紹 《Hello Algo》是一本開源免費、新手友好的數據結構與演算法入門 ...
  • 1.生成單個Proto.bat內容 @rem Copyright 2016, Google Inc. @rem All rights reserved. @rem @rem Redistribution and use in source and binary forms, with or with ...
  • 一:背景 1. 講故事 前段時間有位朋友找到我,說他的窗體程式在客戶這邊出現了卡死,讓我幫忙看下怎麼回事?dump也生成了,既然有dump了那就上 windbg 分析吧。 二:WinDbg 分析 1. 為什麼會卡死 窗體程式的卡死,入口門檻很低,後續往下分析就不一定了,不管怎麼說先用 !clrsta ...
  • 前言 人工智慧時代,人臉識別技術已成為安全驗證、身份識別和用戶交互的關鍵工具。 給大家推薦一款.NET 開源提供了強大的人臉識別 API,工具不僅易於集成,還具備高效處理能力。 本文將介紹一款如何利用這些API,為我們的項目添加智能識別的亮點。 項目介紹 GitHub 上擁有 1.2k 星標的 C# ...