🍜 SVGの色を変えたいときに試す4種の方法
作成日: 2021/10/24
0

読み飛ばして良いまえがき
ここのアイコンは色が変わるだけなのでインラインSVGで実装していたところ…… 「このSVGは外部ファイルにしてくれ!」 というオーダーが入ることは、稀によくあります。今回は、こんなときに「どうすれば良いんだ……。🤦🏻‍♂️」と頭を抱えているあなたのための回です。


本日の本題

まずはインラインSVG+CSSで色を変えよう

まず最初にそもそもインラインSVGで色を変えるというののおさらいです。
星型の箇所がSVGになっていて、マウスオーバーすると色が変わるというサンプルです。

これは通常のHTML+CSSと同じ感覚で扱えるのでとてもわかり易いです。 一方、今回はシンプルな星型だったので、コードに圧は感じませんが、ちょっと凝ったアイコンとかをSVGで用意した場合は、かなりの行数になりますし、それらがコードの可読性を下げてしまうということもあります。同時に必要な箇所にいちいち、このコードを埋め込むことになるため、メンテナンス性も下がります。 共通化して、それをサーバーサイドetc.で組み込むという場合には…… このアイコンのためだけにそんな頑張るんだっけ?みたいな話が発生してしまうので、ファイルを外部化し、それを読み込んでくれ!という要望が出てくるのは、よくわかります。

useを使って外部のSVGを読み込んで色を変えてみる

最初に思いつくのはidとuseを使ったパターンになるかと思います。
ここのサンプルは諸事情あってCodePen Projectを使っているので、詳細はリンク先で見てみてください。
https://codepen.io/kaitou1192/project/editor/AwymaK
の ver_use.html と star.svg です。

<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <title>Document</title>
  <style>
    div {
      width: 200px;
      margin: 0 auto;
    }
    svg {
      width: 200px;
      height: 200px;
      pointer-events: none;
    }
    use {
      fill: #009; 
      cursor: pointer;
      pointer-events: fill;
      transition: all 0.3s ease;
    }
    use:hover {
      fill: #f00; 
    }
  </style>
</head>
<body>
  <div>
    <svg>
      <use href="star_symbol.svg#star"></use>
    </svg>
  </div>  
</body>
</html>
<svg viewBox="0 0 200 200" xmlns="http://www.w3.org/2000/svg">
  <path id="star" d="m100 153.6-52.9 27.8 10.1-58.9-42.8-41.7 59.1-8.6 26.5-53.6 26.5 53.6 59.1 8.6-42.8 41.7 10.1 58.9z"/>
</svg>

SVGにはidで指定したものを、useで呼び出すという機能があります。
この際、呼び出すときに外部ファイルのSVG内のものも呼び出すということが可能です。

今回はSVG内のpathにid="star"を設定してuse xlink:href="star.svg#star"で呼び出しています。
star.svgがファイル、#starが対象ファイル内のid要素です。

ここで…… いつものようにどこか適当なところに画像ファイルを置いた上でのCodePenではなくCodePen Projectを使っている理由が出てきます。
このuseで外部のファイルを読み込む際、別ドメインのものである場合はクロスドメイン制約に引っかかります。
サーバー側で設定して解決する方法もあるのですが…… もしそれができない場合、別ドメインの画像を読み込むことはできません。

object+JavaScriptで外部のSVGを読み込んで色を変える

さて…… 経験が長い方の場合は、object経由でJS使えば良いじゃん!ということになると思います。
https://codepen.io/kaitou1192/project/editor/AwymaK
に ver_object.html を用意しました。
(外部svgファイルは先のと同じくstar.svgです。)

<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <title>Document</title>
  <style>
    div {
      width: 200px;
      margin: 0 auto;
    }
    object {
      width: 200px;
      height: 200px;
    }
  </style>
