欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

在React項目中實現(xiàn)一個簡單的錨點目錄定位

 更新時間:2023年09月26日 09:27:51   作者:linwu  
錨點目錄定位功能在長頁面和文檔類網(wǎng)站中非常常見,它可以讓用戶快速定位到頁面中的某個章節(jié),本文講給大家介紹一下React項目中如何實現(xiàn)一個簡單的錨點目錄定位,文中有詳細(xì)的實現(xiàn)代碼,需要的朋友可以參考下

前言

錨點目錄定位功能在長頁面和文檔類網(wǎng)站中非常常見,它可以讓用戶快速定位到頁面中的某個章節(jié)

  • 如何在React中實現(xiàn)錨點定位和平滑滾動
  • 目錄自動高亮的實現(xiàn)思路
  • 處理頂部導(dǎo)航遮擋錨點的解決方案
  • 服務(wù)端渲染下的實現(xiàn)方案
  • 性能優(yōu)化策略

實現(xiàn)基本錨點定位

首先,我們需要實現(xiàn)頁面內(nèi)基本的錨點定位功能。對于錨點定位來說,主要涉及這兩個部分:

  • 設(shè)置錨點,為頁面中的某個組件添加id屬性
  • 點擊鏈接,跳轉(zhuǎn)到指定錨點處

例如:

// 錨點組件
function AnchorComponent() {
  return <h2 id="anchor">This is anchor</h2> 
}
// 鏈接組件
function LinkComponent() {
  return (
    <a href="#anchor" rel="external nofollow"  rel="external nofollow"  rel="external nofollow" >Jump to Anchor</a> 
  )
}

當(dāng)我們點擊Jump to Anchor這個鏈接時,頁面會平滑滾動到AnchorComponent所在的位置。

使用useScrollIntoView自定義hook

React中實現(xiàn)錨點定位,最簡單的方式就是使用useScrollIntoView這個自定義hook。

import { useScrollIntoView } from 'react-use';
function App() {
  const anchorRef = useRef();  
  const scrollToAnchor = () => {
    useScrollIntoView(anchorRef);
  }
  return (
    <>
      <a href="#anchor" rel="external nofollow"  rel="external nofollow"  rel="external nofollow"  onClick={scrollToAnchor}>
        Jump to Anchor  
      </a>
      <h2 id="anchor" ref={anchorRef}>This is anchor</h2>
    </>
  )
}

useScrollIntoView接受一個ref對象,當(dāng)調(diào)用這個hook函數(shù)時,會自動滾動頁面,使得ref對象在可視區(qū)域內(nèi)。

原生scrollIntoView方法

useScrollIntoView內(nèi)部其實就是使用了原生的scrollIntoView方法,所以我們也可以直接調(diào)用:

function App() {
  const anchorRef = useRef();
  const scrollToAnchor = () => {
    anchorRef.current.scrollIntoView({
      behavior: 'smooth',
      block: 'start'
    })
  };
  return (
    <>  
      <a href="#anchor" rel="external nofollow"  rel="external nofollow"  rel="external nofollow"  onClick={scrollToAnchor}>Jump to Anchor</a>
      <h2 id="anchor" ref={anchorRef}>This is anchor</h2> 
    </>
  )
}

scrollIntoView可以讓元素的父容器自動滾動,將這個元素滾動到可見區(qū)域。behavior:'smooth'可以啟用平滑滾動效果。

錨點定位和目錄聯(lián)動

很多時候,我們會在頁面中實現(xiàn)一個目錄導(dǎo)航,可以快速定位到各個章節(jié)。此時就需要實現(xiàn)錨點定位和目錄的聯(lián)動效果:

  • 點擊目錄時,自動滾動到對應(yīng)的章節(jié)
  • 滾動頁面時,自動高亮正在瀏覽的章節(jié)

目錄導(dǎo)航組件

目錄導(dǎo)航本身是一個靜態(tài)組件,我們通過props傳入章節(jié)數(shù)據(jù):

function Nav({ chapters }) {
  return (
    <ul className=" chapters">
      {chapters.map(chapter => (
        <li key={chapter.id}>
          <a href={'#' + chapter.id}>  
            {chapter.title}
          </a>
        </li>
      ))}
    </ul>
  )
}

錨點組件

然后在頁面中的每一章使用Anchor組件包裹:

