こなさんみんばんわ。

2 月 2 日に公開した記事の締めのところで今回のサンプルには当初の主目的からすると完全に欠けてる部分があるとして、auto-fillauto-fit の切り替え機能が実装されてないことに言及しておりましたが、本日さっそくその機能を追加しましたので、前回同様にその作業内容をまとめてみました。有言実行してえら〜い!(cv: コウペンちゃん)

あと、その際に中途半端にしか説明をしてなかった 2 つのキーワードの挙動の違いにつきましても、この機能追加したサンプルでのスクリーンショットを交えつつ説明をしております。えっもちろん実際に見ながらの方が理解しやすいかなーと思ってあえてそうしたんですよ、当たり前やないですか🤪

その前に、なぜこの機能を付けてなかったか

それは単純に、キーワード部分はカスタム・プロパティで置き換えることができないのでは? という認識不足からですね。つまり、次のように書いても通らないだろうと思ってたんですよ。

:root {
  --repeat-mode: auto-fill;
}
ul {
  grid-template-columns: repeat(var(--repeat-mode), minmax(var(--grid-min-size, 100px), 1fr));
}

で、そうなると直接 grid-template-columns の部分の宣言そのものを書き換えないといけない訳で、それは書くことが長すぎてめんd…大変だよなぁと思って、とりあえず後回しにしておりました。

ですが、先日の記事を書いてる途中でふと試してみると、カスタム・プロパティに置き換えても普通に通ることが確認できてなーんや! となりましたので、それだったら付けてみようということになった訳です。

機能追加…の前に、モバイル・ファーストっぽく書き換えておく

それではさっそく実装していきましょう! なのですが、その前に前回までのコードを若干変更しまして、モバイル・ファーストというか、狭い画面のスタイルを基準に、広い画面の場合にスタイルを上書きするようにしておきます。あと、そうなるとあえて Range 書式を使う意味もなくなるので、メディアクエリーの書き方も旧来のものに戻しておきます。これで意図した表示が適用されるブラウザが少し増えるかと思います。

修正したところ抜粋。順に CSS, HTML, JavaScript。

:root {
  /*
    --small-image-size をベースに、幅 640px 以上で
    --large-image-size が適用されるように変更
   */
  --grid-min-size: var(--small-image-size);
  @media (min-width: 640px) {
    --grid-min-size: var(--large-image-size);
  }
}
<form>
  <label>画像の最小幅:
  <!-- デフォルトの変更に合わせて value を 50 に、max を 120 に -->
    <input type="range" min="20" max="120" value="50" id="slider">
  </label>
  <!-- 変更した value に合わせて 50px に -->
  <output id="monitor">50px</output>
</form>
// 幅 640px 以上の画面では max を 240 に
if (window.matchMedia("(min-width: 640px)").matches) {
  slider.max = 240;
}

切り替え用のチェックボックスを付ける

今回は簡単にチェックボックス 1 つで対応しようと思います。デフォルトは auto-fill で、チェックが入った状態では auto-fit になるようにしてみます。

ということで、まずは切り替え用のチェックボックスを追加します。あとでスクリプトからチェックするのはもう分かりきってることなので、最初から id 属性も振っておきましょう。

<form>
  <!-- 省略 -->
  <output id="monitor">50px</output><br>
  <!-- 以下を追加 -->
  <label><input id="mode-switch" type="checkbox"> <code>auto-fit</code> に変更する</label>
</form>

auto-fill キーワードをカスタム・プロパティに置き換える

次に --repeat-mode というカスタム・プロパティを定義して、デフォルトを auto-fill にします。

:root {
  /* これ以外は変更がないので省略 */
  --repeat-mode: auto-fill;
}

そして grid-template-column のところをこのカスタム・プロパティを使って書き換えます。

ul {
  /* これ以外は変更がないので省略 */
  grid-template-columns: repeat(var(--repeat-mode, auto-fill), minmax(var(--grid-min-size, 100px), 1fr));
}

あっちなみにプロパティ値の方もフォールバック値の方も、どちらも "auto-fill" って引用符で括っちゃダメですよ。括ると文字列にされてしまって repeat() 関数内で使える有効なキーワードじゃなくなっちゃいますからね。気をつけましょう!(そんな勘違いをされる方はおられないかと思いますが、念のために書いておきました)

切り替えコードを実装して、完成

あとは JavaScript ですね。こんな感じでいかがでしょう。

// 前準備の最後に追加
const modeSwitch = document.getElementById("mode-switch");

// 初期化処理の最後に追加
// Firefox が再読み込みで状態をリセットしてくれないのを Fix
modeSwitch.checked = false;

// スライダーメイン処理の後ろに追加
// チェックボックスの状態によって auto-fill / auto-fit を切り替える
//(チェックあり → auto-fit・なし → auto-fill)
modeSwitch.addEventListener("input", (e) =>{
  thumbnails.style.setProperty("--repeat-mode",
    e.target.checked ? "auto-fit" : "auto-fill"
  );
});

