SVGをコンポーネント化して内容を操作をする

更新日
2017.07.28
作成日
2017.01.21

SVG はファイル内に CSS や JavaScript を梱包する事が出来るのでコンポーネント化しやすいんじゃないかなぁ~と少し考えてみました。

SVGファイルを作る

セール用バッジを作ってみます。

普段 SVG はイラストレーターで作成しているのですが、Boxy SVGエディタという無料のアプリが良いという噂を聞いて使ってみました。各要素にIDやクラス名など独自の属性を付けたかったのですが、付け方がわからないというか出来ないのかな?あとでテキストエディタで開いて編集するので問題はないのですが、だいたいこんな感じです。開き直しても付加したID等は消えないみたいなのでよしとします。

グラフィックだけのファイルを作成

Boxy SVG で作成したコードです。text-anchor: middle;やフォントの指定などはインスペクターで直接弄ってます。

<?xml version="1.0" encoding="utf-8"?>
<svg class="defs" xmlns="http://www.w3.org/2000/svg">
    <g viewBox="0 0 60 60" transform="matrix(0.984807, -0.173648, 0.173648, 0.984807, -6.028826, 3.622701)">
        <polygon
            points="55.558 14.953 47.433 12.334 44.815 4.209 36.47 6.002 30.137 0.275 23.805 6.001 15.46 4.208 12.841 12.334 4.716 14.953 6.51 23.299 0.782 29.63 6.508 35.961 4.715 44.308 12.841 46.928 15.46 55.052 23.805 53.257 30.137 58.984 36.468 53.258 44.815 55.052 47.435 46.925 55.696 44.333 53.765 35.963 59.491 29.63 53.765 23.299"/>
        <text y="32.069" x="22.704" style="fill: rgb(255, 255, 255); font-family: Verdana; font-size: 18px; text-anchor: middle; white-space: pre;">00</text>
        <text x="36.008" y="31.952" style="fill: rgb(255, 255, 255); font-family: Verdana; font-size: 13px; white-space: pre;">%</text>
        <text x="19.485" y="46.88" style="fill: rgb(255, 255, 255); font-family: Verdana; font-size: 13px; white-space: pre;">OFF</text>
    </g>
</svg>

テキストエディタで編集

テキストエディタで開き、この中の数値が入るテキストの要素に#numとギザギザの背景の要素に#bgというIDを付けて、スクリプトを追加します。

<?xml version="1.0" encoding="utf-8"?>
<svg class="defs" xmlns="http://www.w3.org/2000/svg">
    <script>
        //<![CDATA[
        window.onload = function () {
            var num = parseInt(location.search.substr(1)||0);
            document.getElementById('num').textContent = num;
            switch (true) {
                case num > 60:
                    document.getElementById('bg').setAttribute('fill', '#ea280e');
                    break;
                case num > 30:
                    document.getElementById('bg').setAttribute('fill', '#f96551');
                    break;
                default:
                    document.getElementById('bg').setAttribute('fill', '#ffbb00');
            }
        };
        //] ]>
    </script>
    <g viewBox="0 0 60 60" transform="matrix(0.984807, -0.173648, 0.173648, 0.984807, -6.028826, 3.622701)">
        <polygon id="bg"
            points="55.558 14.953 47.433 12.334 44.815 4.209 36.47 6.002 30.137 0.275 23.805 6.001 15.46 4.208 12.841 12.334 4.716 14.953 6.51 23.299 0.782 29.63 6.508 35.961 4.715 44.308 12.841 46.928 15.46 55.052 23.805 53.257 30.137 58.984 36.468 53.258 44.815 55.052 47.435 46.925 55.696 44.333 53.765 35.963 59.491 29.63 53.765 23.299" />
        <text y="32.069" x="22.704" style="fill: rgb(255, 255, 255); font-family: Verdana; font-size: 18px; text-anchor: middle; white-space: pre;" id="num">00</text>
        <text x="36.008" y="31.952" style="fill: rgb(255, 255, 255); font-family: Verdana; font-size: 13px; white-space: pre;">%</text>
        <text x="19.485" y="46.88" style="fill: rgb(255, 255, 255); font-family: Verdana; font-size: 13px; white-space: pre;">OFF</text>
    </g>
</svg>

例はURLパラメータで割引率の数値を取得して、数値に寄って塗りの色を変更します。

HTMLから読み込む

img タグだとスクリプトが実行されないので object タグで読み込みます。

<object data="images/badge.svg?5" type="image/svg+xml" class="sale_icon"></object>

デモ

デモページ

SVG ファイルをテンプレート化して使いまわせるようになりました(•ө•)ノ

メリットとデメリット

object タグで SVG を読み込むと新たにドキュメントを作成するので CSS や JavaScript 等をカプセル化できグローバルを汚染しません。Web Components 的方法かなーと思います。ただこの方法は要素をたくさん使う場合その数だけリクエストが発生してしまうので、10個ぐらいならいいけど20個30個あるとう~んな感じです。その代わり複雑な処理もまとめることが出来るので、内容を動的に変えたいバナーとかではこういう方法もありだと思います。

