feat: introduce Picture component for optimized image handling across posts and hero sections

This commit is contained in:
2026-03-17 17:32:21 +09:00
parent bb07ca05f0
commit 0640943c28
4 changed files with 69 additions and 5 deletions
+3 -1
View File
@@ -3,6 +3,7 @@ import Tags from "@/components/ui/Tags.astro";
import Categories from "@/components/ui/Categories.astro";
import { type CollectionEntry, render } from "astro:content";
import { toR2Url } from "@/utils/r2";
import Picture from "@/components/ui/Picture.astro";
export interface Props {
post: CollectionEntry<"blog">;
@@ -23,10 +24,11 @@ const readingTime = remarkPluginFrontmatter?.minutesRead
<div class="relative h-48 w-full overflow-hidden">
{
coverImage && (
<img
<Picture
src={coverImage}
alt={post.data.title}
class="h-full w-full object-cover transition-transform duration-300 group-hover:scale-105"
loading="lazy"
/>
)
}
+60
View File
@@ -0,0 +1,60 @@
---
import { R2_BASE } from "@/config/r2.mjs";
export interface Props {
src: string;
alt: string;
class?: string;
sizes?: string;
fetchpriority?: "high" | "low" | "auto";
loading?: "lazy" | "eager";
}
const {
src,
alt,
class: className,
sizes = "(max-width: 640px) 100vw, (max-width: 1024px) 80vw, 1200px",
fetchpriority,
loading,
} = Astro.props;
const WIDTHS = [480, 800, 1200];
const isR2 = src.includes(R2_BASE.replace("https://", ""));
function buildTransformUrl(src: string, width: number) {
const path = src.replace(R2_BASE, "");
return `${R2_BASE}/cdn-cgi/image/width=${width},format=auto,onerror=redirect${path}`;
}
const srcset = WIDTHS.map((w) => `${buildTransformUrl(src, w)} ${w}w`).join(
", ",
);
const defaultSrc = isR2 ? buildTransformUrl(src, 800) : src;
---
{
isR2 ? (
<picture>
<source type="image/webp" srcset={srcset} sizes={sizes} />
<img
src={defaultSrc}
srcset={srcset}
sizes={sizes}
alt={alt}
class={className}
fetchpriority={fetchpriority}
loading={loading}
/>
</picture>
) : (
<img
src={src}
alt={alt}
class={className}
fetchpriority={fetchpriority}
loading={loading}
/>
)
}
+3 -3
View File
@@ -5,6 +5,7 @@ import Headline from "@/components/ui/Headline.astro";
import Button from "@/components/ui/Button.astro";
import { Icon } from "astro-icon/components";
import { toR2Url } from "@/utils/r2";
import Picture from "@/components/ui/Picture.astro";
const {
title = await Astro.slots.render("title"),
@@ -33,11 +34,10 @@ const heroImage = toR2Url(bgImage);
{
heroImage && (
<Fragment slot="bg">
<img
fetchpriority="high"
<Picture
src={heroImage}
alt=""
aria-hidden="true"
fetchpriority="high"
class="absolute inset-0 h-full w-full object-cover"
/>
<div class="absolute inset-0 bg-black/55" aria-hidden="true" />
+3 -1
View File
@@ -4,6 +4,7 @@ import Schema from "@/components/seo/Schema.astro";
import Tags from "@/components/ui/Tags.astro";
import Categories from "@/components/ui/Categories.astro";
import { toR2Url } from "@/utils/r2";
import Picture from "@/components/ui/Picture.astro";
import { getCollection, render } from "astro:content";
@@ -74,10 +75,11 @@ const metadata = {
{
coverImage && (
<div class="reveal relative mb-8 h-96 w-full overflow-hidden rounded-xl shadow-lg">
<img
<Picture
src={coverImage}
alt={title}
class="h-full w-full object-cover"
fetchpriority="high"
/>
</div>
)