単純にチェックボックスにイベントが発生したらその状態を調べて、チェックが入っていればカスタム・プロパティの値を auto-fit に、外れていれば auto-fill にと、三項演算子で出力してるだけですね。

という訳で、完成したものがこちらです。あっちなみに今回のサンプルも別タブで開くようにしております。

さわって理解する auto-fillauto-fit の挙動の違い

それでは、サンプルを色々な端末で、チェックボックスにチェックを入れたり外したりしながら、スライダーを動かして遊んでみましょう。さて、どうなるでしょう?

アイテムが複数行に渡る場合 → 違いはない

まず画像が複数行に渡って並んでる場合ですが、この場合はチェックボックスにチェックを入れても、特に何も変化はないですよね。これは別に書いたコードがバグってる訳ではなくて😅 この段階ではそれが正常なんです。

グリッド幅 100px でチェックなし = auto-fill の場合(上)とチェックあり = auto-fit の場合(下)のスクリーンショット。アイテムが複数行に渡って並んでいる状態では、どちらでも挙動に違いはない
グリッド幅 100px でチェックなし = auto-fill の場合(上)とチェックあり = auto-fit の場合(下)のスクリーンショット。アイテムが複数行に渡って並んでいる状態では、どちらでも挙動に違いはない

アイテムが一行に収まる場合 → 違いが発生

では、この状態からそれぞれスライダーをめいっぱい左に寄せて、画像の幅を最小の 20px に近づけていってみてください。あっスマホでやられる場合は縦向きだと最小にしても複数行のままなんで、端末を横向きにして試さないとたぶん「えっ何が違うん?」ってなります。涙

どうですか? チェックを外した状態 (auto-fill) では画像はどんどん小さくなっていきますが、チェックを入れた状態 (auto-fit) では、ある一定のところまでいくと画像がそれ以上小さくならないですよね? どうやらこれが auto-fillauto-fit の違いのように思えます。

グリッド幅 20px でチェックなし = auto-fill の場合(上)とチェックあり = auto-fit の場合(下)のスクリーンショット。auto-fit の場合はある一定のサイズまでいくとそれ以上は小さくならないことが分かる
グリッド幅 20px でチェックなし = auto-fill の場合(上)とチェックあり = auto-fit の場合(下)のスクリーンショット。auto-fit の場合はある一定のサイズまでいくとそれ以上は小さくならないことが分かる

2 つの違いは余ったスペースの埋め方…ではない

つまり auto-fillauto-fit の違いとは、指定のグリッド幅ですべてのアイテムを並べてもコンテナ幅を埋め尽くせない場合に、auto-fill は余ったスペースに新しいグリッドを追加できるだけ追加して埋める、auto-fit は余ったスペースを既存のアイテムを拡大することで埋める…といいたいところですが、これは正確ではありません。

実はどちらの場合も、空のグリッドを追加してるのは一緒なんです。試しに開発者ツールでグリッド・オーバーレイを表示してみると、auto-fill であろうが auto-fit であろうが、最後のグリッド線の番号は一緒であることが分かるかと思います。

グリッド幅が 20px の時にグリッド・オーバーレイを表示してみると、auto-fill の場合(上)も auto-fit の場合(下)も、最後のグリッド線の番号は 33 で変わらないことが分かる
グリッド幅が 20px の時にグリッド・オーバーレイを表示してみると、auto-fill の場合(上)も auto-fit の場合(下)も、最後のグリッド線の番号は 33 で変わらないことが分かる

ではどこに違いがあるかというと、auto-fill で追加された空のグリッドは空でないグリッドと同じ幅を持ちますが、auto-fit で追加された空のグリッドは折りたたまれる…つまり幅はゼロになり、間の溝 (gap) もないものとして扱われます。違いは実はそれだけなのです。

えっじゃあ auto-fit の時に幅が一定以上小さくならないのはなぜかって? それは単に minmax() 関数の最大値に 1fr って入れてるからですね🤣 自分でそう指定したから、空のグリッドが折りたたまれてできたスペースを使ってるってだけの話なのです。信じられないなら開発者ツールなどで 1fr の代わりに 30px とかの固定値を入れてみるといいですよ、普通にスペースが空きますから。

そんな訳で

本日は、前回の記事で作成したスライダーで拡大縮小できる画像一覧に、本来の主目的であった auto-fillauto-fit の挙動の違いを確認するための機能として、この 2 つをチェックボックスで切り替えられる機能を実装した作業のまとめと、完成したものを使って作成したスクリーンショットを交えて、2 つのキーワードの挙動の違いを解説してみました。

まぁ正直僕も最初は「違いは余ったスペースの埋め方なんだな」みたいな雑な認識をしてたんですよ。実際にスライダー動かしながらグリッド・オーバーレイの変化を眺めててあれっ? てなって、あらためて学び直してみたような感じですので、あんまり偉そうにはできんのでした。涙

という訳で、こんな解説でご理解いただけましたでしょうか? 上手く伝わったかどうかちょっと心配ですが、お役に立てましたら幸いです。