perf(movimento): sem tempo morto, vídeos regravados e 3D sem React Three Fiber
Legenda do mergulho até Projetos cobrir a cena; transição de página sem blur e com fundo escuro; foco no conteúdo ao trocar de página; listas com marcador e dois pesos de título; vídeos da SEV e da SC Plus regravados sem quadros brancos; scrub a 12-15 fps (-30%); vitrine com uma parada de Tab e setas, contador no celular e CSS sob demanda; âncoras num módulo só (fragment-nav); FramePulse com setInterval; CSS morto removido.
This commit is contained in:
1 parent
f8545cd442
commit
6ac87824f3
68 files changed
+5735
-3300
No files matched your search
@@ -1,13 +1,69 @@
|
||||
"use client";
|
||||
|
||||
import Image from "next/image";
|
||||
import { type CSSProperties, useId, useRef } from "react";
|
||||
import {
|
||||
type CSSProperties,
|
||||
type RefObject,
|
||||
useEffect,
|
||||
useId,
|
||||
useRef,
|
||||
useState,
|
||||
} from "react";
|
||||
import { type Device, getShowcaseAssets } from "@/lib/project-showcase";
|
||||
import { BrowserFrame, displayUrl } from "./BrowserFrame";
|
||||
import { PhoneFrame } from "./PhoneFrame";
|
||||
import { ProjectActions } from "./ProjectActions";
|
||||
import { type ShowcaseItem, useShowcaseOpen } from "./ShowcaseProvider";
|
||||
import { useNearViewport } from "./useShowcaseMedia";
|
||||
import "./reel-list.css";
|
||||
|
||||
/**
|
||||
* Qual cartão do carrossel está à frente: o índice do cartão cujo início está mais perto do começo da faixa (no fim da
|
||||
* rolagem, o último, que talvez não alcance o começo). Só lê a posição da rolagem; sem rolagem horizontal fica em 0.
|
||||
*/
|
||||
export function carouselIndex(list: HTMLElement) {
|
||||
const cards = Array.from(list.children) as HTMLElement[];
|
||||
if (cards.length < 2 || list.scrollWidth <= list.clientWidth + 1) return 0;
|
||||
if (list.scrollLeft + list.clientWidth >= list.scrollWidth - 2)
|
||||
return cards.length - 1;
|
||||
const stride =
|
||||
cards[1].getBoundingClientRect().left -
|
||||
cards[0].getBoundingClientRect().left;
|
||||
if (stride <= 0) return 0;
|
||||
return Math.min(
|
||||
cards.length - 1,
|
||||
Math.max(0, Math.round(list.scrollLeft / stride)),
|
||||
);
|
||||
}
|
||||
|
||||
/** Acompanha a rolagem horizontal da faixa e devolve o índice do cartão à frente (em um quadro de animação por vez). */
|
||||
function useCarouselIndex(
|
||||
ref: RefObject<HTMLElement | null>,
|
||||
enabled: boolean,
|
||||
) {
|
||||
const [index, setIndex] = useState(0);
|
||||
useEffect(() => {
|
||||
const list = ref.current;
|
||||
if (!enabled || !list) return;
|
||||
let frame = 0;
|
||||
const read = () => {
|
||||
frame = 0;
|
||||
setIndex(carouselIndex(list));
|
||||
};
|
||||
const onScroll = () => {
|
||||
if (!frame) frame = requestAnimationFrame(read);
|
||||
};
|
||||
read();
|
||||
list.addEventListener("scroll", onScroll, { passive: true });
|
||||
window.addEventListener("resize", onScroll);
|
||||
return () => {
|
||||
cancelAnimationFrame(frame);
|
||||
list.removeEventListener("scroll", onScroll);
|
||||
window.removeEventListener("resize", onScroll);
|
||||
};
|
||||
}, [ref, enabled]);
|
||||
return index;
|
||||
}
|
||||
|
||||
function ListItem({
|
||||
item,
|
||||
@@ -85,6 +141,10 @@ export function ProjectList({
|
||||
}) {
|
||||
const open = useShowcaseOpen();
|
||||
const headingId = useId();
|
||||
const listRef = useRef<HTMLUListElement>(null);
|
||||
// a contagem só existe no celular (o carrossel por toque); a grade das telas maiores não rola para o lado
|
||||
const carousel = device === "mobile";
|
||||
const current = useCarouselIndex(listRef, carousel);
|
||||
return (
|
||||
<section
|
||||
className="ds-reel-list"
|
||||
@@ -94,7 +154,7 @@ export function ProjectList({
|
||||
<h2 id={headingId} className="sr-only">
|
||||
Projetos de clientes
|
||||
</h2>
|
||||
<ul className="ds-reel-items">
|
||||
<ul className="ds-reel-items" ref={listRef}>
|
||||
{items.map((item) => (
|
||||
<ListItem
|
||||
key={item.config.id}
|
||||
@@ -104,6 +164,20 @@ export function ProjectList({
|
||||
/>
|
||||
))}
|
||||
</ul>
|
||||
{carousel && items.length > 1 && (
|
||||
// quem lê com leitor de tela já tem a lista (com o número de itens): a contagem é só visual
|
||||
<p className="ds-reel-count" aria-hidden="true">
|
||||
<span className="ds-reel-dots">
|
||||
{items.map((item, index) => (
|
||||
<i
|
||||
key={item.config.id}
|
||||
data-on={index === current ? "" : undefined}
|
||||
/>
|
||||
))}
|
||||
</span>
|
||||
{current + 1}/{items.length}
|
||||
</p>
|
||||
)}
|
||||
</section>
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user