BeAct Co., Ltd.

BLOG
社員ブログ

CSSはここまで進化した

こんにちは! 今日もコーディングを楽しんでいますか?

さて、以前の記事で:

近ごろのCSSは、想像を超えるほど高機能になってきていて、「CSSだけで、ここまでできるの?!」と驚くばかりです。

と書きましたが、具体的にどんなふうに進化したのか? 気になるところですよね。こんかいは、「JavaScriptなしのCSSだけで、ここまでできる!」という例を、いくつか紹介してみたいと思います。

注意:

当記事で取り上げるCSSは、ChromeおよびSafariの最新版であれば、デスクトップ版・スマートフォン版をとわず動作します。

本記事ではSafariの最低バージョン16.5と想定していますが、Safariのバージョンは、お使いのOSバージョンに依存しています。そのため、OSバージョンがそれ未満だと、コード例が動作しません。もしOSが最新版ではない、あるいはアップグレードしたくない場合は、Firefoxブラウザ(無料)を導入してみてください。


チェックボックス

フォーム部品の「四角いやつ」でおなじみの要素です。マークアップは全部同じなのに、CSSによってこんなにも面白い表現ができるようになりました。

See the Pen CSS2024の例1 by isobeact (@rmzpacpm-the-builder) on CodePen.

特にトグルスイッチのような見せ方や、記事を掘り下げていくドリルダウンのような見せ方が、CSSだけでできるだなんて信じられませんよね!

ラジオボタン

もともとの「複数のうちひとつしかチェックできない」という性質を利用して、こんな見せ方ができるようになりました。

See the Pen CSS2024の例2 by isobeact (@rmzpacpm-the-builder) on CodePen.

念のために、このタブのUI自体も、ラジオボタンでできています!

スナップスクロールとスナップ合わせ位置

手動でのスクロール操作で、子要素の位置に磁石で吸い付くように「カチッ」と止める(スナップする)事ができるようになりました(これは Safari 11 から対応しているので、心配ないでしょう)。

スナップの強さは、mandatory(必須=強い)かproximity(おおよそ=弱い)かを選べます。例で言えば、縦長の写真の閲覧中、強制スナップがじゃまになってしまう場合などに、proximityが役立ちます。

また子要素に対しては、スナップの「合わせ位置」をどこにするかも、start(先頭)、center(中央)、end(末端)を選べます。

See the Pen CSS2024の例3 by isobeact (@rmzpacpm-the-builder) on CodePen.

ではここからは、上記の例で、CSSの進化した点を説明していきます。


擬似クラス:has()という福音

上記の例のCSSを確認してみてください。セレクタの中に、やたらと:has()という構文が現れているのがわかると思います。これはCSS4で実装された「関係擬似クラス(Reletional Pseudo-class)」と呼ばれるもののひとつです。

ここでいう関係とは、中学英語の授業で習う「関係代名詞」(Relative pronoun)の「関係」(Relation)と同じです。この構文は、

(同じセレクタでも)特定の文脈に関係付けられたセレクタ

を表現するために生み出されました。

基本の使い方

実例をあげましょう。このようなマークアップを想定してみます。

<label class="button-like">
  <input type="checkbox">
  まるでボタンみたい!
</label>

このとき、CSSがこうであったとすると、どのように解釈されるでしょうか?

.button-like:has(input) {
  background: #aaa;
}

これは、英語調に書くと 、

a .button-like class that has input element.

すなわち

input要素を子孫に持つ.button-likeクラス。

と解釈されます。

重要なこととして、セレクタはあくまでも.button-likeであり、input要素の記載は、あくまでも.button-like絞り込みの条件に過ぎません

:has()の中には「セレクタ」を書く

この:has()の中に書く「絞り込み条件」には、左側に書かれたセレクタから見た、セレクタが入ります。正しいセレクタ記法であれば、より複雑な指定も可能です。たとえば、

.button-like:has(+ h3) {
  background: #8f8;
}

は、「h3要素を直後の妹要素として持つ.button-like」と解釈されます。

.button-like:has(+ h3 > input) {
  background: #8f8;
}

は、「input要素を直接の子として持つh3要素を、直後の妹要素として持つ、.button-like」と解釈されます。

.button-like:has(+ h3 > input:checked) {
  background: #8f8;
}

は、「:checkedであるinput要素を、直接の子として持つh3要素を、直後の妹要素として持つ、.button-like」と解釈されます。

.button-like:has(+ h3 > input[value="4"]:checked) {
  background: #8f8;
}

は、「:checkedであり、かつ、value属性が"4"であるinput要素を、直接の子として持つh3要素を、直後の妹要素として持つ、.button-like」と解釈されます。

「特定の条件を満たしたセレクタ」からのセレクタが定義できる

さて、こうした関係疑似クラスは、これまでわれわれが夢見て、それでも不可能だった、以下の表現を可能にします!

.button-like:has(input[value="4"]:checked) .grid {
  grid-template-rows: 1fr;
}

:checkedであり、かつ、value属性が"4"であるinput要素を子孫として持つ、.button-likeの子孫である、.grid

どうですか、この持って回ったような言い回し! まさに中学英語で習った「関係代名詞」そっくりですね!