スプライト化して埋め込む

リクエスト数を減らす方法としては SVG を HTML に埋め込んでしまうのが定石ですね。というわけでコンポーネント化とは少し離れますが埋め込み用に変更してみます。

<svg class="defs" xmlns="http://www.w3.org/2000/svg">
    <script>
        //<![CDATA[
        window.onload = function () {
            var use = document.querySelectorAll('svg[data-use="SvgSaleBadge"]');
            var template = document.querySelector('#SvgSaleBadge>g');
            for (var i=0; i < use.length; i++) {
                var elem = use[i];
                var num = elem.getAttribute('data-num');
                var content = template.cloneNode(true);
                var bg = content.querySelector('.SvgSaleBadge_bg');
                switch (true) {
                    case num > 60:
                        bg.style.fill = '#ea280e';
                        break;
                    case num > 30:
                        bg.style.fill = '#f96551';
                        break;
                    default:
                        bg.style.fill = '#ffbb00';
                }
                content.querySelector('.SvgSaleBadge_num').textContent = num;
                elem.appendChild(content);
            }
        };
        //] ]>
    </script>
    <defs>
        <symbol id="SvgSaleBadge" viewBox="0 0 60 60">
            <g transform="matrix(0.984807, -0.173648, 0.173648, 0.984807, -6.028826, 3.622701)">
                <use width="60" height="60" transform="matrix(0.9704837799072265, 5.551115123125783e-17, -5.551115123125783e-17, 0.9704837799072265, -727.7822813409801, -66.86588724536806)" x="751.6547012329102" y="72.65470123291016" style="fill: rgb(237, 91, 0);" xlink:href="#SvgSaleBadge-Bg" class="SvgSaleBadge_bg"></use>
                <text y="35.069" x="23.704" style="fill: rgb(255, 255, 255); font-family: Verdana; font-size: 18px; text-anchor: middle; white-space: pre;" class="SvgSaleBadge_num">00</text>
            </g>
        </symbol>
        <symbol id="SvgSaleBadge-Bg" viewBox="0 0 60 60">
            <g>
                <polygon points="55.558 14.953 47.433 12.334 44.815 4.209 36.47 6.002 30.137 0.275 23.805 6.001 15.46 4.208 12.841 12.334 4.716 14.953 6.51 23.299 0.782 29.63 6.508 35.961 4.715 44.308 12.841 46.928 15.46 55.052 23.805 53.257 30.137 58.984 36.468 53.258 44.815 55.052 47.435 46.925 55.696 44.333 53.765 35.963 59.491 29.63 53.765 23.299" />
                <text x="36.008" y="31.952" style="fill: rgb(255, 255, 255); font-family: Verdana; font-size: 13px; white-space: pre;">%</text>
                <text x="19.485" y="46.88" style="fill: rgb(255, 255, 255); font-family: Verdana; font-size: 13px; white-space: pre;">OFF</text>
            </g>
        </symbol>
        <symbol id="SvgPickup" viewBox="0 0 50 30">
            <ellipse cx="24.893" cy="14.611" rx="22.238" ry="11.159" style="fill: rgb(255, 116, 128);" />
            <text x="7.278" y="18.799" style="fill: rgb(255, 255, 255); white-space: pre; font-size: 12px;">Pickup</text>
        </symbol>
    </defs>
</svg>

PHP やコピペで HTML に埋め込みます。コピペだとメンテナンス性が下がるので読み込みが良いと思います。

<?php
echo file_get_contents('images/badge-inc.svg');
?>

HTMLから読み込む

use で読み込むと要素が操作できないため、元のシンボルのクローンを作成します。操作しなくても良い範囲は別にシンボルを作りましたが SVG 関係の記事を読むと use や symbol は内部処理が複雑なのであまり使わないほうが良いという記述も…。

<svg data-use="SvgSaleBadge" data-num="5"></svg>

デモ

デモページ

埋め込んでいるので表示は断然早いです。

ちなみに、fill などのスクリプティング以外で使うパラメータは param(name)url(#paramName) など方法があるようですが、試していないのでこれも後で調べてみたいと思います。

SVG非対応ブラウザへの配慮

IE8 以下では非対応になっています。重要性の低い要素については特に対応しなくても良いと思うのですが以下のような方法でフォールバックが可能です。

Modernizrを使用

object タグを使う場合は Modernizr などを使って振り分ける処理が出来ます。

if (!Modernizr.svg) {
// 非対応ブラウザの処理 代替画像などに入れ替える
}

foreignObjectを使用

svg タグを使う場合は foreignObject が使用できます。

<svg><foreignObject display="none"><img src="images/icon.png"></foreignObject></svg>