perf(movimento): vitrine e 3D mais leves, WebGL por software lembrado na sessão e suporte ao WebKit
This commit is contained in:
1 parent
9fd1570426
commit
8903f3c57e
28 files changed
+854
-406
No files matched your search
@@ -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];
|
||||
|
||||
Reference in new issue
Block a user