perf(movimento): vitrine e 3D mais leves, WebGL por software lembrado na sessão e suporte ao WebKit

This commit is contained in:
Evandro Fonseca Junior committed 2026-10-14 22:54:27 -03:00
1 parent 9fd1570426
commit 8903f3c57e
28 files changed
+854 -406

No files matched your search

+13 -19
View File
@@ -7,7 +7,7 @@ import {
useRef,
useState,
} from "react";
import { isLowPowerDevice, isSoftwareWebGL } from "@/lib/device";
import { isKnownSoftwareWebGL, isLowPowerDevice } from "@/lib/device";
import type { HeroFieldSceneProps } from "./HeroFieldScene";
/** A mesma condição do CSS do mergulho (globals.css): tela com altura para o conteúdo e sem movimento reduzido. */
@@ -44,21 +44,12 @@ const smooth = (a: number, b: number, x: number) => {
};
/**
* O aparelho abre um contexto WebGL2 de verdade, na GPU. Um WebGL por software (SwiftShader, llvmpipe, Microsoft
* Basic Render) abre, mas desenha a cena a poucos quadros por segundo na CPU: nesse caso vale o hero estático.
* O navegador tem a API do WebGL2? É só uma leitura: abrir um contexto de teste aqui, na hidratação, custava uma tarefa
* de 150 ms (500 ms por software). Se o WebGL2 abre de verdade, e se ele desenha na GPU, quem diz é a própria cena,
* no contexto que ela abre para desenhar (HeroFieldScene.createRenderer); o veredito "por software" fica anotado na
* sessão (isKnownSoftwareWebGL), e as páginas seguintes já nascem estáticas.
*/
function supportsWebGL2() {
if (typeof window.WebGL2RenderingContext === "undefined") return false;
try {
const context = document.createElement("canvas").getContext("webgl2");
if (!context) return false;
const software = isSoftwareWebGL(context);
context.getExtension("WEBGL_lose_context")?.loseContext();
return !software;
} catch {
return false;
}
}
const hasWebGL2 = () => typeof window.WebGL2RenderingContext !== "undefined";
/** Um intervalo ocioso do navegador (sem requestIdleCallback, um instante depois). */
function nextIdle() {
@@ -183,7 +174,9 @@ function trackVisibility(
* outros o hero é só o palco de 100svh com o fundo estático leve do CSS (.hero-glow), sem ouvinte de rolagem
* e sem baixar o Three.js. No aparelho capaz, a cena WebGL entra na primeira interação ou quando a página fica
* ociosa com folga, em passos separados (pacote, geometria, Canvas); a rolagem do hero conduz o mergulho
* (trackDive). Sem legendas sobre o 3D: o espaço da direita é das molduras.
* (trackDive). O desenho por software só se descobre no contexto que a cena abre (nunca num contexto de teste na
* hidratação): ela avisa por `onError` e o hero volta ao palco estático. Sem legendas sobre o 3D: o espaço da direita
* é das molduras.
*/
export default function HeroField() {
const rootRef = useRef<HTMLDivElement>(null);
@@ -202,9 +195,10 @@ export default function HeroField() {
if (isLowPowerDevice()) return;
const section = rootRef.current?.closest<HTMLElement>(".hero");
if (!section) return;
// WebGL2 só por software (ou nenhum): o hero estático de 100svh, já na hidratação, sem o bloco de rolagem do
// mergulho que depois seria desfeito com a página já rolada
if (!supportsWebGL2()) return;
// sem WebGL2, ou com o desenho por software já descoberto nesta sessão: o hero estático de 100svh, já na hidratação,
// sem o bloco de rolagem do mergulho. Num aparelho ainda não testado o mergulho liga já (o veredito sai do contexto
// da cena, ocioso, sem uma tarefa longa na hidratação); sendo software, a cena avisa e o hero volta a 100svh
if (!hasWebGL2() || isKnownSoftwareWebGL()) return;
let alive = true;
const stopDive = window.matchMedia?.(DIVE_QUERY).matches
? trackDive(section, progressRef, setDone)