Projetos sobem sobre o palco do hero e o celular tem hero de uma tela sem mergulho; passagens com animation-timeline em CSS, sem GSAP nem motion; troca da vitrine sem texto duplicado; moldura de celular só com pôster e scrub reencodado em 960 px (9,4 para 5,2 MB); 3D monta na primeira interação e para quando parado; transição entre páginas do mesmo segmento; foco em anel duplo; texto de 15 px no celular; código morto removido.
55 lines
1.6 KiB
TypeScript
55 lines
1.6 KiB
TypeScript
"use client";
|
|
|
|
import { type RefObject, useEffect, useState } from "react";
|
|
|
|
interface InViewOptions {
|
|
/** Fração do elemento (0 a 1) que precisa estar à vista; em elementos altos, vale também essa fração da altura da janela. */
|
|
amount?: number;
|
|
/** Verdadeiro (padrão): depois de aparecer uma vez, continua verdadeiro. Falso: acompanha a entrada e a saída. */
|
|
once?: boolean;
|
|
}
|
|
|
|
function isShown(entry: IntersectionObserverEntry, amount: number) {
|
|
return (
|
|
entry.isIntersecting &&
|
|
(entry.intersectionRatio >= amount ||
|
|
entry.intersectionRect.height >= window.innerHeight * amount)
|
|
);
|
|
}
|
|
|
|
/**
|
|
* O elemento está à vista? Usa IntersectionObserver (sem ouvir a rolagem). Sem IntersectionObserver devolve
|
|
* verdadeiro logo na montagem: o conteúdo nunca fica escondido por falta do recurso.
|
|
*/
|
|
export function useInView(
|
|
ref: RefObject<Element | null>,
|
|
{ amount = 0.2, once = true }: InViewOptions = {},
|
|
): boolean {
|
|
const [inView, setInView] = useState(false);
|
|
|
|
useEffect(() => {
|
|
const element = ref.current;
|
|
if (!element) return;
|
|
if (typeof IntersectionObserver === "undefined") {
|
|
setInView(true);
|
|
return;
|
|
}
|
|
const observer = new IntersectionObserver(
|
|
(entries) => {
|
|
for (const entry of entries) {
|
|
const shown = isShown(entry, amount);
|
|
if (shown) {
|
|
setInView(true);
|
|
if (once) observer.disconnect();
|
|
} else if (!once) setInView(false);
|
|
}
|
|
},
|
|
{ threshold: [0, amount] },
|
|
);
|
|
observer.observe(element);
|
|
return () => observer.disconnect();
|
|
}, [ref, amount, once]);
|
|
|
|
return inView;
|
|
}
|