function Chapter({ chapter }) {
  return (
    <Anchor id={chapter.id}>  
      <h2>{chapter.title}</h2>
      {chapter.content}
    </Anchor>
  )
}
function Anchor({ children, id }) {
  return (
    <div id={id}>
      {children}  
    </div>
  )
}

這樣通過id屬性建立章節(jié)內(nèi)容和目錄鏈接之間的關(guān)聯(lián)。

處理點擊事件

當(dāng)點擊目錄鏈接時,需要滾動到對應(yīng)的章節(jié)位置:

function App() {
  //...
  const scrollToChapter = (chapterId) => {
    const chapterEl = document.getElementById(chapterId);
    chapterEl.scrollIntoView({ behavior: 'smooth' });
  }
  return (
    <>
      <Nav 
        chapters={chapters}
        onLinkClick={(chapterId) => scrollToChapter(chapterId)} 
      />
      {chapters.map(chapter => (
        <Chapter 
         key={chapter.id}
         chapter={chapter}
        />
      ))}
    </>
  )
}

給Nav組件傳一個onLinkClick回調(diào),當(dāng)點擊鏈接時,通過chapterId獲取到元素,并滾動到可視區(qū)域,實現(xiàn)平滑跳轉(zhuǎn)。

自動高亮

實現(xiàn)自動高亮也很簡單,通過監(jiān)聽滾動事件,計算章節(jié)元素的偏移量,判斷哪個章節(jié)在可視區(qū)域內(nèi),并更新active狀態(tài):

function App() {
  const [activeChapter, setActiveChapter] = useState();
  useEffect(() => {
    const handleScroll = () => {
      chapters.forEach(chapter => {
        const element = document.getElementById(chapter.id);
        // 獲取元素在可視區(qū)域中的位置
        const rect = element.getBoundingClientRect();  
        // 判斷是否在可視區(qū)域內(nèi) 
        if (rect.top >= 0 && rect.bottom <= window.innerHeight) {
          setActiveChapter(chapter.id);
        }
      })
    }
    window.addEventListener('scroll', handleScroll);
    return () => {
      window.removeEventListener('scroll', handleScroll);
    }
  }, []);
  return (
    <>
     <Nav
       chapters={chapters}
       activeChapter={activeChapter}
      />
    </>
  )
}

通過getBoundingClientRect可以得到元素相對于視窗的位置信息,根據(jù)位置判斷是否在可見區(qū)域內(nèi),如果是就更新activeChapter狀態(tài),從而觸發(fā)目錄的高亮效果。

問題解析

遮擋問題

有時錨點會被固定的Header遮擋,此時滾動會定位到元素上方,用戶看不到錨點對應(yīng)的內(nèi)容。

常見的解決方案是:

  • 設(shè)置錨點元素margin-top
#anchor {
  margin-top: 80px; /* header高度 */
}

直接設(shè)置一個和Header高度相同的margin,來防止遮擋。

  • 在滾動方法中加入offset
// scroll offset
const scrollOffset = -80; 
chapterEl.scrollIntoView({
  offsetTop: scrollOffset
})

給scrollIntoView傳入一個頂部偏移量,這樣也可以跳過Header的遮擋。

響應(yīng)式問題

在響應(yīng)式場景下,目錄的遮擋問題會更復(fù)雜。我們需要區(qū)分不同斷點下,計算匹配的offset。

可以通過MatchMedia Hook獲取當(dāng)前的斷點:

import { useMediaQuery } from 'react-responsive';
function App() {
  const isMobile = useMediaQuery({ maxWidth: 767 });
  const isTablet = useMediaQuery({ minWidth: 768, maxWidth: 1023 });
  const isDesktop = useMediaQuery({ minWidth: 1024 });
  let scrollOffset = 0;
  if (isMobile) {
    scrollOffset = 46; 
  } else if (isTablet) {  
    scrollOffset = 60;
  } else if (isDesktop) {
    scrollOffset = 80;
  }
  const scrollToChapter = (chapterId) => {
    const chapterEl = document.getElementById(chapterId);
    chapterEl.scrollIntoView({
      offsetTop: scrollOffset  
    })
  }
  //...
}

根據(jù)不同斷點,動態(tài)計算滾動偏移量,這樣可以適配所有情況。

性能優(yōu)化

使用節(jié)流

