Files

117 lines
4.5 KiB
TypeScript

/**
* Leituras do aparelho que decidem o que a página carrega e anima: ponteiro de toque, pouca CPU ou memória,
* "economizar dados" e largura de celular. Tudo roda só no navegador (no servidor, devolve o valor mais leve).
*/
/** Abaixo disto (px) a página usa as versões de celular dos vídeos e capas. */
export const MOBILE_MAX_WIDTH = 767;
export const MOBILE_QUERY = `(max-width: ${MOBILE_MAX_WIDTH}px)`;
type NavigatorHints = Navigator & {
deviceMemory?: number;
connection?: { saveData?: boolean };
};
function matches(query: string) {
return (
typeof window !== "undefined" &&
typeof window.matchMedia === "function" &&
window.matchMedia(query).matches
);
}
/** Ponteiro principal impreciso (dedo): sem rolagem suavizada nem cena 3D. */
export const isCoarsePointer = () => matches("(pointer: coarse)");
export const prefersReducedMotion = () =>
matches("(prefers-reduced-motion: reduce)");
/** Tela de celular: abaixo de 768 px. */
export const isMobileViewport = () => matches(MOBILE_QUERY);
/** "Economizar dados" ligado: só imagens, nenhum vídeo é baixado sozinho. */
export function isSaveData() {
if (typeof navigator === "undefined") return false;
return (navigator as NavigatorHints).connection?.saveData === true;
}
/**
* Aparelho que não deve montar WebGL: toque, 4 núcleos ou menos, 4 GB de memória ou menos, economia de dados
* ou movimento reduzido. O que não é informado pelo navegador (Safari, Firefox) não conta contra o aparelho.
*/
export function isLowPowerDevice() {
if (typeof navigator === "undefined") return true;
const hints = navigator as NavigatorHints;
return (
isCoarsePointer() ||
prefersReducedMotion() ||
isSaveData() ||
(typeof hints.hardwareConcurrency === "number" &&
hints.hardwareConcurrency <= 4) ||
(typeof hints.deviceMemory === "number" && hints.deviceMemory <= 4)
);
}
/** Nomes que os navegadores dão a um renderizador de WebGL que roda na CPU: SwiftShader (Chrome), llvmpipe (Mesa) e o Microsoft Basic Render Driver. */
const SOFTWARE_RENDERER = /swiftshader|llvmpipe|basic render/i;
/** O nome do renderizador (`UNMASKED_RENDERER_WEBGL`) é de um desenho por software? */
export const isSoftwareRenderer = (name: string) =>
SOFTWARE_RENDERER.test(name);
/**
* O contexto WebGL desenha na CPU (SwiftShader, llvmpipe, Basic Render)? Abre normalmente, mas a cena 3D sairia a
* poucos quadros por segundo. Sem a extensão `WEBGL_debug_renderer_info` (Firefox, Safari) não dá para saber, e o
* aparelho não conta contra si.
*/
export function isSoftwareWebGL(
context: WebGLRenderingContext | WebGL2RenderingContext,
) {
const info = context.getExtension("WEBGL_debug_renderer_info");
if (!info) return false;
const renderer: unknown = context.getParameter(info.UNMASKED_RENDERER_WEBGL);
return typeof renderer === "string" && isSoftwareRenderer(renderer);
}
const SOFTWARE_WEBGL_KEY = "webgl-software";
/**
* O WebGL deste aparelho já se mostrou por software nesta sessão (aba)? Abrir um contexto só para descobrir custa uma
* tarefa longa (150 ms na GPU, 500 ms ou mais por software), então quem abre o contexto de verdade anota o veredito
* aqui e as páginas seguintes da sessão já nascem estáticas. `sessionStorage` bloqueado vale como "não sei".
*/
export function isKnownSoftwareWebGL() {
try {
return sessionStorage.getItem(SOFTWARE_WEBGL_KEY) === "1";
} catch {
return false;
}
}
/** Anota na sessão que o WebGL deste aparelho desenha por software (ver `isKnownSoftwareWebGL`). */
export function rememberSoftwareWebGL() {
try {
sessionStorage.setItem(SOFTWARE_WEBGL_KEY, "1");
} catch {
// armazenamento bloqueado (modo privado, política do navegador): sem anotação, a próxima página testa de novo
}
}
/**
* O navegador é WebKit (Safari no Mac, no iPhone e no iPad; no iOS todo navegador é WebKit)? É o único que deixa em
* branco um vídeo que ainda não tocou uma vez, mesmo com o quadro já decodificado: só nele vale tocar e pausar o
* vídeo da vitrine para ele aparecer, e tocar faz o arquivo inteiro ser baixado (ver PinnedReel).
*/
export function isWebKit() {
if (typeof navigator === "undefined") return false;
const { userAgent, platform, maxTouchPoints } = navigator;
// o iPad com "site para computador" se apresenta como Mac, mas é o único Mac com toque
const iOS =
/iP(hone|ad|od)/.test(userAgent) ||
(platform === "MacIntel" && maxTouchPoints > 1);
const safari =
/Safari\//.test(userAgent) &&
!/Chrome|Chromium|Edg|OPR|Android/.test(userAgent);
return iOS || safari;
}