Astroの画像の扱いについて
v3.0で組み込まれたastro:assetsにはImage
コンポーネントがあり、srcディレクトリ内の画像であれば最適化して表示してくれる仕組みがあります。
最適化する場合、画像はそのままコンポーネントにパスを指定するのではなく、importして指定する必要があります。
---
// astroコンポーネント
import { Image } from 'astro:assets';
import thumbnail from '../images/thumbnail.jpg'; // 相対パスで指定してimport
---
// Imageコンポーネントの出力
<Image src={thumbnail} alt="サムネイル" width="800" height="600"/>
<!-- 出力結果 -->
<img
src="./_astro/thumbnail.hash.webp"
width="800"
height="600"
decoding="async"
loading="lazy"
alt="サムネイル"
/>
デフォルトではwebpに自動的に最適化して<img>タグで出力を返してくれます。またこの<Image>コンポーネントは多数のフォーマットに対応していて、より圧縮率の高いavif形式でも出力することができます。
// Imageコンポーネントの出力
<Image src={thumbnail} format="avif" alt="サムネイル" width="800" height="600"/>
<!-- 出力結果 -->
<img
src="./_astro/thumbnail.hash.avif"
width="800"
height="600"
decoding="async"
loading="lazy"
alt="サムネイル"
/>
jpgやpngなどの拡張子は任意で指定しない限り出力されません。とは言っても今はほぼ全てのモダンブラウザでwebpが対応しているため、あまり気にならない部分かなと思います。ただavifはEdgeなどが未対応のブラウザがあるので、Imageコンポーネントで指定するのは避けたほうがいいです。
レスポンシブイメージにも対応させたい
この<Image>
コンポーネントは出力を<img>
タグとしてしか出力できないため、<picture>
タグで画像を切り分けるアートディレクションには対応しておらず、使いづらさがあります。
Astro v3.3でPictureコンポーネントが追加されました。
ただ2023年12月このブログを書いている時点では画像のフォーマットによる切り分けるしかなく、media属性によりレスポンシブイメージやsrcset属性での解像度の切分けはできないようです。
ただ今後のアップデートで機能が追加される可能性はあるので公式リファレンスも見ておくと良いかもしれません。
また組込みのコンポーネントは先ほどの例のように1枚ずつ画像をimportする必要があることも記述が多くなってしまうため、避けたいところです。そこで公式のコンポーネントではないですが、Astro用のフレームワークとしてアートディレクションに対応したAstro-Imagetoolsというものを見つけたので、ちょっと使ってみたいと思います。
インストール
まずは下記のコマンドでインストールしてみます。
npm install astro-imagetools
インストールができたらastro.config
ファイルのインテグレーションに追加します。
import { astroImageTools } from "astro-imagetools"; // astro-imagetoolsのimport
export default {
integrations: [astroImageTools], // インテグレーションに追加
};
これでAstro-Imagetoolsに組み込まれてるPicture
コンポーネントを使えるようになります。
Astro-ImagetoolsのPictureコンポーネントを使ってみる
まず公式ドキュメントに従いimport
で<Picture>コンポーネントを読み込み、<Picture>タグに必要な属性を書いていきます。
---
import { Picture } from "astro-imagetools/components";
---
<Picture
src="/src/images/thumbnail.jpg"
alt="サムネイル"
artDirectives={[
{
src: "/src/images/thumbnail-sp.jpg",
media: "(max-width: 767px)",
},
]}
/>
artDirectivesは配列形式でmediaとsrc属性で読み込みたい画像とメディアクエリを記入していきます。画像に対してimportを挟まずに指定できるのはいいですね。上記のものを出力した結果が以下の内容です。
<!-- 出力結果 -->
<style>
.astro-imagetools-picture-hash {
--opacity: 1;
--z-index: 0;
}
.astro-imagetools-picture-hash img {
z-index: 1;
position: relative;
}
.astro-imagetools-picture-hash::after {
inset: 0;
content: "";
left: 0;
width: 100%;
height: 100%;
position: absolute;
pointer-events: none;
transition: opacity 1s;
opacity: var(--opacity);
z-index: var(--z-index);
}
.astro-imagetools-picture-hash img {
object-fit: cover;
object-position: 50% 50%;
}
.astro-imagetools-picture-hash::after {
background-size: cover;
background-image: url("data:image/jpeg;base64,#######Base64文字列#######");
background-position: 50% 50%;
}
@media (max-width: 767px) {
.astro-imagetools-picture-hash img {
object-fit: cover;
object-position: 50% 50%;
}
.astro-imagetools-picture-hash::after {
background-size: cover;
background-image: url("data:image/jpeg;base64,#######Base64文字列#######");
background-position: 50% 50%;
}
}
</style>
<picture class="astro-imagetools-picture astro-imagetools-picture-hash" style="position: relative; display: inline-block; ; max-width: 100%; height: auto;">
<source srcset="../_astro/thumbnail-sp@320w.hash.avif 320w, ../_astro/thumbnail-sp@607w.hash.avif 607w, ../_astro/thumbnail-sp@750w.hash.avif 750w" sizes="(min-width: 750px) 750px, 100vw" width="750" height="1300" type="image/avif" media="(max-width: 767px)" />
<source srcset="../_astro/thumbnail-sp@320w.hash.webp 320w, ../_astro/thumbnail-sp@607w.hash.webp 607w, ../_astro/thumbnail-sp@750w.hash.webp 750w" sizes="(min-width: 750px) 750px, 100vw" width="750" height="1300" type="image/webp" media="(max-width: 767px)" />
<source srcset="../_astro/thumbnail-sp@320w.hash.jpeg 320w, ../_astro/thumbnail-sp@607w.hash.jpeg 607w, ../_astro/thumbnail-sp@750w.hash.jpeg 750w" sizes="(min-width: 750px) 750px, 100vw" width="750" height="1300" type="image/jpeg" media="(max-width: 767px)" />
<source srcset="../_astro/thumbnail@320w.hash.avif 320w, ../_astro/thumbnail@693w.hash.avif 693w, ../_astro/thumbnail@992w.hash.avif 992w, ../_astro/thumbnail@1216w.hash.avif 1216w, ../_astro/thumbnail@1365w.hash.avif 1365w, ../_astro/thumbnail@1440w.hash.avif 1440w" sizes="(min-width: 1440px) 1440px, 100vw" width="1440" height="1240" type="image/avif" />
<source srcset="../_astro/thumbnail@320w.hash.webp 320w, ../_astro/thumbnail@693w.hash.webp 693w, ../_astro/thumbnail@992w.hash.webp 992w, ../_astro/thumbnail@1216w.hash.webp 1216w, ../_astro/thumbnail@1365w.hash.webp 1365w, ../_astro/thumbnail@1440w.hash.webp 1440w" sizes="(min-width: 1440px) 1440px, 100vw" width="1440" height="1240" type="image/webp" />
<img src="../_astro/thumbnail@1440w.hash.jpeg" alt="サムネイル" srcset="../_astro/thumbnail@320w.hash.jpeg 320w, ../_astro/thumbnail@693w.hash.jpeg 693w, ../_astro/thumbnail@992w.hash.jpeg 992w, ../_astro/thumbnail@1216w.hash.jpeg 1216w, ../_astro/thumbnail@1365w.hash.jpeg 1365w, ../_astro/thumbnail@1440w.hash.jpeg 1440w" sizes="(min-width: 1440px) 1440px, 100vw" width="1440" height="1240" loading="lazy" decoding="async" class="astro-imagetools-img" style="display: inline-block; overflow: hidden; vertical-align: middle; ; max-width: 100%; height: auto;" onload="parentElement.style.setProperty('--z-index', 1); parentElement.style.setProperty('--opacity', 0);" />
</picture>
<picture>タグ以外にもクラス名やスタイルが出力されています。
デフォルトで設定されるplaceholder
Astro Imagetoolsの<Picture>コンポーネントはplaceholderが用意されていて、読み込みが終わるまでの間、低解像度画像を出力する「blurred」がデフォルトで表示される仕様になっています。読み込み時点で余白が生まれないのでUX的にも良さそうです。
ちょっと個人的に気になったのは、ただ<style>タグが<picture>タグの直前に挿入されていること、そして<img>のスタイルが子孫要素で指定されていることです。
<style>タグが<body>の中に出てきてしまうのはのWordPressなどのCMSでプラグインや出力されるものなら仕方ないなとも思えるのですが、Astroのような静的生成を主とするものでは何だかいただけないなと思いました。
子孫要素のスタイルについても詳細度がスタイルに影響しそうなのも困ります。
そのほかにもAstro-Imagetoolsのいけてないポイントがかなりあり、実装で使うのは難しいという結論に至りました。
参考:Astroの画像インテグレーションAstro ImageToolsを使うときに注意すること
個人的にはシンプルに実装したかったので、この辺をまるっと削除する方法を調べたんですが、公式ドキュメントでは見当たりませんでした。
一応placeholderをnoneにすればstyleの中身自体は出力されなくなるようです。
Astro Imagetoolsは高度な分、思想が強いのか個人的にちょっと使いづらいです。
Pictureの出力は自前で実装することにしました。
Astroには「astro:assets」の中にgetImage関数があり、これでカスタムコンポーネントを作成できるということでこれを使ってコンポーネントを作成してみました。
一応以下の条件を満たすように作ったつもりです。
- 画像はimportを挟まずコンポーネント側だけで指定
- media属性でレスポンシブイメージを実装できる
- 複数の画像フォーマットと解像度の出力ができる
npmにパッケージで用意したので、興味がある方はインストールしてみてください。
https://www.npmjs.com/package/astro-simple-art-direction
npm install astro-simple-art-direction
使い方
インストールしたら、そのままコンポーネントを読み込んで使ってください。
以下はその例です。
import { Picture } from 'astro-simple-art-direction';
<Picture
src={{
file:"my-image.jpg",
width:1000,
height: 800
}}
artDirectives={[
{
media:"(max-width: 767px)",
file:"my-image-sp.jpg",
width:400,
height: 400
}
]}
alt="My image"
/>
出力結果
画像は「jpg」「png」デフォルトのフォーマットに加えて、avifとwebpを出力します。また解像度は自動で1xと2xまで出力されます。
<!-- Output Results -->
<picture>
<source media="(max-width: 767px)" width="400" height="400" srcset="./_astro/my-image-sp.hash.avif 1x,./_astro/my-image-sp.hash.avif 2x" sizes="(max-width: 400px) 100vw, 400px" type="image/avif">
<source media="(max-width: 767px)" width="400" height="400" srcset="./_astro/my-image-sp.hash.webp 1x,./_astro/my-image-sp.hash.webp 2x" sizes="(max-width: 400px) 100vw, 400px" type="image/webp">
<source media="(max-width: 767px)" width="400" height="400" srcset="./_astro/my-image-sp.hash.jpg 1x,./_astro/my-image-sp.hash.jpg 2x" sizes="(max-width: 400px) 100vw, 400px">
<source srcset="./_astro/my-image.hash.avif 1x,./_astro/my-image.hash.avif 2x" sizes="(max-width: 1000px) 100vw, 1000px" type="image/avif">
<source srcset="./_astro/my-image.hash.webp 1x,./_astro/my-image.hash.webp 2x" sizes="(max-width: 1000px) 100vw, 1000px" type="image/webp">
<img width="1000" height="800" src="./_astro/my-image.hash.jpg" srcset="./_astro/my-image.hash.jpg 1x,./_astro/my-image.hash.jpg 2x" sizes="(max-width: 1000px) 100vw, 1000px" loading="lazy" decoding="auto" alt="My image">
</picture>
src
このコンポーネントはsrcを設定するだけで使えるようにしています。このsrcはオブジェクト形式で指定します。srcオブジェクトのサイズを基準に複数の解像度の生成基準にするため、幅と高さの指定は必須にしています。
src={{
file:"my-image.jpg", // パス不要でsrc/imagesディレクトリ内にあるファイルを参照
width: 1000, // 画像の幅はNumber型で指定
height: 800 // 画像の高さはNumber型で指定
}}
artDirectives
artDirectivesはレスポンシブイメージのためのもので、先ほどのsrcオブジェクトにmedia属性を加えたオブジェクトを配列形式で指定します。media属性は配列順で出力されます。以下は使用例です。
artDirectives={[
{
media:"(max-width: 1200px)",
file:"my-image-medium.jpg",
width:600,
height: 400
},
{
media:"(max-width: 767px)",
file:"my-image-small.jpg",
width:400,
height: 400
}
]}
<!-- Output Results -->
<source media="(max-width: 1200px)" width="600" height="400" srcset="./_astro/my-image-medium.hash.avif 1x,./_astro/my-image-medium.hash.avif 2x" sizes="(max-width: 600px) 100vw, 600px" type="image/avif">
<source media="(max-width: 1200px)" width="600" height="400" srcset="./_astro/my-image-medium.hash.webp 1x,./_astro/my-image-medium.hash.webp 2x" sizes="(max-width: 600px) 100vw, 600px" type="image/webp">
<source media="(max-width: 1200px)" width="600" height="400" srcset="./_astro/my-image-medium.hash.jpg 1x,./_astro/my-image-medium.hash.jpg 2x" sizes="(max-width: 600px) 100vw, 600px">
<source media="(max-width: 767px)" width="400" height="400" srcset="./_astro/my-image-small.hash.avif 1x,./_astro/my-image-small.hash.avif 2x" sizes="(max-width: 400px) 100vw, 400px" type="image/avif">
<source media="(max-width: 767px)" width="400" height="400" srcset="./_astro/my-image-small.hash.webp 1x,./_astro/my-image-small.hash.webp 2x" sizes="(max-width: 400px) 100vw, 400px" type="image/webp">
<source media="(max-width: 767px)" width="400" height="400" srcset="./_astro/my-image-small.hash.jpg 1x,./_astro/my-image-small.hash.jpg 2x" sizes="(max-width: 400px) 100vw, 400px">
alt, class, style, loading, decoding
その他、imgタグで使われる属性も追加したり変更が可能です。
背景用のコンポーネント
その他、背景コンポーネントも用意しました。スタイルはタグで出力せずにastroコンポーネントのスタイルで出力しているため、ビルド時に他のアセットと一緒に統合されるので使いやすいかと思います。astro-imagetoolsにあるようなplaceholderは対応していません。
BackgroundPicture
import { BackgroundPicture } from 'astro-simple-art-direction';
<BackgroundPicture
TagName="section"
image={{
src: {
file:"my-image.jpg",
width: 1000,
height: 800
}
}}
/>
<h1>astro-simple-art-direction</h1>
</BackgroundPicture>
<!-- Output Results -->
<section data-astro-hash class="bgp">
<figure aria-hidden="true" data-astro-hash style="--imageWidth: 100%;--imageHeight: 100%;--attachment: cover;">
<picture>
<source srcset="./_astro/my-image.hash.avif 1x,./_astro/my-image.hash.avif 2x" sizes="(max-width: 500px) 100vw, 500px" type="image/avif">
<source srcset="./_astro/my-image.hash.webp 1x,./_astro/my-image.hash.webp 2x" sizes="(max-width: 500px) 100vw, 500px" type="image/webp"> <img width="500" height="2000" src="./_astro/my-image.hash.jpg" srcset="./_astro/my-image.hash.jpg 1x,./_astro/my-image.hash.jpg 2x" sizes="(max-width: 500px) 100vw, 500px" loading="lazy" decoding="auto" alt="">
</picture>
</figure>
<div class="bgp-inner" data-astro-hash style="--imageWidth: 100%;--imageHeight: 100%;--attachment: cover;">
<h1>astro-simple-art-direction</h1>
</div>
</section>
BackgroundImage
import { BackgroundImage } from 'astro-simple-art-direction';
<BackgroundImage TagName="section" image={ {src:{file:"my-image.jpg", width:500, height:2000}} }>
<h1>astro-simple-art-direction</h1>
</BackgroundImage>
<!-- Output Results -->
<section style="background-image: url(./_astro/my-image.hash.jpg);">
<h1>astro-simple-art-direction</h1>
</section>
最後に
Astroで静的サイトを作成するときは画像の出力については当面このコンポーネントを使っていこうと思います。ただ今はアップデートの変化が激しいため、また組込みのコンポーネントが汎用性があるものになればそちらに切り替えるかしれません。
出力コードの解説は気が向いたらまた別の機会に書こうと思います。