Edge のバグと戦った話


この記事は、CAM Advent Calendar 25日目の記事です。
前回は Jung0 さんの Androidアプリでのエラー処理について を紹介しました。

自己紹介

今年の4月からフロントエンジニアとして働いています、kidoyuna です。
学生時代はデータ分析を勉強していたのですが、アイドルが好きすぎたため、
アーティストファンサイトを展開している今の会社に入社することを決めました。
Web 未経験でしたが、周りに助けられながら日々奮闘中です。

前提条件

自分用メモでもある、Edge 戦記です。

ちなみに、Webcomponents を使用したプロジェクトでのお話です。
これについては、2日目 MuuKojima さんの
Web Components入門: フルに導入されたプロジェクトに入った結果とサンプル実装をご覧ください。

ShadowDOM が関係してくるので、まずは各ブラウザの対応状況から。
2019年12月現在 Firefox, Chrome, Opera は対応済み。Safari は一部バグあり。IE と Edge は未対応です。
そのため、幅広いブラウザに対応するには Webcomponents の Polyfill の利用が必要となります。

Polyfill について

Polyfill とは、Webcomponents のような最新技術に未対応のブラウザのために、
現在搭載されている機能でほぼ同等の機能を展開してくれるライブラリです。ありがたい...

Webcomponents の大きな特徴の1つである ShadowDOM とは、DOM を外から見えないようにカプセル化するというものです。
これを Polyfill で再現したものが ShadyDOM です。

ShadyDOM も ShadowDOM 同様、カプセル化されているので普通に考えれば外からスタイルを当てることはできません。
それを可能にする Polyfill が、ShadyCSSです。

バグ1つ目

上記の ShadyCSS を使用してバグが発生しました。

公式の README には以下のようにあります。(日本語のコメントは筆者が追加しました)

<my-element>
  <!-- shadow-root -->
  <style>
    :host {
      display: block;
    }
    #container slot::slotted(*) {
      color: gray;
    }
    #foo {
      color: black;
    }
  </style>
  <div id="foo">Shadow</div>
  <div id="container">
    <slot>
      <!-- span distributed here -->
    </slot>
  </div>
  <!-- /shadow-root -->
  <span>Light</span> // このspanごと、上のslotタグが置き換わる
</my-element>

becomes:

<style scope="my-element">
  my-element {
    display: block;
  }
  #container.my-element > * {
    color: gray;
  }
  #foo.my-element {
    color: black;
}
</style>
<my-element>
  <div id="foo">Shadow</div>
  <div id="container">
    <span>Light</span> // slotがあったところにspanが入った状態
  </div>
</my-element>
Selector scoping
You must have a selector for ascendants of the <slot> element when using the ::slotted pseudo-element.
You cannot use any selector for the <slot> element. Rules like .foo .bar::slotted(*) are not supported.

と、あるので
slot されたあとのものにスタイルを当てようと思ったら

親Element slot::slotted(*) {

のようにすべきだと思いました。公式が言ってるんだもん。
しかし、スタイルが当たらない...。なんでやねん!

そこで、同プロジェクトの2年目の先輩が

「レンダリングされたあと slot タグないし、もしかしたらさあ...」

と以下の記述を提案しました。

親Element ::slotted(*) {

効きました!

ちなみに、標準で ShadowDOM が実装されている Chrome などでは

::slotted(*) {

のみで問題ないです。はぁ...

バグ2つ目

次からは ShadyDOM はおそらく関係ないバグです。

CSSプロパティの colorbackground-color などに使用できる値 currentColor についてです。

currentColor

こちらは、親ブロックで指定した色がそのまま使える便利なやつです。
例えば、アイドルのメンバーカラーによって文章の枠色を変えたいとき、ありますよね。

See the Pen currentColor by Kido Yuna (@kidoyuna) on CodePen.

こういったときに便利です。

color: currentcolor; の記述は今回の構造だと必要ありませんが、ShadowDOM でどんどん値を受け渡していく際は必要になることがあります。

これを、CSS で変数を使って書いてみます。

See the Pen currentColor x custom property by Kido Yuna (@kidoyuna) on CodePen.

同じ意味なので、もちろん見た目は変わりませんね。
ですが、これを Edge で見ると...

白...

しかしこちら、color: "currentcolor"; とすると、理想の挙動になる模様。
(ちなみにそうすると border がバグるので構造を変える必要あり)

参考:currentColor set as a custom property doesn't work in Edge

バグ3つ目

font-size を変数で指定して、その font-size に合わせて padding をつけてみます。

See the Pen font by Kido Yuna (@kidoyuna) on CodePen.

こうすると、全体的に font-size を変更したくなったとき
--base-font-size を変えるだけで、 --font-big--font-small の比率は変わらないので管理が楽になりますね。

また、 description は font-size の半分の値で padding をとっていますが
これによって、 font-size が変わっても見た目のスペース比率は変わりません。楽。

ですが、これが Edge だと...

ぎゃー。 padding 消えた。

これは、ブラウザ側で

padding: calc(var(--font-small) / 2);

の記述が

padding: calc(calc(var(--base-font-size) * 0.8) / 2);

と解釈されて、さらに

padding: calc(calc(16px * 0.8) / 2);

と解釈されているためです。
これが Edge は理解できないみたいです。

ちなみに、試しに

padding: calc((var(--base-font-size) * 0.8) / 2);

と直書きしてみたら、理想通りの挙動になりました。
うーん... calc って文字がネストするとわかんなくなっちゃうんだなぁ。

カスタムプロパティcalc も Edge 対応してるのに。

工夫すれば実現はできますが、ちょっと不便...。

Edge との戦いを終えて

(正確に言うとまだ戦っている最中なのですが...)

「バグかも?」と思ったときに詳細を調べる速度が、先輩方はめちゃめちゃ早かったです。

私はとりあえず「Edge calc」で検索して出てきた、それっぽい Qiita の記事を読み漁るなどしてましたが恐らく NG です。

基本中の基本ではありますが、まず公式ドキュメントをじっくり読む。
ブラウザ間で揺れがあるバグなら、caniuse で対応状況確認。そこに issue が載ってることもあるので探す。
それから、 Polyfill 等ライブラリについてなら公式 Github の issue で同様のことを嘆いている人はいないか探す。

英語の文献はついつい避けがちな私ですが、これを機に「まず公式ドキュメント!!」を身につけようと思います。