滾動事件會高頻觸發(fā),直接在滾動回調(diào)中計算章節(jié)位置會造成性能問題。

我們可以使用Lodash的throttle函數(shù)進(jìn)行節(jié)流:

import throttle from 'lodash.throttle';
const handleScroll = throttle(() => {
  // 計算章節(jié)位置
}, 100);

這樣可以限制滾動事件最多每100ms觸發(fā)一次。

IntersectionObserver

使用IntersectionObserver提供的異步回調(diào),只在章節(jié)進(jìn)入或者離開可視區(qū)域時才執(zhí)行位置計算:

import { useRef, useEffect } from 'react';
function App() {
  const chaptersRef = useRef({});
  useEffect(() => {
    const observer = new IntersectionObserver(
      (entries) => {
        // 章節(jié)進(jìn)入或者離開可視區(qū)域時更新
      }
    );
    chapters.forEach(chapter => {
      observer.observe(
        document.getElementById(chapter.id)  
      );
    })
  }, []);
} 

這種懶加載式的方式可以大幅減少無效的位置計算。

SSR支持

在Next.js等SSR場景下,客戶端腳本會延后加載,頁面初次渲染時目錄聯(lián)動會失效。

getInitialProps注水

可以在getInitialProps中提前計算目錄數(shù)據(jù),注入到頁面中:

Home.getInitialProps = async () => {
  const chapters = await fetchChapters();
  const mappedChapters = chapters.map(chapter => {
    return {
      ...chapter,
      highlighted: isChapterHighlighted(chapter) 
    }
  });
  return {
    chapters: mappedChapters
  };
};

hydrate處理

客戶端腳本加載后,需要調(diào)用ReactDOM.hydrate而不是render方法,進(jìn)行數(shù)據(jù)的補充填充,避免目錄狀態(tài)丟失。

import { useEffect } from 'react';
function App({ chapters }) {
  useEffect(() => {
    ReactDOM.hydrate(
      <App chapters={chapters} />,  
      document.getElementById('root')
    );
  }, []);
}

服務(wù)端渲染的實現(xiàn)方案

在使用了服務(wù)端渲染(SSR)的框架如Next.js等情況下,實現(xiàn)錨點定位和目錄聯(lián)動也會有一些不同。

主要區(qū)別在于:

  • 服務(wù)端和客戶端環(huán)境不統(tǒng)一
  • 腳本加載時間差

這會導(dǎo)致一些狀態(tài)錯位的問題。

問題復(fù)現(xiàn)

假設(shè)我們有下面的目錄和內(nèi)容結(jié)構(gòu):

function Nav({ chapters }) {
  return (
    <ul>
      {chapters.map(ch => (
        <li>
          <a href={'#' + ch.id}>{ch.title}</a>
        </li>
      ))}
    </ul>
  )
}
function Chapter({ chapter }) {
  const ref = useRef();
  // 占位組件
  return <div ref={ref}>{chapter.content}</div> 
}
function App() {
  const chapters = [
    { id: 'chapter-1', title: 'Chapter 1' },
    { id: 'chapter-2', title: 'Chapter 2' },
  ];
  return (
    <>
      <Nav chapters={chapters} />
      <Chapter chapter={chapters[0]} />
      <Chapter chapter={chapters[1]} />
    </>
  )
}

非SSR環(huán)境下,點擊鏈接和滾動都可以正常工作。

但是在Next.js的SSR環(huán)境下就會有問題:

點擊目錄鏈接時,頁面不會滾動。

這是因為在服務(wù)端,我們無法獲取組件的ref,所以錨點元素不存在,自然無法定位。

滾動頁面時,目錄高亮也失效。

服務(wù)端渲染的靜態(tài)HTML中,并沒有綁定滾動事件,所以無法自動高亮。

預(yù)取數(shù)據(jù)

首先,我們需要解決點擊目錄鏈接的問題。

既然服務(wù)端無法獲取組件ref,那就需要在客戶端去獲取元素位置。

這里有兩個方法:

  • 組件掛載后主動緩存元素位置
// Chapter組件
useEffect(() => {
  // 緩存位置數(shù)據(jù)
  cacheElementPosition(chapter.id, ref.current); 
}, []);
// Utils
const elementPositions = {};
function cacheElementPosition(id, element) {
  const rect = element.getBoundingClientRect();
  elementPositions[id] = {
    left: rect.left,
    top: rect.top,
  }
}
  • 點擊時實時獲取元素位置
