video posters get a ladder
A poster was one file at the video's full size — 1280x720 for a hero displayed 412 CSS pixels wide. Three times the pixels a phone can show, on the element that IS the page's Largest Contentful Paint, which is the most expensive place on the site to be careless. 384, 512, 768, 1024, plus poster.webp as the widest rung. Those are viewport widths times the density behind them: 412 at 1.75 wants 768, a tablet wants 768, a desktop wants 1280. Cut from the still rather than from poster.webp, so each rung is scaled once from the original frame instead of being a re-encode of an encode. poster.webp keeps its name — the placeholder is cut from it, the site falls back to it, and a directory built before this has only that one. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
+46
-1
@@ -109,6 +109,26 @@ export const LADDER = [320, 480, 640, 960, 1280, 1920, 2560] as const;
|
||||
*/
|
||||
export const SQUARE_LADDER = [256, 384, 480, 640, 960] as const;
|
||||
|
||||
/**
|
||||
* Poster rungs, in pixels of width — `poster-768.webp`, and so on.
|
||||
*
|
||||
* A video's poster was one file at the source's full size: 1280x720 for a hero displayed 412 CSS
|
||||
* pixels wide. That is three times the pixels a phone can show, on the element that IS the page's
|
||||
* Largest Contentful Paint — the most expensive place on the site to be careless.
|
||||
*
|
||||
* A hero is full-bleed, so the widths are viewport widths times the density behind them:
|
||||
*
|
||||
* 412 x 1.75 = 721 -> 768 (the emulated phone, and most of the real ones)
|
||||
* 412 x 3 = 1236 -> 1280 (a dense phone, from the full-size poster)
|
||||
* 768 x 1 = 768 -> 768 (a tablet)
|
||||
* 1280 x 1 = 1280 -> 1280 (a desktop)
|
||||
*
|
||||
* `poster.webp` keeps its name and stays the widest rung. Everything already refers to it — the
|
||||
* placeholder is cut from it, the site falls back to it, and a directory built before this has only
|
||||
* that one.
|
||||
*/
|
||||
export const POSTER_LADDER = [384, 512, 768, 1024] as const;
|
||||
|
||||
/**
|
||||
* WebP quality, by rung.
|
||||
*
|
||||
@@ -639,7 +659,32 @@ async function buildVideo(
|
||||
*/
|
||||
const placeholder = await encodePlaceholder(stillJpg, join(staging, 'placeholder.webp'), email);
|
||||
await rm(stillJpg, { force: true });
|
||||
variants.push({ file: 'poster.webp', width: null, height: null, bytes: poster.bytes });
|
||||
variants.push({ file: 'poster.webp', width: width || null, height: height || null, bytes: poster.bytes });
|
||||
|
||||
/*
|
||||
The poster's own ladder, cut from the same still.
|
||||
|
||||
From `stillJpg` rather than from `poster.webp`, so each rung is scaled once from the original frame
|
||||
instead of being a re-encode of an encode. The still is a JPEG at the video's full size and it is
|
||||
about to be deleted anyway.
|
||||
|
||||
Rungs narrower than the source only: one at or above it would be `poster.webp` under another name.
|
||||
*/
|
||||
for (const rung of POSTER_LADDER) {
|
||||
if (!width || rung >= width) continue;
|
||||
const cut = await post<{ path: string; bytes: number }>('/v1/sharp', email, {
|
||||
inputPath: stillJpg,
|
||||
outputPath: join(staging, `poster-${rung}.webp`),
|
||||
pipeline: [{ resize: { width: rung, fit: 'inside', withoutEnlargement: true } }],
|
||||
output: { format: 'webp', options: { quality: qualityFor(rung) } },
|
||||
});
|
||||
variants.push({
|
||||
file: `poster-${rung}.webp`,
|
||||
width: rung,
|
||||
height: height ? Math.round((rung / width) * height) : null,
|
||||
bytes: cut.bytes,
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
kind: 'video',
|
||||
|
||||
Reference in New Issue
Block a user