Отслеживание изменения offsetTop в React

Мне необходимо отслеживать изменения высоты (width), ширины (height) и положения (offsetTop) элемента на странице. При изменении этих параметров элемента DOM-дерева, необходимо вызывать onChange. Для width/height работает вот этот код:

import React, { useEffect, useRef } from "react";

const ElementChange = React.memo(({ children, onChange }) => {
  const changeRef = useRef();
  const observer = useRef();

  useEffect(() => {
    if (observer.current) observer.current.disconnect();

    if (changeRef.current && !!onChange) {
      const element = changeRef.current;

      observer.current = new ResizeObserver(() => {
        onChange({ width: element.clientWidth, height: element.clientHeight });
      });

      observer.current.observe(element);
    }

    return () => {
      if (observer.current) observer.current.disconnect();
    };
  }, [changeRef, onChange]);

  if (React.Children.toArray(children) > 1)
    return null;

  return React.cloneElement(children, { ref: changeRef });
});

export default ElementChange;

Как отслеживать изменения offsetTop?

PS Сторонние библиотеки не рассматриваются.


Ответы (1 шт):

Автор решения: posix

Пока остановился на таком варианте:

import React, { useEffect, useRef } from "react";

const ElementChange = React.memo(
  ({ children, delay, onChange, getData, ...props }) => {
    const changeRef = useRef();
    const interval = useRef();
    const data = useRef();

    useEffect(() => {
      clearInterval(interval.current);

      if (changeRef.current && !!onChange && !!getData) {
        interval.current = setInterval(() => {
          const newData = getData(changeRef.current);

          if (JSON.stringify(data.current) !== JSON.stringify(newData)) {
            data.current = newData;
            if (data.current) onChange(data.current);
          }
        }, delay);
      }

      return () => clearInterval(interval.current);
    }, [changeRef, onChange, getData]);

    if (React.Children.toArray(children) > 1) return null;

    return React.cloneElement(children, { ref: changeRef, ...props });
  }
);

export default ElementChange;

ElementChange.defaultProps = {
  delay: 100,
};

Пример использования:

<ElementChange
  getData={(element) => {
    const { offsetTop, clientWidth, clientHeight } = element;
    return {
      top: offsetTop,
      width: clientWidth,
      height: clientHeight
    };
  }}
  onChange={(s) => console.log(s)}
>
  <Item />
</ElementChange>

В Item должен быть forwardRef:

import React from "react";

const Item = React.memo(
  React.forwardRef((children, ref, ...props) => {
    return (
      <div ref={ref} {...props}>
        {children}
      </div>
    );
  })
);

export default Item;

Вариант конечно не слишком элегантный, да и точность упирается в delay, но если у кого-нибудь есть идеи интереснее, то буду рад услышать.

→ Ссылка