詳細度よりも優先される CSS の Cascade Layers について

2022.05.03

MAD 事業部の高橋ゆうきです。

少し前の話になりますが Safari 15.4 がリリースされました。

これにより Chrome や Firefox、Edge などのブラウザではすでに実装されていた CSS の Cascade Layers が Safari でも利用できるようになりました。

ある程度 Safari のバージョンがさらに上がっていけば実際に使っても良さそうなのか判断するためにも、Cascade Layers について整理してみました。

ここでは Cascade Layers について焦点をあてるため、その他登場する用語については理解していることを前提とし説明をしていません。

また、執筆時には Devtools が Styles pane での表示に十分対応しきれていないことから、画面キャプチャについては Chrome Canary での表示となります。

以下は今回のサンプル用に作成したページとソースコードになります。

Cascade Layers について

カスケードは以下の判断基準で優先されます。

  1. origin (スタイルの宣言出自) と !important
  2. context(Shadow DOM)
  3. style 属性
  4. レイヤー
  5. 詳細度
  6. 出現順序

仕様では下記で記述されています。

レイヤーは詳細度よりも優先されていることがわかります。

div.square {
  background-color: red;
}

.square {
  background-color: blue;
}

詳細度によるスタイルの変化。より詳細度の高いものが優先される

上記の CSS ではより詳細度の高い div.square のスタイルが優先され、背景色は red になります。

それぞれレイヤーで括ってみます。

@layer red {
  div.square {
    background-color: red;
  }
}

@layer blue {
  .square {
    background-color: blue;
  }
}

レイヤーで優先度の高いスタイルが適用されている

このように詳細度よりも優先されるレイヤー(より後で記述したレイヤーが優先されます)で記述されているスタイルが優先され、背景色は blue になります。

:where() などを使用した詳細度でのスタイルの優先度調整よりも、シンプルでとてもわかりやすいと感じます。

レイヤーの優先順位は Devtools では画像の赤枠の Toggle CSS Layer View ボタンをクリックする、あるいは Layer 名をクリックすると確認できます。Layer 名をクリックすると、クリックした Layer 名がハイライトされます。

数値が大きいものほど優先度が高いレイヤーになります。

レイヤーの優先順

暗黙的なレイヤー

仕様では unlayered や implicit (final) layer と表記されています。これは文字通り、レイヤーを持たないスタイルでレイヤー内ではもっとも優先されます。

.square {
  background-color: red;
}

@layer blue {
  .square {
    background-color: blue;
  }
}

暗黙的レイヤーはもっとも優先度が高い

Devtools では implicit outer layer として表示されています。

入れ子

レイヤーは入れ子にできます。

@layer blue {
  @layer red {
    .square {
      background-color: red;
    }
  }
}

入れ子はネストされた識別子をフラットなリストとしても記述できます。

@layer blue.red {
  .square {
    background-color: purple;
  }
}

以下のようにすると、.square は何色になるでしょうか。

@layer blue {
  @layer red {
    .square {
      background-color: purple;
    }
  }
}

@layer red {
  .square {
    background-color: red;
  }
}

@layer blue.red {
  .square {
    background-color: purple;
  }
}

同じレイヤー名は統合される

入れ子で記述しても、フラットなリストで記述しても同一のレイヤーに追加されます。上記の場合には blue.red は最初に記述されたレイヤーに追加されるため、背景色は赤色となります。

優先順序を事前に確立する

上記の入れ子の例で背景色を紫にしたい場合には、優先順序を事前に確立させることで実現できます。

@layer blue, red;

@layer blue {
  @layer red {
    .square {
      background-color: purple;
    }
  }
}

@layer red {
  .square {
    background-color: red;
  }
}

@layer blue.red {
  .square {
    background-color: purple;
  }
}

レイヤーは事前に順序を確立できる

匿名レイヤー

レイヤーは名前をつけない匿名のレイヤー(anonymous layer/unnamed layer)を作成できます。ただし、匿名であるがゆえに優先順序を制御したり、スタイルを追加するなどの外側からの参照ができなくなります。

つまり、以下のような例では背景色は赤となります。

@layer {
  @layer red {
    .square {
      background-color: red;
    }
  }
}

@layer red {
  .square {
    background-color: blue;
  }
}

@layer {
  @layer red {
    .square {
      background-color: red;
    }
  }
}

匿名レイヤーは追加することができない

匿名なので記述順序以外で優先順位を制御できません。安易に使うと思わぬスタイルの上書きをしてしまいそうなので、使用するには慎重さが求められる印象を受けました。

revert-layer キーワード

グローバルキーワードにはこれまで以下がありました。

revert-layer はスタイルを前のレイヤーで指定されたスタイルに戻すことができます。

@layer red {
  .square {
    background-color: red;
  }
}

@layer blue {
  .square {
    background-color: blue;
  }

  .reset {
    background-color: revert-layer;
  }
}

revert-layer

square reset がクラスに指定されている場合、背景色は赤くなります。

import した CSS にレイヤーを指定する

import した CSS にレイヤーを指定できます。

/* style.css */
@layer red, blue;
@import url("./imported.css") layer(blue);

@layer red {
  .square {
    background-color: red;
  }
}
/* imported.css */
.square {
  background-color: blue;
}

@import はパフォーマンスに悪影響が出るため、postcss-import などを使うことになりますが、postcss-import ではすでに import 時におけるレイヤーのサポートがされています。

手元で試してみたところ、問題なく以下のように 1 つのファイルにまとめることができていました。サンプルコードでは npm run build:css でビルドを試すことができます(出力結果の例)。

/* combined.css */
@layer red, blue;
@layer blue {
  .square {
    background-color: blue;
  }
}
@layer red {
  .square {
    background-color: red;
  }
}

PostCSS などで結合しない解決策としては以下のようなものが議論されているようです。

まとめ

Safari の最新バージョンで対応したとはいえ、ある程度の古いバージョンへの対応も必要になることがほとんどです。そのため今すぐ使うのであれば Polyfill がほしいところですが、現在作成中のようです。

レイヤーが使えるようになると、CSS フレームワークや UI フレームワークなどのスタイルを変更がシンプルな記述で書き換えることができそうです。

CSS の設計でもこれまで詳細度でスタイルの調整をしてきたものについては、すべてレイヤーで置き換えることができるようになるため、大きな変化が起こりそうな気がしています。

実際に使えるようになるのが楽しみな機能です。

参考