ahooks源码分析-useScroll

756 阅读3分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第10天,点击查看活动详情

今天开始一起学习分享 ahooks 的源码

ahooks是阿里巴巴出品的一套高质量可靠的 React Hooks 库

今天分享 第10个 hooks-useScroll

useScroll

首先看下 useScroll 的作用是什么

useScroll 的作用是监听元素的滚动位置。

接下来 看下 API

API

const position = useScroll(target, shouldUpdate);

Params

一共两个参数

第1个参数是目标的元素,可以传入dom节点,也可以传入 ref

第2个参数是boolean值,表示控制是否更新滚动信息

参数说明类型默认值
targetDOM 节点或者 refElement | Document | (() => Element) | MutableRefObject<Element>document
shouldUpdate控制是否更新滚动信息({ top: number, left: number }) => boolean-

Result

结果返回一个position,表示滚动容器当前的滚动位置

参数说明类型
position滚动容器当前的滚动位置{ left: number, top: number } | undefined

接下来 看下 用法

基本用法

尝试滚动一下文字内容。

import React, { useRef } from 'react';
import { useScroll } from 'ahooks';

export default () => {
  const ref = useRef(null);
  const scroll = useScroll(ref);
  return (
    <>
      <p>{JSON.stringify(scroll)}</p>
      <div
        style={{
          height: '160px',
          width: '160px',
          border: 'solid 1px #000',
          overflow: 'scroll',
          whiteSpace: 'nowrap',
          fontSize: '32px',
        }}
        ref={ref}
      >
        <div>
          Lorem ipsum dolor sit amet, consectetur adipisicing elit. A aspernatur atque, debitis ex
          excepturi explicabo iste iure labore molestiae neque optio perspiciatis
        </div>
        <div>
          Aspernatur cupiditate, deleniti id incidunt mollitia omnis! A aspernatur assumenda
          consequuntur culpa cumque dignissimos enim eos, et fugit natus nemo nesciunt
        </div>
        <div>
          Alias aut deserunt expedita, inventore maiores minima officia porro rem. Accusamus ducimus
          magni modi mollitia nihil nisi provident
        </div>
        <div>
          Alias aut autem consequuntur doloremque esse facilis id molestiae neque officia placeat,
          quia quisquam repellendus reprehenderit.
        </div>
        <div>
          Adipisci blanditiis facere nam perspiciatis sit soluta ullam! Architecto aut blanditiis,
          consectetur corporis cum deserunt distinctio dolore eius est exercitationem
        </div>
        <div>Ab aliquid asperiores assumenda corporis cumque dolorum expedita</div>
        <div>
          Culpa cumque eveniet natus totam! Adipisci, animi at commodi delectus distinctio dolore
          earum, eum expedita facilis
        </div>
        <div>
          Quod sit, temporibus! Amet animi fugit officiis perspiciatis, quis unde. Cumque
          dignissimos distinctio, dolor eaque est fugit nisi non pariatur porro possimus, quas quasi
        </div>
      </div>
    </>
  );
};

当滚动文字的时候 top值会实时更新

image.png

监测整页的滚动

尝试滚动一下页面。当滚动页面的时候,会监听整个页面的滚动

import React from 'react';
import { useScroll } from 'ahooks';

export default () => {
  const scroll = useScroll(document);
  return (
    <div>
      <div>{JSON.stringify(scroll)}</div>
    </div>
  );
};

image.png

控制滚动状态的监听

在垂直方向 100px 到 200px 的滚动范围内监听

image.png

import React, { useRef } from 'react';
import { useScroll } from 'ahooks';

