Skip to content

Commit 65f41a3

Browse files
committed
Resize avatar image for larger DPIs
1 parent 55cec29 commit 65f41a3

3 files changed

Lines changed: 70 additions & 33 deletions

File tree

astro.config.ts

Lines changed: 64 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,15 @@ const optimizeImagesIntegration: AstroIntegration = {
3030
name: "optimize-images",
3131
hooks: {
3232
async "astro:build:done"({ dir, routes }) {
33-
type Images = { original: string; webp: string }[];
34-
type MatchGroups = { preSrc: string; src: string; postSrc: string };
35-
33+
type Images = { original: string; webp: string | string[] }[];
34+
type MatchGroups = {
35+
resize?: string;
36+
preSrc: string;
37+
src: string;
38+
postSrc: string;
39+
};
40+
41+
const resizeSizes = [1, 1.5, 2, 3, 4];
3642
const images: Images = [];
3743

3844
for (const route of routes) {
@@ -43,19 +49,30 @@ const optimizeImagesIntegration: AstroIntegration = {
4349
const htmlPath = nodeUrl.fileURLToPath(route.distURL);
4450
let html = await fs.readFile(htmlPath, "utf-8");
4551
const matches = html.matchAll(
46-
/<img optimize-image(?<preSrc>.+)src="(?<src>[^"]+)"(?<postSrc>.+)>/g
52+
/<img optimize-image (?<resize>resize="true")?(?<preSrc>.+)src="(?<src>[^"]+)"(?<postSrc>.+)>/g
4753
);
4854

4955
for (const match of matches) {
50-
const { preSrc, src, postSrc } = match.groups as MatchGroups;
51-
const webp = src.slice(0, src.lastIndexOf(".")) + ".webp";
56+
const { resize, preSrc, src, postSrc } = match.groups as MatchGroups;
57+
const baseName = src.slice(0, src.lastIndexOf("."));
58+
let webp;
59+
60+
if (resize) {
61+
webp = resizeSizes.map((size) => `${baseName}-${size}.webp`);
62+
} else {
63+
webp = `${baseName}.webp`;
64+
}
5265

5366
images.push({ original: src, webp });
5467

5568
html = html.replace(
5669
match[0],
5770
`
58-
<source srcset="${webp}" type="image/webp">
71+
<source srcset="${
72+
typeof webp === "string"
73+
? webp
74+
: webp.map((fileName, i) => `${fileName} ${resizeSizes[i]}x`)
75+
}" type="image/webp">
5976
<img ${preSrc} src="${src}" ${postSrc}>
6077
`
6178
);
@@ -71,38 +88,54 @@ const optimizeImagesIntegration: AstroIntegration = {
7188
}) as string;
7289
for (let { original, webp } of images) {
7390
original = path.join(distDir, original);
74-
webp = path.join(distDir, webp);
91+
92+
if (typeof webp === "string") {
93+
webp = [path.join(distDir, webp)];
94+
} else {
95+
webp = webp.map((fileName) => path.join(distDir, fileName));
96+
}
7597

7698
const format = path.extname(original).slice(1);
7799
const image = sharp(original, {
78100
limitInputPixels: false,
79101
animated: format === "gif"
80102
});
103+
const { width, height } = await image.metadata();
81104

82-
const cachedWebp = path.join(cacheDir, path.basename(webp));
83-
try {
84-
await fs.access(cachedWebp);
85-
105+
for (let i = 0; i < webp.length; i++) {
106+
const cachedWebp = path.join(
107+
cacheDir,
108+
path.basename(webp[i] as string)
109+
);
86110
try {
87-
await fs.copyFile(cachedWebp, webp);
88-
} catch {}
89-
} catch {
90-
// In KB
91-
const size = (await fs.stat(original)).size / 1000;
92-
93-
await image
94-
.clone()
95-
.webp({
96-
effort: 6,
97-
// For some reason, using lossless compression mode on large GIFs
98-
// increase their WebP size.
99-
lossless:
100-
format === "gif" &&
101-
// Less than 1MB
102-
size < 1000
103-
})
104-
.toFile(webp);
105-
await fs.copyFile(webp, cachedWebp);
111+
await fs.access(cachedWebp);
112+
113+
try {
114+
await fs.copyFile(cachedWebp, webp[i] as string);
115+
} catch {}
116+
} catch {
117+
const clone = image.clone();
118+
119+
if (typeof width === "number" && typeof height === "number") {
120+
clone.resize(
121+
width * (resizeSizes[i] as number),
122+
height * (resizeSizes[i] as number)
123+
);
124+
}
125+
126+
await clone
127+
.webp({
128+
effort: 6,
129+
// For some reason, using lossless compression mode on large
130+
// GIFs increase their WebP size.
131+
lossless:
132+
format === "gif" &&
133+
// Less than 1MB
134+
(await fs.stat(original)).size / 1000 < 1000
135+
})
136+
.toFile(webp[i] as string);
137+
await fs.copyFile(webp[i] as string, cachedWebp);
138+
}
106139
}
107140

108141
const cachedOriginal = path.join(cacheDir, path.basename(original));

src/components/about/hero.astro

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import avatarPng from "@assets/avatar.png";
55
---
66

77
<section>
8-
<Picture alt="mcecode's avatar" {...avatarPng} />
8+
<Picture resize={true} alt="mcecode's avatar" {...avatarPng} />
99
<h1>Hi, I'm Matthew</h1>
1010
<p>I code to turn ideas into reality</p>
1111
</section>

src/components/ui/picture.astro

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ type Props = {
55
height: number;
66
alt: string;
77
class?: string | { picture: string; img: string };
8+
resize?: boolean;
89
format?: string;
910
};
1011
@@ -20,7 +21,10 @@ if (typeof Astro.props.class === "object") {
2021
imgClass = Astro.props.class.img;
2122
}
2223
24+
const { resize = false } = Astro.props;
25+
2326
delete Astro.props.class;
27+
delete Astro.props.resize;
2428
delete Astro.props.format;
2529
---
2630

@@ -32,5 +36,5 @@ delete Astro.props.format;
3236
there should be none.
3337
*/}
3438
<!-- @ts-expect-error -->
35-
<img optimize-image class={imgClass} decoding="async" loading="lazy" {...Astro.props} />
39+
<img optimize-image resize={resize} class={imgClass} decoding="async" loading="lazy" {...Astro.props} />
3640
</picture>

0 commit comments

Comments
 (0)