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

+40 -23
View File
@@ -10,11 +10,13 @@ import {
useRef,
useState,
} from "react";
import { isWebKit } from "@/lib/device";
import {
chapterFraction,
chapterIndexAt,
chapterIndexAtScroll,
chapterScrollFraction,
getScrubSource,
getShowcaseAssets,
videoFractionAtScroll,
} from "@/lib/project-showcase";
import { BrowserFrame, displayUrl } from "./BrowserFrame";
import { PhoneFrame } from "./PhoneFrame";
@@ -23,8 +25,9 @@ import { type ShowcaseItem, useShowcaseOpen } from "./ShowcaseProvider";
import {
CAPTION_OUT_MS,
easeToward,
localAtProgress,
locateInReel,
REEL_HOLD,
reelHolds,
reelOffset,
videoProgress,
} from "./showcase-math";
@@ -69,9 +72,13 @@ function seekVideo(video: HTMLVideoElement | null, fraction: number) {
video.currentTime = target;
}
/** Alguns navegadores móveis só desenham o primeiro quadro de um vídeo que já tocou uma vez. */
/**
* O WebKit (Safari, iPhone, iPad) só desenha o primeiro quadro de um vídeo que já tocou uma vez. Só ele toca e pausa:
* nos outros navegadores tocar faz o arquivo inteiro ser baixado (os 4 vídeos eram 3,2 MB por visita, contra 63 KB
* quando só as buscas da rolagem pedem os trechos que o visitante vê).
*/
function prime(video: HTMLVideoElement) {
if (video.dataset.primed) return;
if (video.dataset.primed || !isWebKit()) return;
video.dataset.primed = "true";
video
.play()
@@ -118,11 +125,15 @@ const tickState = (tick: number, current: number) => {
return tick === current ? "now" : "done";
};
/** Progresso (0 a 1) de cada vídeo quando o projeto `index` está na fração `local` do trecho dele. */
/**
* Progresso da rolagem (0 a 1, sem as pausas) de cada projeto quando o projeto `index` está na fração `local` do trecho
* dele. É da rolagem, não do vídeo: os capítulos têm rolagem igual (`videoFractionAtScroll` leva ao ponto do vídeo).
*/
function targetsAt(count: number, index: number, local: number) {
const { head, tail } = reelHolds(index, count);
return Array.from({ length: count }, (_, i) => {
if (i < index) return 1;
return i === index ? videoProgress(local) : 0;
return i === index ? videoProgress(local, head, tail) : 0;
});
}
@@ -233,12 +244,15 @@ export default function PinnedReel({ items }: { items: ShowcaseItem[] }) {
const next = easeToward(state.smooth[index] ?? 0, target, seconds);
state.smooth[index] = next;
if (Math.abs(next - target) > 0.0004) moving = true;
seekVideo(desktopVideos.current[index], next);
seekVideo(
desktopVideos.current[index],
videoFractionAtScroll(items[index].config, next),
);
bars.current[index]?.style.setProperty("--p", next.toFixed(4));
});
const current = items[state.active];
if (current) {
const index = chapterIndexAt(
const index = chapterIndexAtScroll(
current.config,
state.smooth[state.active] ?? 0,
);
@@ -253,20 +267,26 @@ export default function PinnedReel({ items }: { items: ShowcaseItem[] }) {
);
// um vídeo que acabou de carregar precisa ir ao ponto em que a rolagem já está
const syncVideo = useCallback((index: number) => {
const state = live.current;
seekVideo(
desktopVideos.current[index],
state.smooth[index] ?? state.target[index] ?? 0,
);
}, []);
const syncVideo = useCallback(
(index: number) => {
const state = live.current;
seekVideo(
desktopVideos.current[index],
videoFractionAtScroll(
items[index].config,
state.smooth[index] ?? state.target[index] ?? 0,
),
);
},
[items],
);
/** Troca o projeto em cena: o capítulo vem do ponto em que o vídeo dele já está. */
const changeActive = useCallback(
(index: number) => {
const state = live.current;
state.active = index;
state.chapter = chapterIndexAt(
state.chapter = chapterIndexAtScroll(
items[index].config,
state.smooth[index] ?? 0,
);
@@ -332,12 +352,9 @@ export default function PinnedReel({ items }: { items: ShowcaseItem[] }) {
const goTo = (index: number) => scrollToReel(index, 0);
const goToChapter = (index: number, chapterIndex: number) => {
const fraction = chapterFraction(
items[index].config,
chapterIndex,
"desktop",
);
scrollToReel(index, REEL_HOLD + fraction * (1 - REEL_HOLD * 2));
const { head, tail } = reelHolds(index, items.length);
const progress = chapterScrollFraction(items[index].config, chapterIndex);
scrollToReel(index, localAtProgress(progress, head, tail));
};
const current = items[active];