跟着 ahooks 写 Hook 之 useScroll

346 阅读2分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第17天,点击查看活动详情

💻文档地址:ahooks.js.org/zh-CN/

👨‍💻github地址:github.com/alibaba/hoo…


document.scrollingElementDocument 的只读属性,返回滚动文档的 Element 对象的引用。 在标准模式下,这是文档的根元素。

Element.scrollLeft:属性可以读取或设置元素滚动条到元素左边的距离。

Element.scrollTop:属性可以获取或设置一个元素的内容垂直滚动的像素数。

window.pageYOffsetpageYOffset 属性是 scrollY 属性的别名。

监听当前元素的滚动事件,判断当前元素不是 document 的时候,获取该元素的 scrollLeftscrollTop

function useScroll(
  target?: Target,
): Position | undefined {
  const [position, setPosition] = useRafState<Position>();

  useEffect(() => {
    const el = getTargetElement(target, document);
    if (!el) {
      return;
    }
    const updatePosition = () => {
      let newPosition: Position;
      if (el === document) {
        // ...
      } else {
        newPosition = {
          left: (el as Element).scrollLeft,
          top: (el as Element).scrollTop,
        };
      }
    };

    updatePosition();

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

  return position;
}

export default useScroll;

scrollingElement 返回滚动文档的 Element 对象的引用。

在标准模式下,这是文档的根元素, document.documentElement。当在怪异模式下, scrollingElement 属性返回 HTML body 元素(若不存在返回 null )。

怪异模式和标准模式

目前浏览器的排版引擎使用三种模式:怪异模式(Quirks mode)、接近标准模式(Almost standards mode)、以及标准模式(Standards mode)。

浏览器如何决定使用哪个模式

浏览器使用文件开头的 DOCTYPE 来决定用怪异模式处理或标准模式处理。<!DOCTYPE html>,是所有可用的 DOCTYPE 之中最简单的,也是 HTML5 所推荐的。当今的浏览器都会对这个 DOCTYPE 使用标准模式,就算是早已过时的 Internet Explorer 6 也一样。

分成了两种情况进行赋值。

if (document.scrollingElement) {
  newPosition = {
    left: document.scrollingElement.scrollLeft,
    top: document.scrollingElement.scrollTop,
  };
} else {
  newPosition = {
    left: Math.max(
      window.pageYOffset,
      document.documentElement.scrollTop,
      document.body.scrollTop,
    ),
    top: Math.max(
      window.pageXOffset,
      document.documentElement.scrollLeft,
      document.body.scrollLeft,
    ),
  };
}

动态控制,监听滚动的状态。shouldUpdate 是一个函数,返回一个 bool 值。shouldUpdateRef.current 是函数,当前的 position 如果满足条件,则继续更新坐标。

const shouldUpdateRef = useLatest(shouldUpdate);

const updatePosition = () => {
  let newPosition: Position;
  // ...

  if (shouldUpdateRef.current(newPosition)) {
    setPosition(newPosition);
  }
};