🐣

コンテナクエリを使いそうなところだけざっくりと

2023/08/15に公開

さて、前の記事でビューポートの幅に対してレスポンシブを頑張っても限界があることがわかりました。

直近の案件で久しぶりにサイドバーがあるタイプの2カラムのレイアウトに対峙し、コンテナクエリを使用してみたのですが、途中から綺麗とは言い難いコードになってしまい反省しています。

二度と同じ様なことがおこらないようにある程度の方針を立てておこうと思います。

コンテナクエリの対象

まずは基本の型から。

.parent {
  container-type: inline-size;
}
@container (width < 640px) {
 .child {
    background-color: blue;
  }
}

こんな感じですね。親の横幅が640pxを下回ったら効く感じです。VSCodeでエラーっぽくなるのが気持ち悪い。

そして残念ながらいつもこれほど綺麗な親子関係ではないでしょう。時には祖先の横幅を基準にしたい場合があります。

そんな時はこう。

.grandMother {
  container-type: inline-size;
  container-name: gmama;
}
@container gmama (width < 640px) {
 .child {
    background-color: blue;
  }
}

class名のように複数のcontainer-nameをつけることもできるようです。

.grandMother {
  container-type: inline-size;
  container-name: gmama ladies;
}
@container gmama (width < 640px) {
 .child01 {
    background-color: blue;
  }
}
@container ladies (width < 480px) {
 .child02 {
    background-color: blue;
  }
}

なんとなく便利そうだけど、使い道があるのかはまだわからない。

とりあえずわかったことは親子関係というか家系を強く意識し、その要素がどの要素の大きさを基準として変化するのかを決めていくのが大事そうだということだ。

これまでは基本的に一律でビューポート(≒画面の横幅)を基準にしていたが、これからはこの要素Aは親P1、この要素Bは親P2を基準にするということを意識する。

どの要素もコンテナになる可能性があるのでcontainer-typeやcontainer-nameが乱立する気もする。

またnameか…。class名を考えるだけでも大変なのになぁ。class名と同じでもいいのかな。

名前をつければいいというものではない様子

名前をつければどんな要素でもコンテナの対象になるかというとそうでもないらしい。

一旦は自分の祖先だけという理解をしておいた方が安全そうだ。

となると今度は祖先同士の戦いがどうなるのかが気になる。

まずはこれ

<div class="wrap wrap01">
  <div class="wrap wrap02">
    <div class="wrap wrap03">
      <p class="child">誰が基準になる?</p>
    </div>
  </div>
</div>
.wrap {
  padding-inline: 100px;
}
.wrap03,
.wrap02,
.wrap01 {
  container-type: inline-size;
}
@container (width < 640px) {
  .child {
    background-color: blue;
  }
}

一番直近の親、wrap03が基準になるようです。まぁ想像通り。

では名前をつけると?

.wrap03,
.wrap02,
.wrap01 {
  container-type: inline-size;
}
.wrap02 {
  container-name: neco;
}
@container neco (width < 640px) {
  .child {
    background-color: blue;
  }
}

まぁ当然ですが、wrap02が基準になります。

では名前をつけたうえで、@container では名前を指定しないと・・・

.wrap03,
.wrap02,
.wrap01 {
  container-type: inline-size;
}
.wrap01 {container-name: inu;}
.wrap02 {container-name: neco;}
.wrap03 {container-name: usagi;}
@container (width < 640px) {
  .child {
    background-color: blue;
  }
}

まぁ、そりゃあwrap03が基準ですよね。

じゃあ、全部指定すると?

.wrap03,
.wrap02,
.wrap01 {
  container-type: inline-size;
}
.wrap01 {container-name: inu;}
.wrap02 {container-name: neco;}
.wrap03 {container-name: usagi;}
@container inu (width < 640px) {
  .child {
    background-color: blue;
  }
}
@container neco (width < 640px) {
  .child {
    background-color: red;
  }
}
@container usagi (width < 640px) {
  .child {
    background-color: yellow;
  }
}

黄色になりました。

・・・が、この3個の並び順を変えると結果が変化します。

これはCSSの上書きの問題ですね。どの条件がどのタイミングで発動してどれが一番強くなるのか?

usagiが一番早く640px以下になるので、これが最初に発動します。CSSの順番で最後に@container usagiが書かれていると他のinuやnecoはこれを上書きできません。

逆に、@container neco が最後に書かれている場合、usagiが発動した後にnecoが640px以下になるので、その時点で赤で上書きされます。

はい。混乱の元ですね。

仕様を理解できたらこんな特殊な条件は忘れてしまっていいでしょう。

そもそも基準となるコンテナを複数持たせること自体があまり美しくないと思われますし。

コンテナクエリで新登場の単位

vwと同様なのがcqw、vhと同様なのがcqh。

覚えやすいけど読みにくい?

さて、この指定はコンテナクエリが基準となるだけで考え方は vw や vh と変わらない。

と言うことは前の記事は無駄ではなかった!

つまり「アートボード幅400pxだと計算しやすい」と言っていた「400px」の部分がコンテナクエリの横幅によって変わるというだけだ。

・・・だけ、、、?

ぜんぜん「だけ」じゃない!

