perf(movimento): sem tela vazia, passagens em CSS e vitrine leve
Projetos sobem sobre o palco do hero e o celular tem hero de uma tela sem mergulho; passagens com animation-timeline em CSS, sem GSAP nem motion; troca da vitrine sem texto duplicado; moldura de celular só com pôster e scrub reencodado em 960 px (9,4 para 5,2 MB); 3D monta na primeira interação e para quando parado; transição entre páginas do mesmo segmento; foco em anel duplo; texto de 15 px no celular; código morto removido.
This commit is contained in:
1 parent
10f170abf4
commit
318a764190
77 files changed
+4293
-4951
No files matched your search
@@ -1,6 +1,6 @@
|
||||
import { act, render } from "@testing-library/react";
|
||||
import * as THREE from "three";
|
||||
import HeroFieldScene from "./HeroFieldScene";
|
||||
import HeroFieldScene, { prepareField } from "./HeroFieldScene";
|
||||
|
||||
/**
|
||||
* Testes de caminhos-base do quadro da cena 3D (o callback do useFrame de Field, CC alta) e de SceneBoundary:
|
||||
@@ -23,6 +23,7 @@ const mockState: {
|
||||
throwOnUseThree: boolean;
|
||||
} = { frame: null, three: null, throwOnUseThree: false };
|
||||
const mockMaterials: THREE.ShaderMaterial[] = [];
|
||||
const mockCounts = { vectors: 0 };
|
||||
const mockGeometries: THREE.BufferGeometry[] = [];
|
||||
|
||||
jest.mock("three", () => {
|
||||
@@ -39,10 +40,17 @@ jest.mock("three", () => {
|
||||
mockGeometries.push(this as unknown as THREE.BufferGeometry);
|
||||
}
|
||||
}
|
||||
class CountingVector3 extends actual.Vector3 {
|
||||
constructor(x?: number, y?: number, z?: number) {
|
||||
super(x, y, z);
|
||||
mockCounts.vectors += 1;
|
||||
}
|
||||
}
|
||||
return {
|
||||
...actual,
|
||||
ShaderMaterial: CapturingMaterial,
|
||||
BufferGeometry: CapturingGeometry,
|
||||
Vector3: CountingVector3,
|
||||
};
|
||||
});
|
||||
jest.mock("@react-three/fiber", () => ({
|
||||
@@ -166,6 +174,7 @@ function pointerMove(type: string, x = 600, y = 400) {
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
mockCounts.vectors = 0;
|
||||
mockMaterials.length = 0;
|
||||
mockGeometries.length = 0;
|
||||
mockState.frame = null;
|
||||
@@ -237,15 +246,19 @@ describe("Field: quadro (useFrame) — começo e rolagem", () => {
|
||||
expect(scene.uniforms().uIntro.value).toBe(1);
|
||||
});
|
||||
|
||||
it("path: the scene fades out only at the very end of the dive", () => {
|
||||
it("path: the scene only dims, and only at the very end of the dive (it never ends in the dark)", () => {
|
||||
const scene = setup({ progress: 0 });
|
||||
scene.settle(10);
|
||||
expect(scene.uniforms().uFade.value).toBe(1);
|
||||
|
||||
scene.progressRef.current = 0.9;
|
||||
scene.settle(120);
|
||||
expect(scene.uniforms().uFade.value).toBe(1);
|
||||
|
||||
scene.progressRef.current = 1;
|
||||
scene.settle(120);
|
||||
|
||||
expect(scene.uniforms().uFade.value).toBeCloseTo(0, 6);
|
||||
expect(scene.uniforms().uFade.value).toBeCloseTo(0.6, 6);
|
||||
});
|
||||
|
||||
it("path: layers spread apart only while they face the viewer", () => {
|
||||
@@ -279,13 +292,17 @@ describe("Field: enquadramento e foco", () => {
|
||||
expect(scene.camera.position.z).toBeGreaterThan(9);
|
||||
});
|
||||
|
||||
it("path: end of the dive -> the group has settled back to the center and the camera went through the layers", () => {
|
||||
it("path: end of the dive -> the group has settled back to the center and the camera stopped in front of the data layer", () => {
|
||||
const scene = setup({ progress: 1 });
|
||||
|
||||
scene.settle(200);
|
||||
|
||||
const spread = scene.uniforms().uSpread.value;
|
||||
expect(scene.group.position.y).toBeCloseTo(0, 6);
|
||||
// passou pelas duas primeiras camadas e parou diante da última (a dos dados), a menos de 6 unidades dela
|
||||
expect(scene.camera.position.z).toBeLessThan(0);
|
||||
expect(scene.camera.position.z).toBeGreaterThan(-spread);
|
||||
expect(scene.camera.position.z + spread).toBeLessThan(6);
|
||||
});
|
||||
|
||||
it("path: camera in front of the layers -> the lens focuses on the next layer ahead", () => {
|
||||
@@ -297,12 +314,30 @@ describe("Field: enquadramento e foco", () => {
|
||||
expect(scene.uniforms().uFocus.value).toBeCloseTo(9 - 1.05, 6);
|
||||
});
|
||||
|
||||
it("path: camera past every layer -> the focus never goes below 1.5", () => {
|
||||
it("path: camera parked in front of the data layer -> the lens focuses on it, never closer than 1.5", () => {
|
||||
const scene = setup({ progress: 1 });
|
||||
|
||||
scene.settle(200);
|
||||
|
||||
expect(scene.uniforms().uFocus.value).toBe(1.5);
|
||||
const spread = scene.uniforms().uSpread.value;
|
||||
expect(scene.uniforms().uFocus.value).toBeCloseTo(
|
||||
scene.camera.position.z + spread,
|
||||
6,
|
||||
);
|
||||
expect(scene.uniforms().uFocus.value).toBeGreaterThanOrEqual(1.5);
|
||||
});
|
||||
|
||||
it("path: camera between the first layer and the middle one -> the lens focuses on the middle layer, at the origin", () => {
|
||||
const scene = setup({ progress: 0.55 });
|
||||
|
||||
scene.settle(200);
|
||||
|
||||
const spread = scene.uniforms().uSpread.value;
|
||||
const z = scene.camera.position.z;
|
||||
// já passou da primeira camada (z abaixo de spread) e ainda não chegou à do meio (z acima de 0,2)
|
||||
expect(z).toBeGreaterThan(0.2);
|
||||
expect(z).toBeLessThan(spread);
|
||||
expect(scene.uniforms().uFocus.value).toBeCloseTo(Math.max(1.5, z), 6);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -472,6 +507,43 @@ describe("Field: quando pedir o próximo quadro", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("Field: geometria pré-calculada e quadros sem alocação", () => {
|
||||
it("path: prepareField builds the points before the scene mounts, and mounting reuses them", () => {
|
||||
prepareField();
|
||||
expect(mockGeometries).toHaveLength(1);
|
||||
const prepared = mockGeometries[0];
|
||||
|
||||
prepareField(); // de novo, sem montar: nada é refeito
|
||||
expect(mockGeometries).toHaveLength(1);
|
||||
|
||||
setup();
|
||||
expect(mockGeometries).toHaveLength(1);
|
||||
expect(mockGeometries[0]).toBe(prepared);
|
||||
expect(prepared.getAttribute("position").count).toBeGreaterThan(1000);
|
||||
});
|
||||
|
||||
it("path: the preparation is used once; a second scene builds its own points", () => {
|
||||
prepareField();
|
||||
setup();
|
||||
expect(mockGeometries).toHaveLength(1);
|
||||
|
||||
setup();
|
||||
expect(mockGeometries).toHaveLength(2);
|
||||
});
|
||||
|
||||
it("path: no vector is created per frame, not even with the mouse driving the cursor over the layer", () => {
|
||||
const scene = setup();
|
||||
jest.spyOn(performance, "now").mockReturnValue(5000);
|
||||
pointerMove("mouse");
|
||||
scene.frame(5.1, 1 / 60);
|
||||
const before = mockCounts.vectors;
|
||||
|
||||
for (let i = 0; i < 60; i++) scene.frame(5.2 + i / 60, 1 / 60);
|
||||
|
||||
expect(mockCounts.vectors).toBe(before);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Field: densidade, contexto WebGL e limpeza", () => {
|
||||
const pointCount = () => {
|
||||
const geometry = mockGeometries[mockGeometries.length - 1];
|
||||
|
||||
Reference in new issue
Block a user