React 實(shí)現(xiàn)具備吸頂和吸底功能組件實(shí)例
目錄
- 背景
- 實(shí)現(xiàn)
- 結(jié)語(yǔ)
背景
現(xiàn)在手機(jī)應(yīng)用經(jīng)常有這樣一個(gè)場(chǎng)景:
頁(yè)面上有一個(gè)導(dǎo)航,導(dǎo)航位置在頁(yè)面中間位置,當(dāng)頁(yè)面頂部滾動(dòng)到導(dǎo)航位置時(shí),導(dǎo)航自動(dòng)吸頂,頁(yè)面繼續(xù)往下滾動(dòng)時(shí),它就一直在頁(yè)面視窗頂部顯示,當(dāng)往上滾動(dòng)時(shí),經(jīng)過最初位置時(shí),導(dǎo)航自動(dòng)復(fù)原,不再吸頂。
效果就如京東超市首頁(yè)的導(dǎo)航欄一樣:
下面我們就來具體實(shí)現(xiàn)這樣一個(gè) React
組件,實(shí)現(xiàn)后還會(huì)再擴(kuò)展延伸一下 吸底
功能,因?yàn)?吸底
場(chǎng)景也不少。
具體要求:
- 需要可以設(shè)置是
吸頂
還是吸底
。 吸頂
可以設(shè)置距離視窗頂部的位置,吸頂
可以設(shè)置距離視窗底部的位置。- 可以對(duì)正常組件都生效,不影響組件自身的樣式。
實(shí)現(xiàn)
組件主要是為了 吸頂
或者 吸底
功能,那么就命名為 AutoFixed
。
主要實(shí)現(xiàn)邏輯:需要判斷自身在視窗內(nèi)的位置與設(shè)置的 吸頂
或者 吸底
位置是否匹配,匹配上了則可以進(jìn)行 吸頂
或者 吸底
。
獲取自身位置一般可以用 滾動(dòng)的位置
和 自身距離頁(yè)面頂部
的位置來判斷,但實(shí)現(xiàn)起來會(huì)麻煩一些,IntersectionObserver
也很好用,而且性能會(huì)更好,因此這里將直接使用 IntersectionObserver
來處理。
下面,我們先實(shí)現(xiàn)一個(gè)基于 IntersectionObserver
實(shí)現(xiàn)的判斷位置的 hook
。
定義 props 類型:
import { RefObject } from "react";type Props = { el: React.RefObject<Element>; options?: IntersectionObserverInit;};
可接受參數(shù):
el
: React 的 ref
實(shí)例,被判斷判斷位置的 DOM 元素。 options
: IntersectionObserver 構(gòu)造函數(shù)的初始化參數(shù)。
具體實(shí)現(xiàn):
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 實(shí)例 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;}
現(xiàn)在實(shí)現(xiàn)了一個(gè)可以根據(jù)傳入的參數(shù)來控制否到了指定位置區(qū)域的 hook :useIntersection
。
useIntersection
只是對(duì) IntersectionObserver
的簡(jiǎn)單封裝,并沒有復(fù)雜實(shí)現(xiàn),具體作用就是用于判斷某個(gè)元素是否進(jìn)入了 可視窗口
,想了解更多可以點(diǎn)擊去查看它的MDN文檔。
下面再來實(shí)現(xiàn)我們要實(shí)現(xiàn)的具備吸頂和吸底功能的組件:AutoFixed
。
參數(shù)定義:
export type AutoFixedProps = React.ImgHTMLAttributes<HTMLDivElement> & { /** 吸頂距離 */ top?: string; /** 吸底距離 */ bottom?: string; /** 是否一直吸頂或者吸底 */ alwaysFixed?: boolean; zIndex?: number; children: React.ReactNode; /** 元素框高度 */ height: number | string; /** 相對(duì)的目標(biāo)元素,因?yàn)槭怯玫?fixed 定位,記得做相應(yīng)處理。 */ root?: Element | Document | null; /** 固定的時(shí)候才有的className */ fixedClassName?: string; /** 固定的時(shí)候才有的樣式 */ fixedStyle?: React.CSSProperties; /** fixed狀態(tài)改變時(shí)調(diào)用 */ onFixedChange?: (isFixed: boolean) => void;};
可接受參數(shù) 基于 React.HtmlHTMLAttributes<HTMLDivElement>
,也就是繼承了 div
的默認(rèn)屬性。
其他自定義參數(shù)說明:
top
吸頂距離,元素頂部
距離視窗頂部
小于等于top
時(shí),進(jìn)行吸頂。bottom
吸底部距離,元素底部
距離視窗底部
大于等于bottom
時(shí),進(jìn)行吸底。注意邏輯是和吸頂
相反。alwaysFixed
,用于支持默認(rèn)就要一直吸頂或者吸底的情況,需要配合top
和bottom
來使用。zIndex
控制吸頂或者吸底時(shí)的樣式層級(jí)。children
children
元素是正常的 React 組件即可。height
被包裹元素的高度.也就是children
元素 的高度。root
,相對(duì)視窗的目標(biāo)元素,也就是可以控制在某個(gè)區(qū)域內(nèi)進(jìn)行吸頂和吸底,但因?yàn)檫@里是用的fixed
定位,如果需要設(shè)置root
時(shí),需要改變成absolute
定位。fixedClassName
吸頂和吸底的時(shí)候需要?jiǎng)討B(tài)添加的className
。fixedStyle
吸頂和吸底的時(shí)候需要?jiǎng)討B(tài)添加的樣式
。onFixedChange
吸頂和吸底的時(shí)候告訴外界。
具體實(shí)現(xiàn):
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` 值存在時(shí),表面要懸浮底部 const isFiexdTop = !bottom; const wrapperRef = useRef<HTMLDivElement>(null); // 設(shè)置監(jiān)聽參數(shù)控制:top 為吸頂距離,bottom 為吸底距離 const options = { rootMargin: isFiexdTop ? `-${top || "0px"} 0px 1000000px 0px` : `0px 0px -${bottom || "0px"} 0px`, // 設(shè)置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> );};
實(shí)現(xiàn)邏輯:
- 使用了
alwaysFixed
判斷是否一直懸浮。 - 默認(rèn)懸浮頂部,
bottom
值存在時(shí),表明要懸浮底部。 - 給
useIntersection
傳入監(jiān)聽位置控制參數(shù)。 - 根據(jù)
useIntersection
的結(jié)果來判斷是否應(yīng)該吸頂
或吸底
。 - 做了
style
樣式和className
傳入處理的問題,以及 zIndex 層級(jí)問題。 - 吸頂時(shí),不進(jìn)行設(shè)置
bottom
,吸底時(shí),不進(jìn)行設(shè)置bottom
。
主要核心邏輯是第 3
點(diǎn):
const options = { rootMargin: `-${top || "0px"} 0px -${bottom || "0px"} 0px`,};
rootMargin
中:-${top || "0px"}
為吸頂距離,-${bottom || "0px"}
為吸底距離。一定要是負(fù)的,正數(shù)表示延伸到了視窗外的距離,負(fù)數(shù)表示距離視窗頂部或者底部的距離。
使用方式:
<AutoFixed // 距離頂部為 20px 吸頂 top="20px" // 占位高度,也就是 children 的高度 height="20px" // fixed狀態(tài)改變時(shí) onFixedChange={(isFixed) => { console.log(`isFixed: ` + isFixed); }} // fixed狀態(tài)需要添加的className fixedClassName="hello" // fixed狀態(tài)需要添加的style fixedStyle={{ color: "red" }}> <div>我是懸浮內(nèi)容,高度 20px, 距離頂部為 20px 吸頂 </div></AutoFixed>
實(shí)現(xiàn)效果:
可以看出 一直吸頂
、滾動(dòng)到設(shè)定位置吸頂
、 一直吸底
、滾動(dòng)到設(shè)定位置吸底
四個(gè)功能都可以正常工作。
滾動(dòng)到設(shè)定位置吸底
指的是,從底部向上滾動(dòng)的時(shí)候,這個(gè)功能就是為了在劃出屏幕區(qū)域的時(shí)候顯示在底部。
大家也可以打開 示例 自己去體驗(yàn)一下。
結(jié)語(yǔ)
這是之前在比較多的頁(yè)面會(huì)用到的一個(gè)功能點(diǎn),然后寫了幾次后,發(fā)現(xiàn)每次實(shí)現(xiàn)這個(gè)功能都有點(diǎn)復(fù)雜,于是封裝了 吸頂
組件,本次寫文章,就想著剛好可以完善一下,把 吸底
功能也開發(fā)出來,因?yàn)楹罄m(xù)也有用到過不少次。
以上就是React 實(shí)現(xiàn)具備吸頂和吸底功能組件實(shí)例的詳細(xì)內(nèi)容,更多關(guān)于React吸頂吸底功能的資料請(qǐng)關(guān)注其它相關(guān)文章!
