Skip to content

Commit 476b406

Browse files
committed
Reserve workflow sample image space before loading
1 parent 12669cc commit 476b406

2 files changed

Lines changed: 38 additions & 5 deletions

File tree

marketing/src/components/TemplateSampleFigure.tsx

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@ import Image from "next/image";
22
import type { TemplateSample } from "@/data/templateSamples";
33

44
export default function TemplateSampleFigure({ sample }: { sample: TemplateSample }) {
5+
const imageWidth = sample.imageWidth ?? 1280;
6+
const imageHeight = sample.imageHeight ?? 1020;
7+
58
return (
69
<figure>
710
{sample.inputText && (
@@ -11,7 +14,10 @@ export default function TemplateSampleFigure({ sample }: { sample: TemplateSampl
1114
</div>
1215
)}
1316
{sample.image && (
14-
<div className={sample.inputImage ? "grid gap-4 md:grid-cols-2" : "mx-auto w-fit max-w-full"}>
17+
<div
18+
className={sample.inputImage ? "grid gap-4 md:grid-cols-2" : "mx-auto max-w-full"}
19+
style={sample.inputImage ? undefined : { width: Math.min(imageWidth, (imageWidth / imageHeight) * 640) }}
20+
>
1521
{[
1622
{ src: sample.inputImage, label: "Input photo" },
1723
{ src: sample.image, label: "Result" },
@@ -22,9 +28,9 @@ export default function TemplateSampleFigure({ sample }: { sample: TemplateSampl
2228
<Image
2329
src={src}
2430
alt={label === "Input photo" ? label : sample.caption || "Workflow result"}
25-
width={label === "Input photo" ? 1024 : sample.imageWidth ?? 1280}
26-
height={label === "Input photo" ? 816 : sample.imageHeight ?? 1020}
27-
className={sample.inputImage ? "aspect-[5/4] w-full object-contain" : "mx-auto h-auto max-h-[640px] w-auto max-w-full object-contain"}
31+
width={label === "Input photo" ? 1024 : imageWidth}
32+
height={label === "Input photo" ? 816 : imageHeight}
33+
className={sample.inputImage ? "aspect-[5/4] w-full object-contain" : "h-auto w-full object-contain"}
2834
/>
2935
</div>
3036
</div>

marketing/tests/e2e/cls.spec.ts

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ const CLS_BUDGET = 0.1;
88

99
// Pages this gate guards. The homepage is the one Google flagged (0.58); add
1010
// more paths here once each has been audited and reserves its media space.
11-
const ROUTES = ["/"];
11+
const ROUTES = ["/", "/templates/movie-posters"];
1212

1313
// layout-shift entries aren't in the DOM lib.
1414
interface LayoutShiftEntry extends PerformanceEntry {
@@ -23,6 +23,33 @@ declare global {
2323
}
2424

2525
test.describe("Cumulative Layout Shift budget", () => {
26+
test("template samples reserve space before the image loads", async ({ page }) => {
27+
let releaseImage!: () => void;
28+
const imageReady = new Promise<void>((resolve) => { releaseImage = resolve; });
29+
await page.route("**/templates/samples/movie-posters.webp", async (route) => {
30+
await imageReady;
31+
await route.continue();
32+
});
33+
34+
try {
35+
await page.goto("/templates/movie-posters", { waitUntil: "domcontentloaded" });
36+
const image = page.locator("figure img");
37+
const before = await image.boundingBox();
38+
expect(before).not.toBeNull();
39+
expect(before!.width).toBeGreaterThan(400);
40+
expect(before!.height).toBeGreaterThan(600);
41+
expect(await image.evaluate((element: HTMLImageElement) => element.complete)).toBe(false);
42+
43+
releaseImage();
44+
await image.evaluate((element: HTMLImageElement) => element.decode());
45+
const after = await image.boundingBox();
46+
expect(after!.width).toBeCloseTo(before!.width, 0);
47+
expect(after!.height).toBeCloseTo(before!.height, 0);
48+
} finally {
49+
releaseImage();
50+
}
51+
});
52+
2653
for (const path of ROUTES) {
2754
test(`${path} stays under ${CLS_BUDGET} CLS through a full scroll`, async ({
2855
page,

0 commit comments

Comments
 (0)