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,用于支持默认就要一直吸顶或者吸底的情况,需要配合 topbottom 来使用。
  • 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吸顶吸底功能的资料请关注我们其它相关文章!

(0)

相关推荐

  • react-router 重新加回跳转拦截功能详解

    目录 前言 事件经过 v6 之前的版本如何拦截 v6.7.0+ 如何拦截 吐槽 前言 路由的跳转拦截,在一些表单页中尤为常见.场景:用户输入了一些信息后但未提交,为了防止用户误点击某个跳转链接导致所填的表单信息丢失,跳转之前会弹出一个提示,如 “信息未提交,请确认是否离开” 等字样. 事件经过 在 react-router v6 之前有提供一个 <Prompt /> 组件来拦截路由的跳转.而在 v6 中却不支持此功能了!社区一片哀嚎,经查在 v6 实验阶段是有此功能的,但在 v6.0.0 be

  • react echarts tree树图搜索展开功能示例详解

    目录 前言 最终效果 版本信息 核心功能: 关键思路: 附上代码 数据data.js 功能: TreeUtils 总结: 前言 umi+antd-admin 框架中使用类组件+antd结合echarts完成树图数据展示和搜索展开功能 最终效果 版本信息 "antd": "3.24.2", "umi": "^2.7.7", "echarts": "^4.4.0", "echart

  • React Native 中实现倒计时功能

    目录 正文 首次实现 最终实现 示例 正文 在 React Native,该如何实现一个倒计时功能呢? 首次实现 表面看来很简单,譬如像下面这样: const timer = useRef<ReturnType<typeof setInterval> | null>(null) const [count, setCount] = useState(0) const start = () => { setCount(10) timer.current = setInterval

  • reactive readonly嵌套对象转换功能实现详解

    目录 一.单元测试 reactive readonly 二.代码实现 一.单元测试 reactive // src/reactivity/tests/reactive.spec.ts it('nested reactive', () => { const original = { nested: { foo: 1 }, array: [{ bar: 2 }] }; const observed = reactive(original); expect(isReactive(observed.ne

  • React 实现具备吸顶和吸底功能组件实例

    目录 背景 实现 结语 背景 现在手机应用经常有这样一个场景: 页面上有一个导航,导航位置在页面中间位置,当页面顶部滚动到导航位置时,导航自动吸顶,页面继续往下滚动时,它就一直在页面视窗顶部显示,当往上滚动时,经过最初位置时,导航自动复原,不再吸顶. 效果就如京东超市首页的导航栏一样: 下面我们就来具体实现这样一个 React 组件,实现后还会再扩展延伸一下 吸底 功能,因为 吸底 场景也不少. 具体要求: 需要可以设置是 吸顶 还是 吸底. 吸顶 可以设置距离视窗顶部的位置,吸顶 可以设置距离

  • 微信小程序实现吸顶效果

    最开始的时候,在小程序中实现吸顶效果,开发工具看起来还挺好的,但是在真机上就会有问题了. 原因是我不停的去 setData 会导致操作反馈延迟严重,无法及时将操作处理结果及时传递到视图层. 后面就对代码进行了调整,避免不停的去setData 效果图 吸顶前 吸顶后 代码部分 wxml <view style="width: 90%; height: 300rpx; background: #f0f0f0; margin: 30rpx auto;"></view>

  • 微信小程序实现简单的吸顶效果

    本文实例为大家分享了微信小程序实现简单吸顶效果的具体代码,供大家参考,具体内容如下 需求:进入页面后首先看到banner布局,然后是tab切换栏以及页面内容,当页面滚动到一定距离后,tab切换栏始终固定在顶部 wxml部分代码如下: <!--pages/test/test.wxml--> <view style="padding-bottom:30rpx;position:fixed;top:0;width:100%;background:#2ea7e0;">

  • 浅谈react.js中实现tab吸顶效果的问题

    在react项目开发中有一个需求是,页面滚动到tab所在位置时,tab要固定在顶部. 实现的思路其实很简单,就是判断当滚动距离scrollTop大于tab距离页面顶部距离offsetTop时,将tab的position变为fixed. 在react中,我在state中设置一个navTop属性,切换这个属性的值为true或者false,然后tab标签使用classnames()这个方法来利用navTop的值添加类名fixed. 一开始我是这样写的: import cs from 'classnam

  • react-native滑动吸顶效果的实现过程

    前言 最近公司开发方向偏向移动端,于是就被调去做RN(react-native),体验还不错,当前有个需求是首页中间吸顶的效果,虽然已经很久没写样式了,不过这种常见样式应该是so-easy,没成想翻车了,网上搜索换了几个方案都不行,最后去github上复制封装好的库来实现,现在把翻车过程记录下来. 需求效果 翻车过程 第一种方案 失败 一开始的思路是这样的,大众思路,我们需要监听页面的滚动状态,当页面滚动到要吸顶元素所处的位置的时候,我们设置它为固定定位,不过很遗憾,RN对于position属性

  • Android进阶NestedScroll嵌套滑动机制实现吸顶效果详解

    目录 引言 1 自定义滑动布局,实现吸顶效果 1.1 滑动容器实现 1.2 嵌套滑动机制完成交互优化 1.2.1 NestedScrollingParent接口和NestedScrollingChild接口 1.2.2 预滚动阶段实现 1.2.3 滚动阶段实现 1.2.4 滚动结束 引言 在上一篇文章Android进阶宝典 -- 事件冲突怎么解决?先从Android事件分发机制开始说起中,我们详细地介绍了Android事件分发机制,其实只要页面结构复杂,联动众多就会产生事件冲突,处理不得当就是b

  • android中RecyclerView悬浮吸顶效果

    MultiType-Adapter打造悬浮吸顶效果 注:当前版本只适合配合RecyclerView快速打造一款 展示UI 悬浮吸顶效果,如 通讯录效果,由于实现机制的原因,暂时不支持触摸事件. MultiType-Adapter介绍地址:MultiType-Adapter 是一款轻量级支持多数据类型的 RecyclerView 适配器; 使用简单,完全解耦; 悬浮吸顶效果 ```groovy // root build.gradle repositories { jcenter() maven

  • RecyclerVIew实现悬浮吸顶效果

    RecyclerVIew实现悬浮吸顶效果图 这里写图片描述 主页面布局 <?xml version="1.0" encoding="utf-8"?> <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height=&

  • vue实现某元素吸顶或固定位置显示(监听滚动事件)

    最近写了一个VUE的web app项目,需要实现某个部位吸顶的效果.即,页面往上滑动,刚好到达该部位时,该部分,固定在顶部显示. 1.监听滚动事件 利用VUE写一个在控制台打印当前的scrollTop, 首先,在mounted钩子中给window添加一个滚动滚动监听事件, mounted () { window.addEventListener('scroll', this.handleScroll) }, 然后在方法中,添加这个handleScroll方法 handleScroll () {

  • vue中多路由表头吸顶实现的几种布局方式

    vue项目多路由表头吸顶实现的几种布局方式 因为项目较大,有五个界面,每个界面有四个子组件,每个子组件都有一个table表格,需求是每个界面的每个table滚动到顶端表头吸顶,所以尝试用vux做这种需求, 一.先聊js. A.首先在vux可以这样设置. 1.在state文件中设置状态. techo:{ partsFixed:false, repairFixed:false, mateFixed:false, outRepairFixed:false }//吸顶状态 2.在action中commi

随机推荐