2023年7月3日月曜日

object タグ経由で SVG の読み込み完了をチェックする

object タグ経由で SVG の読み込み完了をチェックする方法をまとめておきます。 様々なケースで利用できますが、特にダークモード対応で便利かと思います。

ダークモード対応を考えた時、シンプルな SVG はインライン化するか、use タグで利用するのが一般的です。 なぜなら文字色に合わせて色を反転できる fill="currentColor" を適用するためには、上記の方法を取るしかないからです。 しかし外部の DTD などを読み込む複雑な SVG は、上記の方法だとさすがに扱いにくい問題があります。 下手にインライン化すると ID 重複が起きたりするし…。 そこで仕方なく、一部の SVG は以下のように object タグで読み込むことになります。 ※他にも CSS の filter を使うなどの方法があるかも知れませんが、普通はそんな超絶技巧はしないほうが良いです。
<object type="image/svg+xml" data="test.svg" width="64" height="64">
</object>
ただ object タグで読み込むと、(1) fill="currentColor" が効かない問題と、(2) 読み込みが完了したかのチェックが難しい問題があります。 1 に関しては JavaScript で自分で設定すればどうにかなるのですが、これまで 2 の解決方法がわかりませんでした。 SVG の読み込みを HTML 上で 実装せず JavaScript で実装するしかないのかなと思っていました。 ただそれをやってしまうと JavaScript を OFF にした時の実装が非常に面倒になってしまう問題があります。 また JavaScript のパース前に SVG をロードできるなら、高速化のためにもぜひ事前ロードしたいですよね。

この問題に関して、StackOverflow からヒントが得られました。 そのままだと使いにくいので、使いやすく書き直したものが以下です。 HTML 側で data 属性を事前に書いていると、後から JavaScript 側で onload 属性を付与してチェックしても、既に読み込み完了になっている可能性があります。 そこで getCurrentTime() を使うことで、SVG が読み込み完了かどうかチェックできます。
function isLoaded(object) {
  const doc = object.contentDocument;
  if (!doc) return false;
  const svg = doc.querySelector("svg");
  if (!svg) return false;
  if (svg.getCurrentTime() < 0) return false;
  return true;
}

doc.querySelector("svg") の部分は、doc.documentElement でも良いのではと思ったのですが、駄目みたいです。 svg.getCurrentTime() は Chrome だと読み込み完了してから何秒経ったかを返却するようですが、 Firefox は読み込み完了すると常に 0 になるようなので、0 以上かどうかチェックするのが良さそうです。

この関数を使って object タグで読み込んだ SVG をダークモード対応する時は、たとえば以下のように実装します。 具体例 (タッチde書き順):
if (isLoaded(object)) {
  const svg = object.contentDocument.documentElement;
  svg.style.background = "#212529";
  svg.firstElementChild.style.stroke = "#fff";
} else {
  object.onload = () => {
    const svg = object.contentDocument.documentElement;
    svg.style.background = "#212529";
    svg.firstElementChild.style.stroke = "#fff";
  };
}
この例では background と stroke の色を変えることでダークモードに対応しています。 background が transparent のとき、色の設定はいらないように思うのですが、Chrome の挙動が怪しいので付けています。

今までは お手軽にダークモード対応 (+処方箋) で誤魔化していたので楽だったのですが、 真面目にダークモード対応をするのは大変なんだなと、今さら感じています。 特に SVG の扱いが大変ですね。

0 件のコメント: