Passagens com fade e 32 px, cena anterior termina em opacidade 0, sem visibility nem clip-path. 3D do hero como fundo ambiente, sob demanda e desligado em toque, aparelho fraco, saveData e movimento reduzido. Lenis fora do toque e salto direto em âncoras distantes. Vitrine só de clientes, até 3 capítulos, fixa só no desktop e carrossel no celular; vídeo só do projeto em cena com preload metadata. Rótulos mono a partir de 12 px e limão só no contato.
37 lines
1004 B
TypeScript
37 lines
1004 B
TypeScript
"use client";
|
|
|
|
import { useThree } from "@react-three/fiber";
|
|
import { useEffect } from "react";
|
|
|
|
/**
|
|
* Batida das cenas WebGL em `frameloop="demand"`: enquanto `active`, pede um quadro a no máximo `fps` por
|
|
* segundo (fundo ambiente, sem gastar 60 quadros por segundo); com `active` falso, nenhum quadro é desenhado.
|
|
* Quem precisa de mais (entrada, rolagem, cursor) pede o próximo quadro com `invalidate()` dentro do useFrame.
|
|
*/
|
|
export default function FramePulse({
|
|
active,
|
|
fps = 30,
|
|
}: {
|
|
active: boolean;
|
|
fps?: number;
|
|
}) {
|
|
const invalidate = useThree((state) => state.invalidate);
|
|
|
|
useEffect(() => {
|
|
if (!active) return;
|
|
const gap = 1000 / fps - 2;
|
|
let id = 0;
|
|
let last = 0;
|
|
const beat = (now: number) => {
|
|
id = requestAnimationFrame(beat);
|
|
if (now - last < gap) return;
|
|
last = now;
|
|
invalidate();
|
|
};
|
|
id = requestAnimationFrame(beat);
|
|
return () => cancelAnimationFrame(id);
|
|
}, [active, fps, invalidate]);
|
|
|
|
return null;
|
|
}
|