React 實現具備吸頂和吸底功能組件實例
目錄
- 背景
- 實現
- 結語
背景
現在手機應用經常有這樣一個場景:
頁面上有一個導航,導航位置在頁面中間位置,當頁面頂部滾動到導航位置時,導航自動吸頂,頁面繼續往下滾動時,它就一直在頁面視窗頂部顯示,當往上滾動時,經過最初位置時,導航自動復原,不再吸頂。
效果就如京東超市首頁的導航欄一樣:

下面我們就來具體實現這樣一個 React 組件,實現后還會再擴展延伸一下 吸底 功能,因為 吸底 場景也不少。
具體要求:
- 需要可以設置是
吸頂還是吸底。 吸頂可以設置距離視窗頂部的位置,吸頂可以設置距離視窗底部的位置。- 可以對正常組件都生效,不影響組件自身的樣式。
實現
組件主要是為了 吸頂 或者 吸底 功能,那么就命名為 AutoFixed 。
主要實現邏輯:需要判斷自身在視窗內的位置與設置的 吸頂 或者 吸底 位置是否匹配,匹配上了則可以進行 吸頂 或者 吸底。
獲取自身位置一般可以用 滾動的位置 和 自身距離頁面頂部 的位置來判斷,但實現起來會麻煩一些,IntersectionObserver也很好用,而且性能會更好,因此這里將直接使用 IntersectionObserver 來處理。
下面,我們先實現一個基于 IntersectionObserver 實現的判斷位置的 hook。
定義 props 類型:
import { RefObject } from "react";type Props = { el: React.RefObject<Element>; options?: IntersectionObserverInit;};可接受參數:
el: React 的 ref 實例,被判斷判斷位置的 DOM 元素。 options: IntersectionObserver 構造函數的初始化參數。
具體實現:
import React, { useEffect, useState } from "react";export function useIntersection(props: Props): boolean { const { el, options } = props; // 是否到了指定位置區域 const [intersection, setIntersection] = useState(true); useEffect(() => { if (!el.current) return; // 初始化 IntersectionObserver 實例 const intersectionObserver = new IntersectionObserver( function (entries) {setIntersection(entries[0].intersectionRatio === 1); }, { ...options, threshold: [1] } ); // 開始監聽 intersectionObserver.observe(el.current); return (): void => { // 銷毀 intersectionObserver.disconnect(); }; }, [el.current]); return intersection;}現在實現了一個可以根據傳入的參數來控制否到了指定位置區域的 hook :useIntersection。
useIntersection 只是對 IntersectionObserver 的簡單封裝,并沒有復雜實現,具體作用就是用于判斷某個元素是否進入了 可視窗口,想了解更多可以點擊去查看它的MDN文檔。
下面再來實現我們要實現的具備吸頂和吸底功能的組件:AutoFixed。
參數定義:
export type AutoFixedProps = React.ImgHTMLAttributes<HTMLDivElement> & { /** 吸頂距離 */ top?: string; /** 吸底距離 */ bottom?: string; /** 是否一直吸頂或者吸底 */ alwaysFixed?: boolean; zIndex?: number; children: React.ReactNode; /** 元素框高度 */ height: number | string; /** 相對的目標元素,因為是用的 fixed 定位,記得做相應處理。 */ root?: Element | Document | null; /** 固定的時候才有的className */ fixedClassName?: string; /** 固定的時候才有的樣式 */ fixedStyle?: React.CSSProperties; /** fixed狀態改變時調用 */ onFixedChange?: (isFixed: boolean) => void;};可接受參數 基于 React.HtmlHTMLAttributes<HTMLDivElement> ,也就是繼承了 div 的默認屬性。
其他自定義參數說明:
top吸頂距離,元素頂部距離視窗頂部小于等于top時,進行吸頂。bottom吸底部距離,元素底部距離視窗底部大于等于bottom時,進行吸底。注意邏輯是和吸頂相反。alwaysFixed,用于支持默認就要一直吸頂或者吸底的情況,需要配合top和bottom來使用。zIndex控制吸頂或者吸底時的樣式層級。childrenchildren元素是正常的 React 組件即可。height被包裹元素的高度.也就是children元素 的高度。root,相對視窗的目標元素,也就是可以控制在某個區域內進行吸頂和吸底,但因為這里是用的fixed定位,如果需要設置root時,需要改變成absolute定位。fixedClassName吸頂和吸底的時候需要動態添加的className。fixedStyle吸頂和吸底的時候需要動態添加的樣式。onFixedChange吸頂和吸底的時候告訴外界。
具體實現:
import React, { useRef, useEffect } from "react";import { useIntersection } from "../../components/hooks/use-intersection";export const AutoFixed = (props: AutoFixedProps) => { const { alwaysFixed, top, bottom, style, height, root, zIndex = 100, children, className, fixedClassName, fixedStyle, onFixedChange, ...rest } = props; // `bottom` 值存在時,表面要懸浮底部 const isFiexdTop = !bottom; const wrapperRef = useRef<HTMLDivElement>(null); // 設置監聽參數控制:top 為吸頂距離,bottom 為吸底距離 const options = { rootMargin: isFiexdTop ? `-${top || "0px"} 0px 1000000px 0px` : `0px 0px -${bottom || "0px"} 0px`, // 設置root root, } as IntersectionObserverInit; // 是否懸浮 const intersection = useIntersection({ el: wrapperRef, options }); const shouldFixed = alwaysFixed ? true : !intersection; useEffect(() => { // 通知外部 onFixedChange?.(shouldFixed); }, [shouldFixed, onFixedChange]); return ( <div style={{ ...style, height }} {...rest} className={`${className}${shouldFixed ? " fixedClassName" : ""}`} ref={wrapperRef} > <divstyle={{ height, position: shouldFixed ? "fixed" : "initial", top: isFiexdTop ? top || 0 : undefined, bottom: isFiexdTop ? undefined : bottom || 0, zIndex: zIndex, ...(shouldFixed ? fixedStyle : {}),}} >{children} </div> </div> );};實現邏輯:
- 使用了
alwaysFixed判斷是否一直懸浮。 - 默認懸浮頂部,
bottom值存在時,表明要懸浮底部。 - 給
useIntersection傳入監聽位置控制參數。 - 根據
useIntersection的結果來判斷是否應該吸頂或吸底。 - 做了
style樣式和className傳入處理的問題,以及 zIndex 層級問題。 - 吸頂時,不進行設置
bottom,吸底時,不進行設置bottom。
主要核心邏輯是第 3 點:
const options = { rootMargin: `-${top || "0px"} 0px -${bottom || "0px"} 0px`,};rootMargin 中:-${top || "0px"} 為吸頂距離,-${bottom || "0px"} 為吸底距離。一定要是負的,正數表示延伸到了視窗外的距離,負數表示距離視窗頂部或者底部的距離。
使用方式:
<AutoFixed // 距離頂部為 20px 吸頂 top="20px" // 占位高度,也就是 children 的高度 height="20px" // fixed狀態改變時 onFixedChange={(isFixed) => { console.log(`isFixed: ` + isFixed); }} // fixed狀態需要添加的className fixedClassName="hello" // fixed狀態需要添加的style fixedStyle={{ color: "red" }}> <div>我是懸浮內容,高度 20px, 距離頂部為 20px 吸頂 </div></AutoFixed>實現效果:

可以看出 一直吸頂 、滾動到設定位置吸頂 、 一直吸底 、滾動到設定位置吸底 四個功能都可以正常工作。
滾動到設定位置吸底 指的是,從底部向上滾動的時候,這個功能就是為了在劃出屏幕區域的時候顯示在底部。
大家也可以打開 示例 自己去體驗一下。
結語
這是之前在比較多的頁面會用到的一個功能點,然后寫了幾次后,發現每次實現這個功能都有點復雜,于是封裝了 吸頂 組件,本次寫文章,就想著剛好可以完善一下,把 吸底 功能也開發出來,因為后續也有用到過不少次。
以上就是React 實現具備吸頂和吸底功能組件實例的詳細內容,更多關于React吸頂吸底功能的資料請關注其它相關文章!

網公網安備