React如何实现具备吸顶和吸底功能组件

其他教程   发布日期:2023年06月30日   浏览次数:313

本篇内容介绍了“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}
    >
      <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
    传入监听位置控制参数。
  • 根据

    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如何实现具备吸顶和吸底功能组件的资料请关注九品源码其它相关文章!