Итак, у меня есть следующий компонент 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
, потому что он добавляется библиотекой, которую я использую, поэтому я думаю, что единственный вариант - использовать относительное позиционирование ...