// handle link click
const scrollToChapter = (chapterId) => {
  const element = document.getElementById(chapterId);
  const rect = element.getBoundingClientRect();
  window.scrollTo({
    top: rect.top,
    behavior: 'smooth'
  })
}

無論哪種方法,都需要在組件掛載后獲取元素的位置信息。

這樣我們就可以在點擊目錄鏈接時,正確滾動到對應(yīng)的章節(jié)位置了。

數(shù)據(jù)注水

但是點擊目錄只解決了一半問題,滾動高亮還需要解決。

這里就需要用到數(shù)據(jù)注水的技術(shù)。

簡單來說就是:

  • 在服務(wù)端渲染時,讀取路由參數(shù),提前計算高亮狀態(tài)
  • 將高亮數(shù)據(jù)注入到響應(yīng)中
  • 客戶端拿到注水的數(shù)據(jù)后渲染,不會出現(xiàn)高亮錯位

實現(xiàn)步驟:

1.服務(wù)端獲取參數(shù)和數(shù)據(jù)

// 在getServerSideProps中
export async function getServerSideProps(context) {
  const { hashtag } = context.query;
  const chapters = await fetchChapters();
  const highlightedChapter = chapters.find(ch => ch.id === hashtag);
  return {
    props: {
      chapters,
      highlightedChapter  
    }
  }
}

2.客戶端讀取props

function Nav({ chapters, highlightedChapter }) {
  return (
    <ul>
      {chapters.map(ch => (
        <li className={ch.id === highlightedChapter?.id ? 'highlighted' : ''}>
        </li>
      ))}
    </

以上就是在React項目中實現(xiàn)一個簡單的錨點目錄定位的詳細(xì)內(nèi)容,更多關(guān)于React實現(xiàn)錨點目錄定位的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • React Native使用百度Echarts顯示圖表的示例代碼

    React Native使用百度Echarts顯示圖表的示例代碼

    本篇文章主要介紹了React Native使用百度Echarts顯示圖表的示例代碼,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2017-11-11
  • React中異步數(shù)據(jù)更新不及時問題及解決

    React中異步數(shù)據(jù)更新不及時問題及解決

    這篇文章主要介紹了React中異步數(shù)據(jù)更新不及時問題及解決方案,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教
    2024-03-03
  • 詳解React+Koa實現(xiàn)服務(wù)端渲染(SSR)

    詳解React+Koa實現(xiàn)服務(wù)端渲染(SSR)

    這篇文章主要介紹了詳解React+Koa實現(xiàn)服務(wù)端渲染(SSR),小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2018-05-05
  • React 全面解析excel文件

    React 全面解析excel文件

    這篇文章主要介紹了React 全面解析excel文件,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2022-09-09
  • React組件渲染后對DOM的操作方式

    React組件渲染后對DOM的操作方式

    這篇文章主要介紹了React組件渲染后對DOM的操作方式,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2023-02-02
  • 一文帶你搞懂useCallback的使用方法

    一文帶你搞懂useCallback的使用方法

    useCallback是用來幫忙緩存函數(shù)的,當(dāng)依賴項沒有發(fā)生變化時,返回緩存的指針,而props涉及到復(fù)雜對象類型都是通過指針來傳遞到,下面這篇文章主要給大家介紹了關(guān)于useCallback使用的相關(guān)資料,需要的朋友可以參考下
    2023-02-02
  • React和Vue實現(xiàn)文件下載進(jìn)度條

    React和Vue實現(xiàn)文件下載進(jìn)度條

    本文主要介紹了React和Vue實現(xiàn)文件下載進(jìn)度條,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2023-04-04
  • 淺談React之狀態(tài)(State)

    淺談React之狀態(tài)(State)

    這篇文章主要介紹了淺談React之狀態(tài)(State),小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2018-09-09
  • GraphQL在react中的應(yīng)用示例詳解

    GraphQL在react中的應(yīng)用示例詳解

    這篇文章主要為大家介紹了GraphQL在react中的應(yīng)用示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-10-10
  • React函數(shù)組件和類組件的區(qū)別及說明

    React函數(shù)組件和類組件的區(qū)別及說明

    這篇文章主要介紹了React函數(shù)組件和類組件的區(qū)別及說明,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2022-08-08

最新評論