Реакция Sticky DIV с относительным положением - Как добиться плавного изменения style.top/style.transform translateY при использовании относительного положения? - PullRequest
0 голосов
/ 29 февраля 2020

Итак, у меня есть следующий компонент React, который я назвал RelativeSticky:

import React, { useRef, useMemo, useLayoutEffect } from "react";
import { compose } from "js-utl";
import { classNames } from "react-js-utl/utils";
import { useWindowRef } from "react-js-utl/hooks";
import "./relative-sticky.css";

const getScrollY = element =>
  element === window ? element.scrollY : element.scrollTop;

const RelativeSticky = compose(React.memo)(function RelativeSticky({
  className = void 0,
  children,
  scrollElementRef = void 0,
  disable = false,
  topThresold = 0
} = {}) {
  const windowRef = useWindowRef();
  scrollElementRef =
    useMemo(() => scrollElementRef, [scrollElementRef]) || windowRef;
  const ref = useRef(null);
  const holderRef = useRef(null);
  const topCallback = useMemo(
    () => () => {
      if (!ref.current || !scrollElementRef.current || !holderRef.current) {
        return;
      }
      const { y } = ref.current.getBoundingClientRect();
      const scrollTop = getScrollY(scrollElementRef.current);
      const absTop = scrollTop + y;
      const topThresholdOffset =
        typeof topThresold === "function" ? topThresold() : topThresold;
      const top = Math.max(0, scrollTop - absTop + topThresholdOffset);
      holderRef.current.style.transform = `translateY(${disable ? 0 : top}px)`;
    },
    [scrollElementRef, topThresold, disable]
  );
  useLayoutEffect(() => {
    const el = scrollElementRef.current;
    el && el.addEventListener("scroll", topCallback);
    topCallback();
    return () => {
      el.removeEventListener("scroll", topCallback);
    };
  }, [scrollElementRef, topCallback]);

  return (
    <div className={classNames("relative-sticky", className)} ref={ref}>
      <div className="relative-sticky-holder" ref={holderRef}>
        {children}
      </div>
    </div>
  );
});
RelativeSticky.displayName = "RelativeSticky";
export default RelativeSticky;

, который я использую следующим образом:

import ReactDOM from "react-dom";
import React from "react";
import RelativeSticky from "./RelativeSticky";
import "./styles.css";

function Example() {
  return (
    <React.Fragment>
      <header>HEADER</header>
      <div className="padding" />
      <div className="container">
        {/* For the sticky div I cannot use "position: fixed;"
            because ".container" has "transform: translate(0, 0);"
            and I cannot change the style of ".container"
            because it is added by an external library I am using.
            Indeed, you can see that the ".fixed" element is not fixed even though
            it has "position: fixed;"...

            The only option would be to use a relative-sticky div,
            but when scrolling the translation of the top position of this
            sticky div is janky and jumps and not smooth
            (especially when scrolling fast)...
            */}
        <RelativeSticky topThresold={50 + 10}>
          <div className="sticky-element">STICKY</div>
        </RelativeSticky>
        <div className="fixed">FIXED</div>
      </div>
    </React.Fragment>
  );
}

ReactDOM.render(<Example />, document.getElementById("root"));

Вы можете проверить этот Codesandbox, чтобы увидеть это в действии: https://codesandbox.io/s/react-example-vc0x2

Проблема в том, что «липкость» не гладкая, а затяжная. Изменение свойства style.transform = translateY() в приемнике событий scroll приводит к тому, что div делает небольшие переходы при прокрутке.

Есть ли способ исправить это поведение и сохранить плавность перевода даже при быстрой прокрутке?

Спасибо за внимание.

ПРИМЕЧАНИЕ. Вы можете удивиться, почему я не использовал position: fixed;. Дело в том, что position: fixed не работает, когда контейнер фиксированного div имеет transform: translate(0, 0), как описано здесь -> Фиксированные позиции не работают при использовании -webkit-transform , и я не могу избежать это transform: translate(0, 0) параметр container, потому что он добавляется библиотекой, которую я использую, поэтому я думаю, что единственный вариант - использовать относительное позиционирование ...

...