export default () => {
  const ref = useRef(null);

  const scroll = useScroll(ref, (val) => val.top > 100 && val.top < 200);

  return (
    <>
      <p>{JSON.stringify(scroll)}</p>
      <div
        style={{
          height: '160px',
          width: '160px',
          border: 'solid 1px #000',
          overflow: 'scroll',
          whiteSpace: 'nowrap',
          fontSize: '36px',
        }}
        ref={ref}
      >
        <div>
          Lorem ipsum dolor sit amet, consectetur adipisicing elit. A aspernatur atque, debitis ex
          excepturi explicabo iste iure labore molestiae neque optio perspiciatis
        </div>
        <div>
          Aspernatur cupiditate, deleniti id incidunt mollitia omnis! A aspernatur assumenda
          consequuntur culpa cumque dignissimos enim eos, et fugit natus nemo nesciunt
        </div>
        <div>
          Alias aut deserunt expedita, inventore maiores minima officia porro rem. Accusamus ducimus
          magni modi mollitia nihil nisi provident
        </div>
        <div>
          Alias aut autem consequuntur doloremque esse facilis id molestiae neque officia placeat,
          quia quisquam repellendus reprehenderit.
        </div>
        <div>
          Adipisci blanditiis facere nam perspiciatis sit soluta ullam! Architecto aut blanditiis,
          consectetur corporis cum deserunt distinctio dolore eius est exercitationem
        </div>
        <div>Ab aliquid asperiores assumenda corporis cumque dolorum expedita</div>
        <div>
          Culpa cumque eveniet natus totam! Adipisci, animi at commodi delectus distinctio dolore
          earum, eum expedita facilis
        </div>
        <div>
          Quod sit, temporibus! Amet animi fugit officiis perspiciatis, quis unde. Cumque
          dignissimos distinctio, dolor eaque est fugit nisi non pariatur porro possimus, quas quasi
        </div>
      </div>
    </>
  );
};

接下来一起看下 源码

源码

1.首先定义 目标元素 target的类型

target?: Target,

export type Target = BasicTarget<Element | Document>;

2.定义 shouldUpdate 的类型

shouldUpdate: ScrollListenController = () => true,

export type ScrollListenController = (val: Position) => boolean;

3.通过 state 来 初始化 当前位置信息

const [position, setPosition] = useRafState<Position>();

4.使用 useLatest来获取当前最新的shouldUpdate函数值

const shouldUpdateRef = useLatest(shouldUpdate);

5.获取当前的目标元素,如果元素不存在,则直接 return

const el = getTargetElement(target, document);
      if (!el) {
        return;
      }

6.使用addEventListener来监听元素滚动,使用removeEventListener来移除元素滚动监听

el.addEventListener('scroll', updatePosition);
      return () => {
        el.removeEventListener('scroll', updatePosition);
      };

7.接下来在看下 执行的函数updatePosition。首先判断 目标元素是否是document,如果不是document,就将当前元素的left和top赋值给newPosition。如果是document,并且是document.scrollingElement,则将document.scrollingElement.scrollLeft和document.scrollingElement.scrollTop赋值给newPosition,否则在window.pageXOffset、document.documentElement.scrollTop、document.body.scrollTop最大值赋值给newPosition。最后通过setPosition更新newPosition。

useEffectWithTarget(
    () => {
      const el = getTargetElement(target, document);
      if (!el) {
        return;
      }
      const updatePosition = () => {
        let newPosition: Position;
        if (el === document) {
          if (document.scrollingElement) {
            newPosition = {
              left: document.scrollingElement.scrollLeft,
              top: document.scrollingElement.scrollTop,
            };
          } else {
            // When in quirks mode, the scrollingElement attribute returns the HTML body element if it exists and is potentially scrollable, otherwise it returns null.
            // https://developer.mozilla.org/zh-CN/docs/Web/API/Document/scrollingElement
            // https://stackoverflow.com/questions/28633221/document-body-scrolltop-firefox-returns-0-only-js
            newPosition = {
              left: Math.max(
                window.pageXOffset,
                document.documentElement.scrollLeft,
                document.body.scrollLeft,
              ),
              top: Math.max(
                window.pageYOffset,
                document.documentElement.scrollTop,
                document.body.scrollTop,
              ),
            };
          }
        } else {
          newPosition = {
            left: (el as Element).scrollLeft,
            top: (el as Element).scrollTop,
          };
        }
        if (shouldUpdateRef.current(newPosition)) {
          setPosition(newPosition);
        }
      };

      updatePosition();

      el.addEventListener('scroll', updatePosition);
      return () => {
        el.removeEventListener('scroll', updatePosition);
      };
    },
    [],
    target,
  );

8.最后返回position

  return position;

其他hooks

useLatest

useEventListener

useClickAway

useDocumentVisibility

useTitle