1つのサイト(ページ)では基準となるコンテナクエリの幅の種類は多くないだろうけど、それでも3~4種類は覚悟したいし、別の案件になれば全然違ってくる。

ちょっとデザインが変化球なサイトならもっともっと増えてしまう。

うーん。結局は計算機を作ってコピペかなぁ。

■例えばコンテナクエリ幅960pxで20pxを表現したい場合
20px ÷ 960px = 0.02083333333cqw(≒2.0834cqw)

■例えばコンテナクエリ幅400pxで16pxを表現したい場合
16px ÷ 400px = 0.04cqw(=4cqw)

ん、ちょっと混乱。

コンテナクエリが500pxの時、4cqwは20px
コンテナクエリが1000pxの時、4cqwは40px

うん。あれ、これってもっと簡単にcalcできないのか…?

cqw = 指定px/コンテナpx

であってる?

■コンテナが1200pxの時の60pxは何cqw?
cqw = 60px/1200px = 5cqw

■コンテナが740pxの時の40pxは何cqw?
cqw = 40px/740px ≒ 5.41cqw

■コンテナが360pxの時の20pxは何cqw?
cqw = 20px/360px ≒ 5.56cqw

つまり

calc((指定px * 100cqw) / 規準コンテナpx);

/* 【例】 */
padding: calc((20 * 100cqw) / 360);

これで規準コンテナが360pxの時は20px、720pxの時は40pxになる。単位はcqw。

font-sizeもいけるのかな?

.moji {
  font-size: calc((12 * 100cqw) / 360);
}

うん。大丈夫そう。

これで比率がまったく同じなら使える計算式はわかった。

次は難問だ。。。

規準コンテナが360pxの時は20px で 720pxの時は30px となるような cqw をどう求めるのか?

たぶん差を求めて1px辺りにどれくらい増減するのかをcalcするのだろうということはわかる。

360pxと720pxの間は360pxある。この360pxの間に20pxと30pxの間である10px増えるのだ。

日本語で書くと簡単で、規準コンテナが36px大きくなるにつれて1px増えればいい。

では規準コンテナが 1px 増えると指定pxは何px増えるのか? ⇒ 1 ÷ 36 ≒ 0.02778px

ほうほう。数式にすると…

(30px-20px) / (720px-360px)

うん。これは何を求めているのだっけ?

たぶんやりたいのは

clamp(20px,ここの計算式,30px)

たぶんこれが知りたい。

360px ⇒ 20px
396px ⇒ 21px
432px ⇒ 22px
468px ⇒ 23px
504px ⇒ 24px
540px ⇒ 25px
576px ⇒ 26px
612px ⇒ 27px
648px ⇒ 28px
684px ⇒ 29px
720px ⇒ 30px

となる式だ

20 = (1/36) * 360 + 10
21 = (1/36) * 396 + 10
22 = (1/36) * 432 + 10

つまり

y = (1/36)x + 10

36は何だ?(720px-360px)の10分の1
10は何だ?(30px-20px)

うーん。なんかこの公式はたまたまな気がするぞ。10分の1が曲者。

別のパターン(720pxの時50px)で考えると、

y = (1/12)x - 10

…うん。引き算になった。

ここでまた何を計算したいのかわからなくなった。

で、サンプルを作成して色々弄ってみたところ(こちらが3時間煮込んだものです的な)

clamp(20px,calc(20px + (30 - 20) / (720 - 360) * (100cqw - 360px)),30px)

こんな感じになった。

説明は・・・できない! cqw のところは vw でも大丈夫みたい。

clamp(【A】,calc(【A】 + (【B】 - 【A】) / (【C】 - 【D】) * (100cqw - 【D】)),【B】)

【A】最低サイズ
【B】最高サイズ
【C】ミニマムコンテナサイズ
【D】マックスコンテナサイズ

ということだと思う。保証は無い。使いながら修正してみよう。

ただ、こんなに長ったらしいのをいちいちCSSに書いて行く訳にはいかないので、なんとかCSS変数に放り込みたい。

:root {
  --cqw_360-720_20-30:clamp(20px,calc(20px + (30 - 20) / (720 - 360) * (100cqw - 360px)),30px);
  --cqw_360-960_20-30:clamp(20px,calc(20px + (30 - 20) / (960 - 360) * (100cqw - 360px)),30px);
}
.box {
  margin-bottom: var(--cqw_360-720_20-30);
  font-size: var(--cqw_360-960_20-30);
}

こんな感じかな?

うへぇ。全部にこれを書くのは現実的ではないな。どうしよう。

:root {
  --a20px: var(--cqw_360-720_20-30);
}

変数の入れ子で多少はわかりやすくなるけど、何が20pxなのかわからなくなるよね。

結局は規準コンテナが何種類になるのかによってかなり左右される。

それはそうだよなぁ。今までは全部vwで1個だった訳で。

プロパティ名の工夫さえできれば実用に耐えうるのか、とりあえずしばらくは実験だなぁ。

うーん、なんか例外を考え過ぎている気がする割に、本当に出てくる頻出パターンを想像しきれていない気がする。

clampじゃなくてmin、maxで済む場合も多そうだし、すべてを包括しようとするから助長になる。

運用改善を目指そう。

Discussion