</head>
<body>
  <div>
    <object data="star.svg" type="image/svg+xml"></object>
  </div>
  <script>
    const star = document.querySelector('object[data="star.svg"]');
    
    const loadedFile = (event) => {
      const loadedTarget = event.target;
      const loadedDocument = loadedTarget.contentDocument;
      const loadedSvg = loadedDocument.querySelector('svg');
      const loadedPath = loadedDocument.querySelector('path');
      
      loadedPath.style.fill = '#009';
      loadedPath.style.cursor = 'pointer';
      loadedPath.style.transition = 'all 0.3s ease';

      loadedPath.addEventListener('mouseover', (event) => {
        loadedPath.style.fill = '#f00';
      });
      loadedPath.addEventListener('mouseout', (event) => {
        loadedPath.style.fill = '#009';
      });
    }
    star.addEventListener('load', (event) => loadedFile(event));
  </script>
</body>
</html>

たしかにこんな風に書けばやりたいことは実現できます。
ただし…… こちらも別ドメインの画像を読み込もうとすると、クロスドメイン制約に引っかかって読み込むことができません。

imgで読み込んで色を変える

クロスドメイン制約を飛び越えて外部の画像ファイルを読み込もうとすると…… アレしかありません。imgタグです。
https://codepen.io/kaitou1192/project/editor/AwymaK
の ver_img.html にサンプルを用意しました。
(外部svgファイルは先のと同じくstar.svgです。)

<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <title>Document</title>
  <style>
    div {
      width: 200px;
      margin: 0 auto;
    }
    img {
      width: 200px;
      height: 200px;
      filter: brightness(0) saturate(100%) invert(16%) sepia(97%) saturate(3684%) hue-rotate(239deg) brightness(58%) contrast(133%);
      transition: all 0.3s ease;
      cursor: pointer;
    }
    img:hover {
      filter: brightness(0) saturate(100%) invert(15%) sepia(97%) saturate(2775%) hue-rotate(350deg) brightness(117%) contrast(137%);
    }
  </style>
</head>
<body>
  <div>
    <img src="star.svg" alt="">
  </div>  
</body>
</html>

filterを使えばこんな風に作ることができます。
凄い。できる!

ただ…… いくつか注意点があります。

今までの3例では、星型に沿ったヒットエリアが作れましたが、このimgの方法ではpointer-events: fill;を使ってもそうはなりません。

また、単に赤にしたかっただけなのに「filter: brightness(0) saturate(100%) invert(15%) sepia(97%) saturate(2775%) hue-rotate(350deg) brightness(117%) contrast(137%);」という表記は人間が作れるとは思えない複雑さです。
僕はこんなサイトで生成したものを引っ張ってきて貼り付けました。
https://angel-rs.github.io/css-color-filter-generator/
またimg全体をfilterで色操作しているので「この丸いところだけ赤に変えたい」みたいなことはできません。(その場合は別画像にするとかとか、いろいろ工夫はできると思いますが。)

SVGの色を変えたいときのまとめ

  • インラインSVG
    • 一番扱いやすいけれど、コードの可読性やめんてなメンテナンス性etc.に問題がある
  • useを使っての外部SVGを読み込む
    • CSSが使えるのが便利
    • 別ドメインに置かれているSVGだとクロスドメイン制約に引っかかる
  • objectを使って外部SVGを読み込む
    • おそらくJSを使ってスタイルを操作することになるので手間がかかる
    • 別ドメインに置かれているSVGだとクロスドメイン制約に引っかかる
  • imgを使って外部SVGを読み込む
    • filterを使ってimg全体の色を変えることになるので複雑になりがち
    • 別ドメインに置いてもクロスドメイン制約には引っかからない

これは間違ってるよ!とか、こうすればその問題解決できるよ!は大歓迎なので、ぜひコメントに書き込んでいただければ。🙇🏻‍♂️



Twitterもやっているので、よかったらフォローしてください。🙏🏻
https://twitter.com/Kaitou1192


本業はコードを書かせてもらえないフロントエンドエンジニアです。 こんなサービス作っています。 https://lp.re-shine.jp