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
控制吸頂或者吸底時的樣式層級。children
children
元素是正常的 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吸頂吸底功能的資料請關注其它相關文章!