💨

Astroで背景画像の最適化にチャレンジしたけど無理だった

2024/02/06に公開
2

2024.05.21 追記
実現できます!コメントありがとうございました!
https://zenn.dev/takkyun/articles/cc33c1425cc3b4


前回の記事でPictureコンポーネントを自作し、アートディレクションを行うことができました。
今回は背景画像にもアートディレクションと最適化処理についてチャレンジしてみました。
https://zenn.dev/tam_tam/articles/d989323e7bbdab

背景画像のアートディレクションは難しい

*アートディレクション:media="(min-width: 1024px)"のような画面幅での切り分け

Astroのフロントマターと呼ばれる「---」で囲われる部分で行なった処理はHTML側でしか呼び出せないため、CSSやJavaScript側で変数を呼び出すことができません。

---
const sampleID = "sample";
const background = "/img/bg.jpg";
---

<script>
// この中では使えない
</script>

<div id={sampleID} style={`background-image:url(${background})`}>HTMLでは使える</div>

<style>
// この中では使えない
</style>

背景画像最適化案1 astro-imagetools

画像コンポーネント以外にも背景画像にも対応し、アートディレクションも行えるパッケージです。
https://astro-imagetools-docs.vercel.app/en/components-and-apis

やりたいことが満たされているのでこれでいけそうでしたが、qiitaの記事に書いてある通り不要なコードが入ってきてコントロールが難しそう。
https://qiita.com/heeroo_ymsw/items/07a4dbeac9515559694f

実際に試したけど、breakpointやmedia指定で画面幅指定しても変わってくれなかった。
単純にPC/SP切り替えたいだけの用途にはオーバースペック感。
細かい調整をこちらでコントロールしたい。

背景画像最適化案2 自作する

pictureタグを自作した要領で試したがこれもうまくいかなかった。

フロントマターはHTMLタグにだけ適用できますが、style属性にはmedia指定ができないので以下のような指定では動きません。

---
import { getImage } from 'astro:assets';

const mobileImage = await getImage({ src: '/path/to/mobile-image.jpg', width: 600 });
const desktopImage = await getImage({ src: '/path/to/desktop-image.jpg', width: 1200 });
---

<div 
  style={{
    backgroundImage: `url(${mobileImage.src})`,
    '@media (min-width: 960px)': {
      backgroundImage: `url(${desktopImage.src})`
    }
  }}
>これは機能しません。</div>

CSS側にフロントマターの変数を適用することもできませんでした。

---
import { getImage } from 'astro:assets';

const mobileImageUrl = (await getImage({ src: '/path/to/mobile-image.jpg', width: 600 })).src;
const desktopImageUrl = (await getImage({ src: '/path/to/desktop-image.jpg', width: 1200 })).src;
---

<div class="responsive-background"></div>

<style>
// これも機能しません
  .responsive-background {
    background-image: url('${mobileImageUrl}');
  }

  @media (min-width: 960px) {
    .responsive-background {
      background-image: url('${desktopImageUrl}');
    }
  }
</style>

背景画像最適化案3 Backgroundコンポーネントを自作する

astro-imagetoolsを習ってBackgroundコンポーネントを作ってpropsで渡す形も考えたのですが、結局HTML側で振り分けをする方法がないのでこれもボツになりました。

Astro側で最適化処理を行わないという選択

ちょっと煮詰まったので最初の目的を確認します。
・画像ディレクトリ統一
・画像最適化
・pictureでアートディレクション
・背景画像(CSS)でもアートディレクション
・ビルド時に最適化

「ビルド時に最適化」を外せばAstro使わなくてもできそう。。

ということで無理にAstroでやらなくてもいいんじゃないかという結論に至りました。

Sharpを使って別途最適化処理

全てをpublicディレクトリに格納し、別途最適化の処理を行う方法を模索します。

Astro内部でも使っているSharpを個別で使って最適化します。
パッケージをインストール

npm i chokidar sharp

publicのimg配下にあるpngとjpg拡張子のファイルをwebpに変換するスクリプトです。

optimizedImages.js
const sharp = require("sharp");
const chokidar = require("chokidar");

const directoryPath = "public/img/**";

const convertToWebP = (filePath) => {
  const outputFilePath = filePath.replace(/\.(png|jpg|jpeg)$/, ".webp");

  sharp(filePath)
    .toFormat("webp")
    .toFile(outputFilePath, (err) => {
      if (err) {
        console.error("Error converting image:", err);
      } else {
        console.log(`Converted ${filePath} to WebP`);
      }
    });
};

const watcher = chokidar.watch(directoryPath, { ignored: /^\./, persistent: true });

watcher
  .on("add", (filePath) => {
    if (/\.(png|jpg|jpeg)$/.test(filePath)) {
      convertToWebP(filePath);
    }
  })
  .on("error", (error) => console.error(`Watcher error: ${error}`));

package.jsonに登録しておき、

"img": "node optimizedImages.js",

npm run imgで処理を走らせます。
ビルド時の最適化という目的は果たせなかったですが、
・画像ディレクトリ統一
・画像最適化
・pictureでアートディレクション
・背景画像(CSS)でもアートディレクション
は達成できました。

画像ディレクトリをpublic内に統一した関係で、前回自作したPictureコンポーネントは不要になってしまったのですが目的は果たせたので良しとします!

背景画像もAstroで最適化する方法あったら教えてください!

TAM

Discussion

Takuya OtaniTakuya Otani

css 変数を使うと最適化された画像を参照することができます。

https://zenn.dev/takkyun/articles/cc33c1425cc3b4

で紹介してみました。参考になれば。

kinshistkinshist

うおー!ありがとうございます!
なるほど、define:vars 使えば確かにいけますね。
そもそも知らなかったので参考になりました。

次の案件で取り入れてみますね!