しかし、これによって「特定の条件を持ったセレクタに対する子孫、兄弟要素」に対して、スタイルを当てる事が可能となったのです。これはCSSにおける一大革命と言えます!

CSS定義の入れ子

ついに通常のCSSも、SCSS(SASS)同様に、入れ子が可能となりました! ただし、Safariではバージョンによりどこまでが許可されるのかが変わるので、目印をつけておきます。

&(入れ子セレクタ)によるCSS定義の入れ子

先の例のCSSには、:has()よりももっと多くの&記号が出現しています。SCSS(SASS)をご存知であればわかりやすいと思いますが、この記号は「親セレクタの…」を意味します。違いは、SCSSでは{ }の中に別のセレクタ名称として「親セレクタの名称」を使い回せるのに対し、CSSではそれが禁止されていることです。

/* SCSS の場合 */
.wrapper {
  color: black;

  /* いわゆるBEM記法を実現する書き方 */
  &__content {
    /* `.wapper__content` と解釈される */
    color: red;
  }
}
/* CSS入れ子記法 の場合 */
.wrapper {
  color: black;

  /* これは無効なCSSとなる! */
  &__content {
    color: red;
  }
}

それ以外であれば、&を省略しない限り、SCSSと同様の書き方が可能です。

/* SCSS の場合 */
.wrapper {
  color: black;

  /* そのセレクタへの擬似クラスや疑似要素の場合、 `&` をつける */
  &:hover {
    /* `.wapper:hover` と解釈される */
    color: red;
  }

  /* 単純な子孫セレクタの場合、 `&` はいらない */
  .main {
    /* `.wapper .main` と解釈される */
  }

  /* その他のセレクタでも、 `&` はいらない */
  + footer {
    /* `.wrapper + footer` と解釈される */
    color: gray;
  }

  /* 親セレクタ名を後ろに繰り返すことは可能。その際は `&` で親セレクタを明示する */
  .main & {
    /* `.wapper .main .wrapper` と解釈される */
  }
  /* 連結セレクタを表現する場合は、&を前にして書く */
  &.main {
    /* `.wrapper.main` と解釈される */
  }
  /* `&` とセレクタを連結子なしでつなげると、セレクタの名前が繰り返される */
  &header {
    /* `.wrapperheader` と解釈される */
  }
}
/* CSS入れ子記法 の場合 */
.wrapper {
  color: black;

  /* SCSSと同じ使い方 */
  &:hover {
    /* `.wapper:hover` と解釈される */
    color: red;
  }

  /* もし `&` をつけないと… */
  :hover {
    /* `*:hover` と解釈されてしまう! */
    color: red;
  }

  /* 親セレクタの明示として `&` が必須! */
  & .main {
    /* `.wapper .main` と解釈される */
  }

  /* 親セレクタの明示として `&` が必須! */
  & + footer {
    /* `.wrapper + footer` と解釈される */
    color: gray;
  }

  /* 親セレクタ名を後ろに繰り返すことは可能。その際は `&` で親セレクタを明示する */
  & .main & {
    /* `.wapper .main .wrapper` と解釈される */
  }
  /* 連結セレクタを表現する場合は、&を前にして書く */
  &.main {
    /* `.wrapper.main` と解釈される */
  }
  /* `&` とセレクタを連結子なしでつなげることは、 `&要素名` だけが認められる */
  &header {
    /* 要素名を先頭にした `header.wrapper` と解釈される */
  }
}

&を省略したCSS定義の入れ子

SCSSと比べ、先のCSSは、どうしても&が目ざわりですね。一般的なモダンブラウザ、および Safari 17.2 以降であれば、もっと単純に記載することができます!

/* CSSモダン入れ子記法 の場合 */
.wrapper {
  color: black;

  /* 親セレクタの明示は不要! */
  .main {
    /* `.wapper .main` と解釈される */
  }

  /* 親セレクタの明示は不要! */
  + footer {
    /* `.wrapper + footer` と解釈される */
    color: gray;
  }

  /* 親セレクタ名を後ろに繰り返すことは可能ですが、その際は `&` を省略できません */
  .main & {
    /* `.wapper .main .wrapper` と解釈される */
  }
  /* 連結セレクタを表現する場合も、残念ながら `&` 省略できません */
  &.main {
    /* `.wrapper.main` と解釈される */
  }
  /* `&` とセレクタを連結子なしでつなげることは、 `&要素名` だけが認められるのは同じです */
  &header {
    /* 要素名を先頭にした `header.wrapper` と解釈される */
  }
}

SCSSの便利さとスタイル詳細度のコントロールを熟知している方は、&記号の使い方に気をつけつつ、入れ子でCSSを書くことに挑戦してみましょう!

まとめ

いかがでしたか? 本記事では、もはやJavaScriptを使うことなく、CSSだけで様々なUIを実現できるようになった事例の、ほんの一部をご紹介しました。他にもFlexレイアウトの進化形である「Gridレイアウト」、段組みによる文字の流し込みを実現する「カラムレイアウト」、ヘッダーやフッターをスクロール部分に固定する「粘着ポジション」など、紹介しきれなかった項目はまだまだあります! それらはいずれ、機会がありましたらご紹介しましょう。

こんかいの記事は、以上です。それでは、よきコーディングライフを!