🎓

CSSハックを支える技術

2021/02/02に公開

CSS ハックというと、IE 6 とか IE 7 時代を思い出す人もいると思います。しかし、CSS ハックは現代にもしっかりと存在しており、僕は暇なときに CSS ハックを探しています。

そして、今まで見つけた CSS ハックをオープンソース化して公開しました。せっかくなので、どのようにして CSS ハックを見つけているのか紹介したいと思います。

https://github.com/takamoso/css-hacks

CSSハックとは

その前に、CSS ハックとは何かについて軽く説明します。CSS ハックとは、CSS が特定のブラウザや特定のバージョンのみに適用されるようにするテクニックで、主にブラウザ固有のバグ修正などに使われています。

最近では、Safari や iOS Safari 関連のバグが増えており、CSS ハックを使って対処する場合もあります。できるかぎり CSS ハックを使わずに対処するのがよいのですが、どうしてもといった場合に役立ちます。

CSSハックの仕組み

CSS ハックにはいくつか種類があります。

CSSパーサーのバグ

CSS パーサーの構文解析のバグを逆手にとる手法です。

IE 8
@media \0 {
  .selector {
    property: value;
  }
}

@media の後に \0 を入れると IE 8 だけに適用されるようになります。

IE 9
@media (min-width: 0\0) and (min-resolution: .001dpcm) {
  .selector {
    property: value;
  }
}

これはメディアクエリの特性内に \0 を入れることで IE 9 のみに適用されます。

IE 7
_:_, .selector {
  property: value;
}

もっと特殊なのがこの IE 7 にだけ適用される CSS ハックです。

このような CSS パーサーのバグを利用する手法は、バージョンアップによって最近のブラウザではほとんど使えなくなっています。

擬似要素や擬似クラス

あまり知られていないのですが、擬似要素や擬似クラスが実装されているかどうかを判定できる構文があります。

_[擬似要素・擬似クラス], .selector {
  // ブラウザが擬似要素・擬似クラスを実装していた場合
  // ここに書かれたCSSが適用される
}

例えば、以下のように書くと、ブラウザが :is() セレクタに対応していたとき、.title の文字色が緑色になります。

_:is(_), .title {
  color: green;
}

:is(_) の前の _ は意味のない文字です。_ を抜いてしまうと、*:is(_) というようにすべての要素が対象となってしまうため、無意味で有効な文字かつ通常の Web 制作では使わない文字として _ を使っています。

また、:is() セレクタには引数の指定が必須なので、これも _ を使っています。このテクニックを使って CSS ハックを実装できます。

IE 11
_:not(_)::-ms-backdrop, .selector {
  property: value;
}

IE 11 は 擬似クラス :not() と擬似要素 ::-ms-backdrop を実装しているため、このようにすれば IE 11 のみに適用されます。

Chrome 39+, Opera 26+
_:lang(_), _::-internal-media-controls-overlay-cast-button, .selector {
  property: value;
}

このように、そのブラウザ独自の擬似要素や擬似クラスを見つけられれば、簡単に CSS ハックを実装できます。ただし、上記のようにブラウザの特定のバージョン以降ずっと適用されるような CSS ハックを見つけるときは注意が必要です。

そのブラウザ独自の擬似要素や擬似クラスは途中で非推奨となってブラウザから削除される可能性があるため、できるだけ仕様書に載っているものやブラウザのリソースを探してしばらく削除されないようなものを選ぶ必要があります。

@supportsクエリ

モダンブラウザでは @supports クエリというブラウザがプロパティと値に対応しているかどうかを調べる、まさに CSS ハックにぴったりな機能があります。

Firefox 74
@supports (text-underline-offset: 1%) and (not (top: min(1%, 1%))) {
  .selector {
    property: value;
  }
}

たとえば、これは Firefox 74 のみに適用される CSS ハックです。text-underline-offset: 1% に対応していて、min() 関数に対応していないものは Firefox 74 だけになります。

Safari 9+, iOS Safari 9+
@supports (paint-order: fill) and (-webkit-marquee-speed: 0) {
  .selector {
    property: value;
  }
}

paint-order: fill だけだと他のブラウザにもマッチしてしまうため、Safari 独自の -webkit-marquee-speed: 0 で限定しています。

Chrome 84, Opera 70
@supports (appearance: auto) and (top: revert) and (-webkit-column-width: 0) and (not (counter-set: none)) {
  .selector {
    property: value;
  }
}

他のブラウザにマッチしないように複数のプロパティを使うこともあります。

selector()関数

@supports クエリには selector() 関数を使ってセレクタの対応状況を判定でき、これと not 演算子を組み合わせることでより簡単に CSS ハックを実装できるようになります。

Firefox 82
@supports selector(::file-selector-button) and (not (background: conic-gradient(red, tan))) {
  .selector {
    property: value;
  }
}

擬似要素 ::file-selector-button に対応していて、conic-gradient() 関数に対応していないのは Firefox 82 だけになります。

Firefox 81
@supports (overflow: clip) and (not selector(::file-selector-button)) and (-moz-orient: block) {
  .selector {
    property: value;
  }
}

not selector() のように使えるため、1 個下のバージョン判定にも使えて便利です。

さいごに

基本的には擬似要素や擬似クラス、プロパティとその値の実装状況の違いを利用して CSS ハックを見つけていきます。そして、間違いがないかを caniuse や browser-compat-data を使って確認し、BrowserStack というサービスを使って実機で検証しています。

もしよかったら CSS ハックの発見に協力してもらえるとありがたいです!

Discussion