@sumaq/site-kit 0.4.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -2
- package/package.json +2 -2
- package/src/components/Media.astro +56 -3
- package/src/lib/media.ts +16 -2
- package/src/tokens/class-inventory.json +1 -1
package/README.md
CHANGED
|
@@ -69,13 +69,15 @@ Nothing serves `/media/portrait-a3f9c1d2.webp`. `Media` looks the key up in a bu
|
|
|
69
69
|
|
|
70
70
|
| `src` | What happens |
|
|
71
71
|
|---|---|
|
|
72
|
-
| `/media/x.webp` present in `./media/` | `<Image>` with `srcset` at 640/960/1280/
|
|
72
|
+
| `/media/x.webp` present in `./media/` | `<Image>` with `srcset` at 640/960/1280/1600/1920 (clamped to the original) plus the manifest's LQIP as a background |
|
|
73
73
|
| `/media/x.svg` | `<img>` with the emitted URL — imported SVGs become components in Astro 7, and this keeps them images |
|
|
74
74
|
| `https://…` or any `public/` path | plain `<img>`, untouched |
|
|
75
75
|
| an `ImageMetadata` import | `<Image>`, as before |
|
|
76
76
|
| empty | nothing — unless `fallback` is set |
|
|
77
77
|
|
|
78
|
-
`media/manifest.json`, written by the CMS, carries `{ alt, width, height, lqip }` per key. A site without one simply renders without LQIP.
|
|
78
|
+
`media/manifest.json`, written by the CMS, carries `{ alt, width, height, lqip, alpha }` per key. A site without one simply renders without LQIP.
|
|
79
|
+
|
|
80
|
+
The LQIP is painted as the `<img>`'s own background and **removed as soon as the real image loads**. It has to be: the blur sits behind the picture, so anything the foreground does not cover keeps showing it — through the transparent pixels of a cut-out, or beside the picture when the CSS box and the file disagree on aspect ratio, where it reads as a shadow under the subject. An image the manifest marks `alpha: true` gets no LQIP at all.
|
|
79
81
|
|
|
80
82
|
### Placeholder
|
|
81
83
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sumaq/site-kit",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "Layouts, blocks, helpers and Astro config factory for Sumaq client sites.",
|
|
5
5
|
"license": "UNLICENSED",
|
|
6
6
|
"author": "elingan",
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
"dependencies": {
|
|
34
34
|
"@astrojs/sitemap": "3.7.3",
|
|
35
35
|
"marked": "15.0.7",
|
|
36
|
-
"@sumaq/cms-schema": "0.3.
|
|
36
|
+
"@sumaq/cms-schema": "0.3.1"
|
|
37
37
|
},
|
|
38
38
|
"devDependencies": {
|
|
39
39
|
"astro": "7.1.4",
|
|
@@ -105,13 +105,30 @@ const asset: ImageMetadata | null = usePlaceholder
|
|
|
105
105
|
/** Content images get the full srcset; a hand-passed import keeps its own props. */
|
|
106
106
|
const assetWidths = widths ?? (localAsset || usePlaceholder ? DEFAULT_WIDTHS : undefined);
|
|
107
107
|
|
|
108
|
-
/**
|
|
109
|
-
|
|
110
|
-
|
|
108
|
+
/**
|
|
109
|
+
* LQIP from the manifest, painted as the image's own background while it loads
|
|
110
|
+
* and dropped by the inline script below once it has.
|
|
111
|
+
*
|
|
112
|
+
* It has to go once: the blur sits *behind* the picture, so on anything the
|
|
113
|
+
* foreground does not cover completely it stays visible for the life of the
|
|
114
|
+
* page — through the transparent pixels of a cut-out, and beside the picture
|
|
115
|
+
* whenever the CSS box and the file disagree on aspect ratio (there
|
|
116
|
+
* `background-position` and `object-position` drift apart and the leftover blur
|
|
117
|
+
* reads as a shadow under the subject).
|
|
118
|
+
*
|
|
119
|
+
* An image with an alpha channel gets no LQIP at all: for a cut-out the blurred
|
|
120
|
+
* rectangle is wrong even while it loads, since the real image will never fill
|
|
121
|
+
* that shape.
|
|
122
|
+
*/
|
|
123
|
+
const showLqip = Boolean(meta?.lqip) && meta?.alpha !== true;
|
|
124
|
+
const lqipStyle = showLqip
|
|
125
|
+
? `background-image:url(${meta!.lqip});background-size:cover;background-position:center;background-repeat:no-repeat`
|
|
111
126
|
: undefined;
|
|
112
127
|
const composedStyle = [style, usePlaceholder ? undefined : lqipStyle]
|
|
113
128
|
.filter(Boolean)
|
|
114
129
|
.join(";") || undefined;
|
|
130
|
+
/** Marks the images the cleanup script owns. */
|
|
131
|
+
const lqipFlag = !usePlaceholder && showLqip ? "" : undefined;
|
|
115
132
|
|
|
116
133
|
/**
|
|
117
134
|
* An SVG gets its dimensions written out by hand (it skips `<Image>`), so a
|
|
@@ -145,6 +162,7 @@ const passthrough = !asset && !missing;
|
|
|
145
162
|
decoding={decoding}
|
|
146
163
|
fetchpriority={fetchpriority}
|
|
147
164
|
data-cms-src={cms}
|
|
165
|
+
data-lqip={lqipFlag}
|
|
148
166
|
/>
|
|
149
167
|
) : (
|
|
150
168
|
<Image
|
|
@@ -161,6 +179,7 @@ const passthrough = !asset && !missing;
|
|
|
161
179
|
decoding={decoding}
|
|
162
180
|
fetchpriority={fetchpriority}
|
|
163
181
|
data-cms-src={cms}
|
|
182
|
+
data-lqip={lqipFlag}
|
|
164
183
|
/>
|
|
165
184
|
))
|
|
166
185
|
}
|
|
@@ -179,6 +198,40 @@ const passthrough = !asset && !missing;
|
|
|
179
198
|
decoding={decoding}
|
|
180
199
|
fetchpriority={fetchpriority}
|
|
181
200
|
data-cms-src={cms}
|
|
201
|
+
data-lqip={lqipFlag}
|
|
182
202
|
/>
|
|
183
203
|
)
|
|
184
204
|
}
|
|
205
|
+
|
|
206
|
+
<script>
|
|
207
|
+
/**
|
|
208
|
+
* Removes the LQIP once the real image is on screen — see `lqipStyle` above
|
|
209
|
+
* for why leaving it in place is not harmless.
|
|
210
|
+
*
|
|
211
|
+
* `astro:page-load` fires on the first load and after every navigation made
|
|
212
|
+
* by `<ClientRouter />`, which does not re-execute this module on its own.
|
|
213
|
+
* Clearing the attribute makes a second pass over the same image a no-op.
|
|
214
|
+
*/
|
|
215
|
+
const clear = (img) => {
|
|
216
|
+
img.style.backgroundImage = "none";
|
|
217
|
+
img.removeAttribute("data-lqip");
|
|
218
|
+
};
|
|
219
|
+
|
|
220
|
+
const settle = (img) => {
|
|
221
|
+
if (img.complete && img.naturalWidth > 0) {
|
|
222
|
+
clear(img);
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
// One frame later: the browser has painted the decoded image by then, so
|
|
226
|
+
// dropping the background underneath it is invisible.
|
|
227
|
+
img.addEventListener("load", () => requestAnimationFrame(() => clear(img)), { once: true });
|
|
228
|
+
img.addEventListener("error", () => clear(img), { once: true });
|
|
229
|
+
};
|
|
230
|
+
|
|
231
|
+
const initLqip = () => {
|
|
232
|
+
for (const img of document.querySelectorAll("img[data-lqip]")) settle(img);
|
|
233
|
+
};
|
|
234
|
+
|
|
235
|
+
initLqip();
|
|
236
|
+
document.addEventListener("astro:page-load", initLqip);
|
|
237
|
+
</script>
|
package/src/lib/media.ts
CHANGED
|
@@ -20,8 +20,16 @@
|
|
|
20
20
|
*/
|
|
21
21
|
import type { ImageMetadata } from "astro";
|
|
22
22
|
|
|
23
|
-
/**
|
|
24
|
-
|
|
23
|
+
/**
|
|
24
|
+
* Widths the CMS master (2000px) is resized to. Astro clamps every entry to the
|
|
25
|
+
* original, so a small upload still emits only the sizes it really has.
|
|
26
|
+
*
|
|
27
|
+
* The steps are ~25% apart on purpose: below that a browser saves too little
|
|
28
|
+
* bandwidth to pay for one more file in the build. 640 covers a phone at 1x and
|
|
29
|
+
* a 320px column at 2x, 960 a 480px column at 2x, 1280/1600 laptops, and 1920
|
|
30
|
+
* the full-bleed desktop case.
|
|
31
|
+
*/
|
|
32
|
+
export const DEFAULT_WIDTHS = [640, 960, 1280, 1600, 1920];
|
|
25
33
|
|
|
26
34
|
/** The prefix that marks a string as a local key instead of a URL. */
|
|
27
35
|
export const MEDIA_PREFIX = "/media/";
|
|
@@ -33,6 +41,12 @@ export interface MediaManifestEntry {
|
|
|
33
41
|
height?: number;
|
|
34
42
|
/** Base64 data URI shown while the real image loads. */
|
|
35
43
|
lqip?: string;
|
|
44
|
+
/**
|
|
45
|
+
* The image is see-through. A cut-out gets no LQIP: the blur is painted
|
|
46
|
+
* *behind* the picture, so transparent pixels would show it instead of the
|
|
47
|
+
* page. Absent on entries written before the flag existed.
|
|
48
|
+
*/
|
|
49
|
+
alpha?: boolean;
|
|
36
50
|
}
|
|
37
51
|
|
|
38
52
|
/**
|