2024-01-15

2023年実装されたCSSの機能をいろいろ使ってみた

概要

去年ブログを書くつもりだったけど年をまたいでしまった。

2023年もInterop 2023の取り組みもあってか、沢山の機能がブラウザに実装された。

とは言っても、新しい機能はなかなか実務ですぐに使う機会もなく、使い方がいまいちピンとこないものもあったので、いろいろと機能を詰め込んだWebサイトを作った。2023年だけでなく、ここ数年実装された機能も含まれている。 無理やり詰め込んだ機能もあるけど、なぜ使ったか、どういう使い方をしたかなどを簡単に紹介。

※ 記事内の「全ブラウザ」はChrome, Edge, Safari, Firefoxを指します。

作ったサイト

ポケミクって何?という方は公式を見てください。

使用した技術

  • Vue.js
  • Nuxt3

@layer

@layer / MDN
Interop 2022 で実装され、すでに全ブラウザで使用可能。

@layer は CSS のアットルールで、カスケードレイヤーを宣言するために使用し、また複数のカスケードレイヤーがある場合に、優先順位を定義するためにも使用することができます。

該当ソースコード

@import url('~/assets/css/destyle.css') layer(base);

@layer を使ってみて

安易に使おうとすると今後自分の首を締めることになりそうだし、実務で使おうものなら、考えてCSS設計しないと大きな負債となりそうと感じて、なかなか使う機会がこなかった。使用しているサイトもなかなかお目にかかることはないけど、唯一脳死でやってしまってもいいかなと考えてるのが base レイヤーとして設定すること。リセットCSSであったり、FLOCSS でいう Foundation レイヤーにあたるものはレイヤー設定して、レイヤー未設定CSSが必ず適用されるようにする。

@scope

CSS Cascading and Inheritance Level 6 / W3C
現在 chromium ベースの ChromeEdge のみ。今後もどうなるかまだわからない。

@scope 簡易説明

  • スコープ内の要素にのみスタイルを適用する。
  • 詳細度が同じ場合、スコープルートへ近いCSSが適用される近接性という概念がある。

@scope 実装例

該当ソースコード

<div class="monster-ball" :class="{ 'is-shake': isShake }">
  <div class="top"></div>
  <div class="button"></div>
  <div class="bottom"></div>
</div>
@scope (.monster-ball) {
  // スコープルートに設定した自身へのスタイルは:scope疑似クラスを使用する。
  :scope {
    ...
    container-type: size;
    container-name: monsterBall;
  }
  // ここのコンテナクエリは後述
  @container monsterBall (width > 0px) {
    .top {
      ...
    }
    .bottom {
      ...
    }
    .button {
      ...

      &::before {
        ...
      }
    }
  }
}

@scope を使ってみて

@scope も、@layer 同様に気軽に使えない。理由として、たしかにスコープ内へのスタイルの適用だけど、ShadowDOM のようなカプセル化ではないので、スコープ外から簡単に影響を受けてしまうため、使い方が限定されてしまう。
メリットとしては、BEM による長くなりがちなクラス名を、近接性を利用することで短縮することができる。但し、実装例のような .top.button といったクラス名はどこで使われてもおかしくないので、メリットと引き換えに大きすぎる制約が発生してしまう。その代わりに今回は他に影響が出ないように全てのコンポーネントで @scope を使用したので堅牢にはなったかもしれない…(Scoped CSSに任せよう)

color-mix()

color-mix() / MDN
全ブラウザで使用可能

color-mix() 実装例

該当ソースコード

background-color: color-mix(in srgb, $bg_color_top, black 10%);

color-mix() を使ってみて

color-mix()以前の記事でも紹介したけど、個人的にめちゃくちゃ使いやすい。気軽に使えるので重宝している。今回みたいに「もうちょっと色を明るくしたい、暗くしたい」という時に #fff#000 を混ぜて調整したり、opacity で薄くしたり使うときもある。但し、デザインがしっかり決まっていればこんな小細工する必要はない…

Trigonometric functions

Trigonometric functions in CSS / web.dev
いわゆるCSS三角関数。全ブラウザで使用可能。

Trigonometric functions 実装例

該当ソースコード

<li
  v-for="(item, index) in distMenuList"
  :key="index"
  class="item"
  :class="{ 'is-open': isOpen && !isClose, 'is-close': isClose }"
  :style="{ '--index': index }"
>
@scope (.monster-ball-menu-list) {
  @container layout (width > 0px) {
    .item {
      ...
      &.is-open {
        animation: fadeIn 0.5s ease-in-out forwards;
      }
    }
  }
}
@mixin fadeTranslate() {
  --angle: calc(200deg + calc(140deg / 4 * var(--index)));
  --x: calc(cos(var(--angle)) * clamp(120px, 20vw, 300px));
  --y: calc(sin(var(--angle)) * clamp(120px, 20vw, 300px));
  translate: calc(var(--x) - 50%) calc(var(--y) - 50%);
}

