アイコンフォントの代替を考える
このページではFontAwesomeのアイコンWebフォントを利用していますが、そのロードにかかる負荷はどうなんだろうといつも気になっています。
現在の環境だと、Kitと呼ばれるJavaScriptファイルとウェブフォントを公式のサーバーより取得します。そのサイズと転送スピードを見てみると次のような感じでした。
- https://kit.fontawesome.com/kitname.js
4.1kB : 85ms
- https://ka-f.fontawesome.com/releases/v5.15.4/webfonts/free-fa-solid-900.woff2
79kB : 121ms
- https://ka-f.fontawesome.com/releases/v5.15.4/webfonts/free-fa-brands-400.woff2
77kB : 147ms
これらすべてでページで利用するjpegファイル1つ分ぐらいのサイズなので、気にするほどではないのかもしれませんが、利用している10種ほどのアイコンの為にこれだけの処理をするのは大仰といえるのかもしれません。
そこでSVG画像(SVGタグ)を使ってアイコンフォントの代替ができないか考えてみました。
SVG画像
SVG(Scalable Vector Graphics)はベクター形式のための XML に基づくマークアップ言語です。なので、SVG画像をテキストエディアで開くとXMLが出てきます。
たとえば、以前に電子印の印影を取得するのに使ったINKSCAPEの保存形式も拡張子がSVGです。これで保存したファイルをテキストエディタで開くとその先頭部は次のようになっています。
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
width="16mm"
height="16mm"
viewBox="0 0 16 16"
version="1.1"
id="svg5"
inkscape:version="1.1.1 (3bf5ae0d25, 2021-09-20)"
sodipodi:docname="pocket.svg"
...
ただし、INKSCAPEでは通常の画像も埋め込めますので、すべてが人間が解釈できる意味のある文字ではありません。また、一見すると意味のない文字列のような部分も存在します。
それはpathタグの中にあるd属性にセットされている次のような値です。
これがSVG画像の本体とも呼べる部分で、図形を、これらの文字によって表しています。また円や四角などの図形の場合はそれ用のタグがあり、たとえば円ならcircleで表されます。
これは(8,8)の座標を中心に半径8の円を描く指示となります。数値は線画をする際の仮の値で、描かれた後に縮小や拡大されます。
このSVGファイルをimgのsrc属性にセットすることでブラウザで表示することができます。また、SVGファイルをダブルクリックしたり、ドラック&ドロップすることでも表示させることができます。
このファイルは意味のある文字で構成されているので、テキストファイルから編集する事も可能です。またHTMLと同じようにstyle属性で色をつけることもできます。円を楕円(ellipse)にしてfill(塗りつぶし)にredを指定しています。
svgタグ
SVG画像は、imgのsrc属性としても設定できますが、ブラウザで直接線画することもできます。そのためのタグがsvgタグです。
svgファイルをアップロードできないBloggerでもこの方法を使えばSVG画像を表示させられます。
svgタグは先ほど、INKSCAPEの画像をテキストエディタで開いた時にもでてきました。なので、それで囲まれた領域をHTMLタグとして貼り付けることもできます。
こうすることで、imgのsrcに設定する場合と違って、HTML側のCSSをSVG画像に適用することが可能になります。
INKSCAPEのファイルにはsvgタグに多くの属性がありましたが、HTMLで必要な属性をピックアップして紹介します。
- xmlns
値は「http://www.w3.org/2000/svg」固定です。
先ほどsvg以下の要素をHTMLタグとして貼り付けることができると書きましたが、正式には違ってsvgタグの中は「http://www.w3.org/2000/svg」として定義された名前空間にあるタグで記述しています。svgタグの内側は、外側のHTMLとは違ったルールとなることをこの表記によって示しています。
URLが指定されていますが、単なる名前で直接そのアドレスを使って何かをしているわけではありません。
詳しくはMDN:「名前空間の速修講座」を確認してください。
- viewBox
viewBoxは、SVGを線画するための一時的な空間を定義するものです。開始位置(x)、終了位置(y)、幅(w)、高さ(h)で値をセットします。
前述のpathの座標はこの線画領域に対してのものになります。線画が行われた後、実際の指定されたサイズに縮小または拡大されます。
viewBoxや、pathで指定する値には、pxやmmなどの単位はありません。
- version
現行のバージョンは2です。SVGバージョン2ではバージョン属性を記述しません。先のMDNの名前空間のページにもサンプルや、INKSCAPEでは1.1になっていました。以前PDFを編集したLibre Office DrawからSVGファイルでエクスポートすると、1.2になります(存在しないバージョン?)。
- width,heigh
実際のサイズを指定します。viewBoxに作成された図形はここで指定されたサイズになるように縮小または拡大されます。
gタグ
gタグは、前述のellipseやcircle、pathなどをグループ化する要素です。まとまりに対して、属性値に持ったfill(塗りつぶし色)やstroke-width(線幅)を設定できます。
また、transform属性をつかって、translate(移動)、scale(拡大)やrotate(回転)をまとめて指示する事もできます。
transform属性
transform属性を利用することで図形を変化させることができます。ただし変化の結果viewBoxからはみ出した分は線画されません。
バージョン1.1ではtransform属性を設定できる要素が限られます。また、バージョン2ではCSSのtransformでの指定もできるようになったそうです。
再利用
一度利用したSVGやその構成要素(path等)を再度利用したい場合はuseタグを使うことで可能です。
コピー元となる要素はdefsタグで囲まれていることが推奨されていますが、環境によっては、それが無くても意図したように動きます。
再利用するものが単一の要素だった場合はその要素に直接IDを設定して参照することも可能ですが、複数の要素をまとめたい場合はsymbolタグを用います。
コピーする際は、useタグを使います。この時href属性に#IDとして対象の要素を指定します。
以前まではこのhref属性は、xlinkの名前空間にあるものを利用していましたが(xlink:href)、バージョン2では非推奨となりました。(ただし旧ブラウザ互換性維持のためには必要なようです)
use内ではx,y属性の設定ができます。コピー元の要素にviewBoxが指定してあれば、use内でのwidthとheight属性に効果を持たせることができます。さらに、コピー元でfillやstroke属性が設定されていない場合に限り、それらを加えることができます。
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="16" height="16" viewBox="0 0 16 16">
<defs>
<symbol id="test" viewBox="0 0 16 16">
<path d="M8 15A7 7 0 1 1 8 1a7 7 0 0 1 0 14zm0 1A8 8 0 1 0 8 0a8 8 0 0 0 0 16z"/>
<path d="M12.331 9.5a1 1 0 0 1 0 1A4.998 4.998 0 0 1 8 13a4.998 4.998 0 0 1-4.33-2.5A1 1 0 0 1 4.535 9h6.93a1 1 0 0 1 .866.5zM7 6.5c0 .828-.448 0-1 0s-1 .828-1 0S5.448 5 6 5s1 .672 1 1.5zm4 0c0 .828-.448 0-1 0s-1 .828-1 0S9.448 5 10 5s1 .672 1 1.5z"/>
</symbol>
</defs>
<use href="#test" x="0" y="0" style="opacity:1.0" />
</svg>
<!-- 旧ブラウザ互換表記 -->
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="32" height="32" viewBox="0 0 16 16" style="fill:#ffcccc;">
<use xlink:href="#test"/>
</svg>
<!-- 通常表記 -->
<svg xmlns="http://www.w3.org/2000/svg" width="64" height="64" viewBox="0 0 16 16" style="fill:#ffccff;">
<use href="#test"/>
</svg>
上記のコードを実際に表示させてみます。アイコンはBootstrap Iconsのものを利用させていただきました。(トップページのリストもその表示です)
結果:
遅延処理の実装
ページに挿入するには前述のようにSVGタグを入れればいいのですが、線画のための処理時間が気になったので遅延処理を実装してみようと思います。
このページではほぼアイコンを複製するということがないので、先にtemplate要素を作成しておき必要な時にJavaScriptでクローンを記述するようにします。templateにしたのは読み込み時のレンダリングを回避させるためなので、display:noneを設定する方法でもいいかもしれません。読み込みの負荷が高いようなら、別ファイルのJavaScriptに書き込んで遅延読み込みするのも手かもしれません。
複製する頻度が多ければtemplate要素してレンダリングを回避しつつ保持しておくという方法もあると思います。
ターゲットとなる要素は現状のFontAwesomeを引き継ぎます。FontAwesomeについては、以前の記事で触れていますのでよろしかったら読んでみて下さい。基本的にi要素に専用のクラスが設定されているので、それを前提に操作をします。
sample.html
<span style="font-size: 80px; color:red; background-color: #ffc;"><i class="fab fa-smile"></i><span style="display: inline-block; color:blue; transform: rotate(0.1turn);"><i class="fab fa-smile"></i></span></span>
<template id="fa-smile">
<svg xmlns="http://www.w3.org/2000/svg" x="0" y="0" width="16" height="16" viewBox="0 0 16 16">
<title>smileアイコン</title>
<path d="M8 15A7 7 0 1 1 8 1a7 7 0 0 1 0 14zm0 1A8 8 0 1 0 8 0a8 8 0 0 0 0 16z"/>
<path d="M12.331 9.5a1 1 0 0 1 0 1A4.998 4.998 0 0 1 8 13a4.998 4.998 0 0 1-4.33-2.5A1 1 0 0 1 4.535 9h6.93a1 1 0 0 1 .866.5zM7 6.5c0 .828-.448 0-1 0s-1 .828-1 0S5.448 5 6 5s1 .672 1 1.5zm4 0c0 .828-.448 0-1 0s-1 .828-1 0S9.448 5 10 5s1 .672 1 1.5z"/>
</svg>
</template>
<script>
const setIcon=()=>{
let elTargets = document.querySelectorAll('i.fab,i.fas');
for(let i = 0; i < elTargets.length; i++) {
let wk = [];
let classList = elTargets[i].classList;
for(let j = 0; j < classList.length; j++) {
if (classList[j].startsWith('fa-')) {
wk.push(classList[j]);
}
}
wk.forEach(str => {
let elSrc = document.querySelector('template#'+str);
if(elSrc) {
let elClone = elSrc.content.cloneNode(true);
let svg = elClone.querySelector('svg');
let css = getComputedStyle(elTargets[i]);
let parentColor = css.getPropertyValue('color');
svg.setAttribute('width','1em');
svg.setAttribute('height','1em');
svg.style.fill=parentColor;
/* svg.style.stroke=parentColor; */
elTargets[i].appendChild(elClone);
}
});
}
}
setIcon();
</script>
これを実行してみます。(FontAwesomeとの衝突を避けるため上記のコードとは少し変えています)
getComputedStyleで、親要素(i)からcolorを取得して、svgのスタイルのfillに設定します。ここではcssに設定しましたが属性でも機能します。strokeは設定すると太くなりすぎたので設定していません。バージョン2を設定する(svgタグをバージョンを書かない)事で、cssのtransformも効かせることができました。
画像間の余白が思うように設定できていませんが、i要素の親要素の文字サイズと色でSVG画像を設置することができました。時期をみて本番環境も置き換えようと思います。
いちいち親要素から取得しないでもfillの値に"currentColor"をセットすれば色の指定が可能だとわかりましたので追記しておきます。