59 lines
1.6 KiB
JavaScript
59 lines
1.6 KiB
JavaScript
import React, { useState, useRef, useEffect, useCallback } from "react";
|
|
import "./MeteorRegion.css";
|
|
|
|
const MeteorRegion = ({ children, className = "", maxHeight }) => {
|
|
const [scrollProgress, setScrollProgress] = useState(0);
|
|
const [isVisible, setIsVisible] = useState(false);
|
|
const containerRef = useRef(null);
|
|
|
|
const updateScroll = useCallback(() => {
|
|
const el = containerRef.current;
|
|
if (!el) return;
|
|
|
|
const { scrollTop, scrollHeight, clientHeight } = el;
|
|
const progress = scrollTop / (scrollHeight - clientHeight);
|
|
|
|
setScrollProgress(isNaN(progress) ? 0 : progress);
|
|
setIsVisible(scrollHeight > clientHeight);
|
|
}, []);
|
|
|
|
useEffect(() => {
|
|
const el = containerRef.current;
|
|
if (!el) return;
|
|
|
|
const resizeObserver = new ResizeObserver(updateScroll);
|
|
resizeObserver.observe(el);
|
|
el.addEventListener("scroll", updateScroll);
|
|
|
|
updateScroll();
|
|
|
|
return () => {
|
|
resizeObserver.disconnect();
|
|
el.removeEventListener("scroll", updateScroll);
|
|
};
|
|
}, [updateScroll]);
|
|
|
|
return (
|
|
<div className={`meteor-region-wrapper ${className}`} style={{ maxHeight }}>
|
|
<div className="meteor-region-content" ref={containerRef}>
|
|
{children}
|
|
</div>
|
|
|
|
{isVisible && (
|
|
<div className="meteor-track-local">
|
|
<div
|
|
className="meteor-slider-local"
|
|
style={{
|
|
transform: `translateY(${scrollProgress * (containerRef.current?.clientHeight - 70)}px)`,
|
|
}}
|
|
>
|
|
<div className="meteor-glow-local" />
|
|
</div>
|
|
</div>
|
|
)}
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default MeteorRegion;
|