♪Story in the SVG また繋がるフラグメント (c) AAA

symbol & useをひとしぼり! NEW SVGスプライト!

使えば爽快! SVG Spriteの改訂版。 SVGのsymbolとuseを使ってSVGスプライトを利用する方法を、再度、考えてみた。

今回のSVGのサンプルソース。 ターゲットになっていないuse要素はCSSで非表示にしてしまうことがコツといえばコツ。 あと、ルートのsvg要素には、widthもheightもつけず、viewBoxのみ。


<svg xmlns="http://www.w3.org/2000/svg"
     xmlns:xlink="http://www.w3.org/1999/xlink"
     viewBox="0 0 128 128">
  <style>
    use:not(:target) {
      display: none;
    }
  </style>
  <symbol id="rect1">
    <title>Rect</title>
    <rect x="1" y="1" width="126" height="126"/>
  </symbol>
  <symbol id="circle1">
    <title>Circle</title>
    <circle cx="64" cy="64" r="64"/>
  </symbol>
  <symbol id="triangle1">
    <title>Triangle</title>
    <polygon points="0,128 128,128 64,0"/>
  </symbol>
  <use id="rect" xlink:href="#rect1" fill="rgb(255,0,0)"/>
  <use id="circle" xlink:href="#circle1" fill="rgb(0,255,0)"/>
  <use id="triangle" xlink:href="#triangle1" fill="rgb(0,0,255)"/>
</svg>

CSSの背景画像としてSVGスプライトを使う場合


/* SVG未対応のレガシーブラウザ向け */
@media screen {
  .sample {
    background-image: url(path/to/circle.png);
    background-repeat: repeat;
  }
  .sample:hover {
    background-image: url(path/to/triangle.png);
  }
}

/* SVG対応ブラウザ向け (Safari、IE11以下) */
@media screen and (color) {
  .sample {
    background-image: url(path/to/circle.svg);
    background-size: 3em;
  }
  .sample:hover {
    background-image: url(path/to/triangle.svg);
  }
}

/* SVGスプライト対応ブラウザ向け (Firefox、Chrome、Edge) */
@media screen and (min-resolution: 1dppx) {
  @supports (background-image: unset) {
    .sample {
      background-image: url(path/to/sprite.svg#circle);
    }
    .sample:hover {
      background-image: url(path/to/sprite.svg#triangle);
    }
  }
}

一応、単体のSVGも用意してある場合を想定しているが、それを省略してSafariやIE11以下もレガシーブラウザの扱いにすることも可能。

img要素でSVGスプライトを使う場合


<img src="path/to/sample.png" srcset="path/to/sprite.svg#sample" />

/**
 * svgSpriteFromSrcset
 * SVG#fragmentなスプライト画像をimg要素のsrcset属性から見つけて適用させるJavaScript
 * SVG#fragmentにバグがあるSafariには逆に適用させないようsrcsetを削除
 * 注1:img要素にはsrcset属性が付与されていること
 * 注2:srcset属性の値は、SVGスプライト画像のURLのみを指定すればOK(xやwは不要)
 *     (例)<img src="someId.png" srcset="svgSprite.svg#someId" /> など
 * Copyright (c) 2014-2016 Kazz
 * http://asamuzak.jp
 * Dual licensed under MIT or GPL
 * http://asamuzak.jp/license
 */

(function(_win, _doc) {
  "use strict";
  function svgSpriteFromSrcset() {
    function svgSpriteByDefault(o) {
      return o.srcset &&
             _win.matchMedia && _win.matchMedia("(min-resolution: 1dppx)").matches &&
             _win.CSS && _win.CSS.supports("(background-image: unset)");
    }
    var x = _doc.querySelectorAll("img[srcset]"),
        l = x.length;
    if ((_doc.documentElement.matches || _doc.documentElement.msMatchesSelector) &&
        l > 0 && !svgSpriteByDefault(x[0])) {
      for (var a = /([\w\/:%\$&\(\)~\.=\+\-]+(?:\.svgz?)?#[\-]?[a-zA-Z_][\w\-]+)/,
           i = 0; i < l; i++) {
        var y = x[i],
            z = a.exec(y.getAttribute("srcset"));
        z && (
          y.srcset ?
            y.removeAttribute("srcset") :
            y.setAttribute("src", z[1])
        );
      }
    }
  }
  _doc.addEventListener("DOMContentLoaded", svgSpriteFromSrcset, false);
})(window, document);

srcset属性でSVG#fragmentを付与。

さらに、IEなどsvg#fragmentには対応しているもののsrcsetに未対応なブラウザ向けにJavaScriptで画像を差し替える処理を追加。 一方、SafariにはバグがあるのでSafariは除外しsrcを読ませている。

object要素でSVGスプライトを使う場合


<object data="path/to/sprite.svg#sample" type="image/svg+xml">
  <img src="path/to/sample.png" />
</object>

SVGスプライトに限らず外部SVGの読み込みにはimg要素よりもobject要素の方が適している。 何より元々フォールバックの仕組みを備えている。 Safariはなぜかobject要素から読み込ませるとちゃんとSVGスプライトが適用されるし、IE11や9を含めブラウザのバグがほとんどない。 (古いAndroid標準ブラウザではSVGスプライトのルートのsvg要素にwidthとheightが指定されているとリサイズされずに表示されてしまうというものがあったが…)

唯一、アンカーやボタンなどインタラクティブな要素の子孫でobject要素からSVGを使おうとすると、そのSVGの中でインタラクティブに見栄えなどを変えるようにしていても、それが適用されないという難点がある。 しかし、この点については、別記事で対策してみたのでご参照を。

インラインSVGから外部のSVGスプライトを使う場合


<svg>
  <switch>
    <image width="100%" height="100%" xlink:href="path/to/sprite.svg#sample"
           requiredFeatures="http://www.w3.org/TR/SVG11/feature#BasicGraphicsAttribute" />
    <switch>
      <use xlink:href="path/to/sprite.svg#sample"
           requiredFeatures="http://www.w3.org/TR/SVG11/feature#SVG" />
      <foreignObject>
        <img src="path/to/sample.png" />
      </foreignObject>
    </switch>
  </switch>
</svg>

インラインSVGから外部のSVGスプライトを読み込む場合も検証してみたところ、IEではuseから外部SVGスプライトを読み込まず、Safariではimageから外部SVGスプライトを読み込まないということを確認。 そこでSVGに元々あるswitch要素とrequiredFeatures属性を使って場合分けすれば、インラインSVGから外部のSVGスプライトが使える。

requiredFeaturesのうち、今のところ、Safariが未対応(falseを返す)なのは

  • http://www.w3.org/TR/SVG11/feature#SVG-dynamic
  • http://www.w3.org/TR/SVG11/feature#SVGDOM-dynamic
  • http://www.w3.org/TR/SVG11/feature#BasicGraphicsAttribute
  • http://www.w3.org/TR/SVG11/feature#ColorProfile
  • http://www.w3.org/TR/SVG11/feature#AnimationEventsAttribute

このうち、ColorProfileAnimationEventsAttributeはEdgeも実装していないので、この2つは判別としての利用には向かない。

"♪Story in the SVG また繋がるフラグメント (c) AAA"へのTwitter上でのコメントやRT

10件のツイートがあります。

ツイート 1

ツイート 2

ツイート 3

ツイート 4

ツイート 5

ツイート 6

ツイート 7

ツイート 8

ツイート 9

ツイート 10