RouteReveal layout component applies in-view on every route change. useReveal hook falls back to immediate rAF when element is already visible. bestall/page.tsx: added missing useReveal ref. katalog/[id]/page.tsx: DatasetView uses useReveal directly. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
50 lines
1.4 KiB
TypeScript
50 lines
1.4 KiB
TypeScript
"use client";
|
|
|
|
import { useEffect, useRef } from "react";
|
|
|
|
/**
|
|
* Wire a section so its descendants carrying `.reveal` (+ optional
|
|
* `.reveal-left|-right|-scale|-fade` and `.delay-N`) animate in when the
|
|
* section scrolls into view. Matches the keyframes in globals.css.
|
|
*
|
|
* If the section is already visible on mount (e.g. top-of-page navigation),
|
|
* reveals are applied immediately via rAF instead of waiting for scroll.
|
|
*/
|
|
export function useReveal<T extends HTMLElement = HTMLElement>() {
|
|
const ref = useRef<T>(null);
|
|
useEffect(() => {
|
|
const el = ref.current;
|
|
if (!el) return;
|
|
|
|
const applyReveal = () => {
|
|
el
|
|
.querySelectorAll(
|
|
".reveal, .reveal-left, .reveal-right, .reveal-scale, .reveal-fade",
|
|
)
|
|
.forEach((n) => n.classList.add("in-view"));
|
|
};
|
|
|
|
// If the element's top edge is already within the viewport, apply immediately.
|
|
const rect = el.getBoundingClientRect();
|
|
if (rect.top < window.innerHeight * 0.9) {
|
|
requestAnimationFrame(applyReveal);
|
|
return;
|
|
}
|
|
|
|
// Otherwise use IntersectionObserver for scroll-triggered reveal.
|
|
const io = new IntersectionObserver(
|
|
(entries) =>
|
|
entries.forEach((e) => {
|
|
if (e.isIntersecting) {
|
|
applyReveal();
|
|
io.unobserve(el);
|
|
}
|
|
}),
|
|
{ threshold: 0.15, rootMargin: "0px 0px -10% 0px" },
|
|
);
|
|
io.observe(el);
|
|
return () => io.disconnect();
|
|
}, []);
|
|
return ref;
|
|
}
|