2019年8月3日土曜日

SVGをHTML上で表示する時の注意点色々

SVGをHTML上で表示する時にいつもハマってるので、自分用に注意点をまとめます。

1. embedしたい時には img/object/embed/iframe タグの4つが使える

こんな感じで書く。
<img src="/img/test.svg" width="300" height="200" />
<object type="image/svg+xml" data="/img/test.svg" width="300" height="200">
</object>
<embed type="image/svg+xml" src="/img/test.svg" width="300" height="200">
</embed>
<iframe src="/img/test.svg" width="400" height="300"></iframe>
img タグが手軽。 ただどれでも良い訳ではなく、SVG 内の要素にアクセスしたい時 (例えばクリックイベントを使いたい時など) には object を使わなくてはいけない。 embed は SVG がプラグインなしでは表示できなかった時代のブラウザ対応に使うので、最近は使わない。 iframe も width/height の扱いや速度に制約が多いので、普通は使わないと思われる。


2. SVG を縮小/拡大表示するためには viewBox を使う

object タグで呼び出し、SVGを縮小/拡大表示するためには viewBox を使う必要がある (img タグで呼び出す時はなくても大丈夫)。 SVGファイルの中身は以下のように書かないといけない。 viewBox の値は、width, height と同じ値を書いておけば普通はイケる。この指定がないと縮小表示してくれません。
<?xml version="1.0" encoding="UTF-8"?>
<svg viewBox="0 0 400 300" width="400" height="300" ...>
  ...
</svg>
viewBox の指定さえすれば、あとは 1 と同じ方法で SVG を呼び出すだけ。

3. width, height の指定は px にすべき

自動生成された SVG ではたまに width, height を px 以外で設定していることがある。 この SVG は直リンクで表示するときは良いのだけど、同階層で複数の img タグを呼び出す (下例) と表示サイズが粉々になるケースがある。
<style>
  .responsive { max-width:100%; height:auto; }
</style>
<div>
  <img class="responsive" src="1.png" width="1em" height="2em"/>
  <img class="responsive" src="2.png" width="2em" height="4em"/>
</div>
どういうことかというと、responsive クラスが悪さをする。 ブラウザは最初の SVG で max-width:100%; になるように em の指定を px にスケーリングし直し、div のサイズに画像をリサイズする。 そして以降の SVG も「そのスケーリングを使用して」表示する。 いつか挙動が変わるかも知れないけど、width, height の em のサイズが異なると、これによってスケーリングが崩壊する。 width, height は px にすべきではないかな。

4. 外部 SVG を読み込んで色々操作する (1)

地味にベストプラクティスがわからないのがこれで、CORS 関連による変更でよくわからなくなってます。 JavaScript 主体で書くなら以下が一番良いと思う。

ポイントは (1) text/html として SVG を読み込むことで、HTML 関連のメソッドを使えるようにする。 (2) SVG のルート要素を探索して CDATA のノイズを消すことです。 この方法以外の読み込みだと、SVG 内部で getElementById も querySelector も使えないで疲れる。
fetch('test.svg')
.then(response => response.text())
.then(text => {
  const parser = new DOMParser();
  return parser.parseFromString(text, 'text/html');
}).then(html => {
  var parent = document.getElementById('parent');
  var svg = html.getElementsByTagName('svg')[0];
  parent.appendChild(svg);
  var abc = svg.getElementById('abc');
});


5. 外部 SVG を読み込んで色々操作する (2)

もう一つの実装方法は、object で SVG を読み込んだ後に、contentDocument で中身を操作する方法です。 ちなみに getSVGDocument() でアクセスできるブラウザもありますが、deprecated なので contentDocument を使いましょう。 注意点としては、SVG を読み込んだ後にしか操作できないことです。 そのため、例えば以下のように実装します。
<object type="image/svg+xml" data="/img/test.svg"
  onload="init(this)" width="300" height="200">
</object>
function init(obj) {
  console.log(obj.contentDocument);
}
addEventListener より onload 属性で書いたほうがすっきりします。 1と比較したときの利点は、SVG 内の ID 重複が起きないことと、onload の非同期処理が少し楽になることです。 欠点としては、この方式で動かす時には HTTP/HTTPS 経由で動かさないと、object.contentDocument = null になってしまいます。 何もエラーが出ず null になるので、これに長時間ハマったことがある。

さらに object 要素は data属性を付与した後でイベントを待っても onload イベントが発生しないことに注意する必要があります。 他の要素ではそのようなことは起きません。data属性を付ける前に onload イベントを付与すればきちんとイベントは補足できます。 このような細かく複雑な仕様が至るところにあって、意外と SVG の処理はハマります。

6. 公開時は SVG を simplify してファイルサイズを小さく

SVGのpathを簡略化するとファイルサイズがとても小さくなります。 いつもキーワードを忘れるけど、simplify / optimizer / cleaner あたりで検索すると良さそう。 いくつかツールがありまずが、割と使いやすいのが scour です。

7. 公開時は SVG を minify してファイルサイズを小さく

こちらは書く必要はないかも知れませんが、minify するとさらにファイルサイズを小さくできます。 私は tdewolff/minify を使っています。

8. img タグと object タグの embed は表示動作が異なる

img タグではJavaScript が効かない違いは先にも述べましたが、他にも表示の仕方が何か違うみたい。 私もあまり良くわかってませんが、少なくとも以下の SVG の "Yo" が img タグだとうまくできない。 うまく表示できないタグそのものを弄れば何とかなるみたいだけど、何だろうこれ。

Yo""無線で文字列を送信ボタンAが押されたとき


9. SVG の余白を削除する

複数の SVG を並べて表示するときなどに、SVG の余白が邪魔になるときがある。 SVG の余白を削除するのは Inkscape を使うのがおそらく一番楽です。 メニューの「ファイル」→「ドキュメントのプロパティ」→「ページサイズをコンテントに合わせて変更」を実行すると、余白をゼロにしてくれる。

10. SVG の PNG 書き出し時には外部リンクに注意

SVG を img 要素を介して canvas に書き出す処理 は日常的に使われます。 しかし Web フォントを使っていると、セキュリティの問題から外部リンクが読み出されない問題が発生し、フォントが適用されない問題が発生します。 Web フォントに限らず、何らかの外部リンクは同じような結果になります。 対処法としては、外部リンクの中身を dataURI スキーム形式に変換し, SVG 内に style 要素として埋め込む必要があります。 日本語フォントはサイズが大きいので、当然ながら超重いです。 canvas の解像度を上げて書き出すほうがマシだと思う。

最近は少しでも詰まったらメモっておくのが一番早い。

0 件のコメント:

コメントを投稿