📰

Media Query の Range 記法関連メモ

2023/05/03に公開

早見表

意味 (単位:px) 古の記法 (忘れていい) 新しい記法
100 より上 (min-width: 100.01px) (width > 100px)
100 以上 (min-width: 100px) (width >= 100px)
100 以上 200 未満 (min-width: 100px) and (max-width: 199.9px) (100px <= width < 200px)
100 と等しい (min-width: 100px) and (max-width: 100px) (width = 100px)
100 以下 (max-width: 100px) (width <= 100px)
100 未満 (max-width: 99.9px) (width < 100px)
  • 古の記法
    • 書けるのは「min 以上」「max 以下」のみ
    • 100px「より上」を正確に書けない
      • 101px では 100 と 101 の間に隙間ができるため匙加減で 100.01px としている
    • 100px「未満」も難しい
      • こちらも同様に匙加減で 99.9px と書いている
      • よかれと考えて 99.99px と書くと Google Chrome では 100px と見なされる罠がある
  • 新しい記法
    • 等しいは == ではなく = な罠がある
    • width を必ず左に書かないといけないわけではない
    • (100px <= width < 200px) の書き方が気持ち悪いときは
      (100px <= width) and (width < 200px) と書いてもよい
  • 参照

@media の書き方

<div class="panel">
  <p>Hello</p>
</div>

<style>
@media (width >= 1000px) {
  p {
    color: red;
  }
}
</style>
  • 大きさを調べる目的では使わない方がよい
  • 本当はコンテナ幅で調整したいけどできないので仕方なく画面幅を見ているとしたら今後は @container を使った方がよい

@container の書き方

<div class="panel">
  <p>Hello</p>
</div>

<style>
.panel {
  container-type: inline-size;
}

@container (width >= 1000px) {
  p {
    color: red;
  }
}

/* 確認用 */
.panel {
  border: 1px dashed grey;
  resize: horizontal;
  overflow: auto;
}
</style>

@media を @container に置き換える

<div class="panel">
  <p>Hello</p>
</div>

<style>
:root {
  container-type: inline-size;
}
@container (width >= 1000px) {
  p {
    color: red;
  }
}
</style>
  • 一番外側に暗黙の container-type があり @container に対応する container-type がないときは最終的に一番外側、つまり画面の大きさを見ることで @media と互換性を保ったままスムーズに移行ができるのを期待したが、そうはならない
  • container-type を見つけられない @container は意味のない指定となるのでブラウザは CSS であってもエラーを出してくれてもいいのだけどそうもなっていない
  • なので :root で container-type: inline-size を指定しておく
  • これで @media を @container に置き換えられる

Google Chrome は小数の解釈が不自然

<div class="panel">
  <p>Hello</p>
</div>

<style>
.panel {
  container-type: inline-size;
  width: 100px;
}

@container (max-width: 99.99px) {
  p {
    color: red;
  }
}
</style>
  • max-width: 99.99px の意図は「100px 未満」で正確には「99.99px 以下」の意味になる
  • 上のHTMLではコンテナ幅が 100px なのでその条件にマッチしない
  • しかし Google Chrome 112 ではマッチする
    • 99.99px を 100px と見なされたら「100px 未満」が「100px 以下」の意味に変わってしまう
  • 一方 Safari 16.4 はマッチしない (正常な動作)
    • しかし 99.999999999999999px と書けば 100px と見なされマッチする
  • このように小数の解釈が不自然かつブラウザ間での差異もあるため今後は普通に Range 記法を使った方がよい

関連: メディアクエリは 1px ではなく0.02px で切り替える

Bulma のレスポンシブマクロを新しい記法で書く例

@media 版
// The container horizontal gap, which acts as the offset for breakpoints
$gap: 0px !default
// 960, 1152, and 1344 have been chosen because they are divisible by both 12 and 16
$tablet: 769px !default
// 960px container + 4rem
$desktop: 960px + (2 * $gap) !default
// 1152px container + 4rem
$widescreen: 1152px + (2 * $gap) !default
$widescreen-enabled: true !default
// 1344px container + 4rem
$fullhd: 1344px + (2 * $gap) !default
$fullhd-enabled: true !default

=from($device)
  @media screen and (width >= $device)
    @content

=until($device)
  @media screen and (width < $device)
    @content

=mobile
  @media screen and (width < $tablet)
    @content

=tablet
  @media screen and (width >= $tablet), print
    @content

=tablet-only
  @media screen and ($tablet <= width < $desktop)
    @content

=touch
  @media screen and (width < $desktop)
    @content

=desktop
  @media screen and (width >= $desktop)
    @content

=desktop-only
  @if $widescreen-enabled
    @media screen and ($desktop <= width < $widescreen)
      @content

=until-widescreen
  @if $widescreen-enabled
    @media screen and (width < $widescreen)
      @content

=widescreen
  @if $widescreen-enabled
    @media screen and (width >= $widescreen)
      @content

=widescreen-only
  @if $widescreen-enabled and $fullhd-enabled
    @media screen and ($widescreen <= width < $fullhd)
      @content

=until-fullhd
  @if $fullhd-enabled
    @media screen and (width < $fullhd)
      @content

=fullhd
  @if $fullhd-enabled
    @media screen and (width >= $fullhd)
      @content
@container 版
// The container horizontal gap, which acts as the offset for breakpoints
$gap: 0px !default
// 960, 1152, and 1344 have been chosen because they are divisible by both 12 and 16
$tablet: 769px !default
// 960px container + 4rem
$desktop: 960px + (2 * $gap) !default
// 1152px container + 4rem
$widescreen: 1152px + (2 * $gap) !default
$widescreen-enabled: true !default
// 1344px container + 4rem
$fullhd: 1344px + (2 * $gap) !default
$fullhd-enabled: true !default

=from($device)
  @container (width >= #{$device})
    @content

=until($device)
  @container (width < #{$device})
    @content

=mobile
  @container (width < #{$tablet})
    @content

=tablet
  @container (width >= #{$tablet})
    @content

=tablet-only
  @container (#{$tablet} <= width < #{$desktop})
    @content

=touch
  @container (width < #{$desktop})
    @content

=desktop
  @container (width >= #{$desktop})
    @content

=desktop-only
  @if $widescreen-enabled
    @container (#{$desktop} <= width < #{$widescreen})
      @content

=until-widescreen
  @if $widescreen-enabled
    @container (width < #{$widescreen})
      @content

=widescreen
  @if $widescreen-enabled
    @container (width >= #{$widescreen})
      @content

=widescreen-only
  @if $widescreen-enabled and $fullhd-enabled
    @container (#{$widescreen} <= width < #{$fullhd})
      @content

=until-fullhd
  @if $fullhd-enabled
    @container (width < #{$fullhd})
      @content

=fullhd
  @if $fullhd-enabled
    @container (width >= #{$fullhd})
      @content
  • @container 版は @media 版を元に新しく作った
  • @container の条件に screen や print があると動かないので取った
  • Dart Sass 1.62.1 で @container (width < $tablet) の行が正しく解釈されなかった
    • 一方 @media (width < $tablet) だと通るので Dart Sass は @container を予約語だと認識していないというか @media を特別扱いしていると思われる
    • どうなってんのかよくわからないが @container (width < #{$tablet}) としたら通った

Discussion