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

下面我們就來具體實現這樣一個 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;
// 是否到了指定位置區(qū)域
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] }
);
// 開始監(jiān)聽
intersectionObserver.observe(el.current);
return (): void => {
// 銷毀
intersectionObserver.disconnect();
};
}, [el.current]);
return intersection;
}
現在實現了一個可以根據傳入的參數來控制否到了指定位置區(qū)域的 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狀態(tài)改變時調用 */
onFixedChange?: (isFixed: boolean) => void;
};
可接受參數 基于 React.HtmlHTMLAttributes<HTMLDivElement> ,也就是繼承了 div 的默認屬性。
其他自定義參數說明:
top吸頂距離,元素頂部距離視窗頂部小于等于top時,進行吸頂。bottom吸底部距離,元素底部距離視窗底部大于等于bottom時,進行吸底。注意邏輯是和吸頂相反。alwaysFixed,用于支持默認就要一直吸頂或者吸底的情況,需要配合top和bottom來使用。zIndex控制吸頂或者吸底時的樣式層級。childrenchildren元素是正常的 React 組件即可。height被包裹元素的高度.也就是children元素 的高度。root,相對視窗的目標元素,也就是可以控制在某個區(qū)域內進行吸頂和吸底,但因為這里是用的fixed定位,如果需要設置root時,需要改變成absolute定位。fixedClassName吸頂和吸底的時候需要動態(tài)添加的className。fixedStyle吸頂和吸底的時候需要動態(tài)添加的樣式。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);
// 設置監(jiān)聽參數控制: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}
>
<div
style={{
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傳入監(jiān)聽位置控制參數。 - 根據
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狀態(tài)改變時
onFixedChange={(isFixed) => {
console.log(`isFixed: ` + isFixed);
}}
// fixed狀態(tài)需要添加的className
fixedClassName="hello"
// fixed狀態(tài)需要添加的style
fixedStyle={{ color: "red" }}
>
<div>
我是懸浮內容,高度 20px, 距離頂部為 20px 吸頂
</div>
</AutoFixed>
實現效果:

可以看出 一直吸頂 、滾動到設定位置吸頂 、 一直吸底 、滾動到設定位置吸底 四個功能都可以正常工作。
滾動到設定位置吸底 指的是,從底部向上滾動的時候,這個功能就是為了在劃出屏幕區(qū)域的時候顯示在底部。
大家也可以打開 示例 自己去體驗一下。
結語
這是之前在比較多的頁面會用到的一個功能點,然后寫了幾次后,發(fā)現每次實現這個功能都有點復雜,于是封裝了 吸頂 組件,本次寫文章,就想著剛好可以完善一下,把 吸底 功能也開發(fā)出來,因為后續(xù)也有用到過不少次。
以上就是React 實現具備吸頂和吸底功能組件實例的詳細內容,更多關于React吸頂吸底功能的資料請關注腳本之家其它相關文章!
相關文章
React?Hooks useReducer?逃避deps組件渲染次數增加陷阱
這篇文章主要介紹了React?Hooks?之?useReducer?逃避deps后增加組件渲染次數的陷阱詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2022-09-09
React Native中NavigatorIOS組件的簡單使用詳解
這篇文章主要介紹了React Native中NavigatorIOS組件的簡單使用詳解,小編覺得挺不錯的,現在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2018-01-01
詳解React中的useMemo和useCallback的區(qū)別
React中的useMemo和useCallback是兩個重要的Hooks。常常被用于優(yōu)化組件的性能。雖然這兩個Hooks看起來很相似,但它們彼此之間還是有很大的區(qū)別的,隨著小編一起來學習吧2023-04-04