@keyframes fadeIn {
  0% {
    width: 0;
  }
  40% {
    opacity: 0;
  }
  100% {
    opacity: 1;
    @include fadeTranslate();
  }
}

Trigonometric functions 使ってみて

使い方については、ICS MEDIAの記事を参考にしすぎているので、そちらを見てください。

CSS三角関数は、今のところ円形上に要素を配置する以外の使い方を見たことがないので、もっと違う有効な使い方があれば知りたい。

Container Queries

CSS コンテナークエリー / MDN
全ブラウザで使用可能。

Container Queries 実装例

該当ソースコード

$button_size: 46cqmin;
$button_inner_size: 18cqmin;

@scope (.monster-ball) {
  :scope {
    ...
    container-type: size;
    container-name: monsterBall;
  }
  @container monsterBall (width > 0px) {
    ...
    .button {
      background-color: $bg_color_bottom;
      width: $button_size;
      height: $button_size;
      border-radius: 50%;
      border: 4px solid $border_color;
      position: absolute;
      top: calc(50% - ($button_size / 2));
      left: calc(50% - ($button_size / 2));
      z-index: 1;

      &::before {
        content: '';
        border: 3px solid $border_color;
        background-color: $bg_color_bottom;
        width: $button_inner_size;
        height: $button_inner_size;
        border-radius: 50%;
        position: absolute;
        top: calc(50% - ($button_inner_size / 2));
        left: calc(50% - ($button_inner_size / 2));
      }
    }
  }
}

Container Queries 使ってみて

  • せっかくコンテナクエリを使ったので、レスポンシブで確認しようとしたけど、@scopeを使用したために、スマホでも見ることができなくなって、今回はいいかと諦めた。
  • vmin であったり今回の cqmin は正円や正方形の場合に使う時がある。
  • コンテナ単位使いたいだけだったので仕方なく (width > 0px) としたけど、いい方法があれば知りたい。

Container Queries 注意点

何も考えずにコンテナクエリー使うとツールチップ等の併用で事故る可能性がある。(実務で使用して事故った。)

Media Queries Level 4

Media Queries Level 4 で非常にメディアクエリーが書きやすくなった。全ブラウザ対応。

Media Queries Level 4 簡易説明(実装分)

  • @media(hover:hover)を省略して書けるようになった。
  • range記法が書きやすくなった。

Media Queries Level 4 実装例

該当ソースコード(hover)

.monster-ball-button {
  position: relative;
  z-index: 1;
  transition: 0.25s;
  @media (hover) {
    &:hover {
      filter: brightness(1.1);
    }
  }
}

該当ソースコード(range記法)

@scope (.youtube-dialog) {
  :scope {
    padding: 0;
    aspect-ratio: 16/9;
    width: 100dvmin;
    ...
    @media (width < 768px) {
      width: calc(100dvmin - 4rem);
    }
  }
}

Media Queries Level 4 使ってみて

range 記法は、@media screen and (min-width: 900px) って結局どっち!?といつもなるし、ブレークポイント増えると非常にわかりづらくなったりするので、直感的になって非常に書きやすくなった。

Individual Transform Properties

Finer grained control over CSS transforms with individual transform properties / web.dev
全ブラウザ対応

Individual Transform Properties 簡易説明

  • transform プロパティで指定していた <transform-function> を個別に設定できるようになった。
  • translate, rotate, scaleの3種類。
    • skewX()matrix() などは対応していない。

Individual Transform Properties 実装例

該当ソースコード

.is-shake {
  animation: shake 1s ease 3;
}
.is-load {
  animation: roll 0.7s linear 3, move 2.1s linear;
}

@keyframes shake {
  0% {
    translate: 0;
    rotate: 0;
  }
  25% {
    translate: -10px 0;
    rotate: -30deg;
  }
  50% {
    translate: 10px 0;
    rotate: 30deg;
  }
  75% {
    translate: 0;
    rotate: 0;
  }
}
@keyframes move {
  0% {
    margin-left: 100vw;
    opacity: 0;
  }
  25% {
    opacity: 1;
  }
  100% {
    margin-left: 0;
  }
}
@keyframes roll {
  from {
    rotate: 360deg;
  }
  to {
    rotate: 0deg;
  }
}

Individual Transform Properties 使ってみて

プロパティが独立したことにより、記述量も少なくなり、可読性もあがったし、何より複数のアニメーションを簡単に設定できるようになったので非常に使いやすくなった。