SVGには、<feDropShadow>という要素がある。これを使うと、SVGの図形にCSSのbox-shadowのような影をつけることができる。

feDropShadowタグの使い方

<feDropShadow>タグの’fe’は、’filter effect’の略だ。日本語ではそのままフィルタ効果と呼ぶ。これは、SVGにさまざまな画像効果を加える命令である。

フィルタ効果は、以下のようにして使う。

  1. まず、<defs>タグの中に任意のidを指定した<filter>タグを用意する。
  2. <filter>タグの中に、任意のフィルタ効果(<feXXX>的なタグとその属性値)を指定する。
  3. 任意の図形タグのfilter属性に、1,2で定義した<filter>タグのidを指定する。

<feDropShadow>タグの場合、以下のような属性値を指定して、影の効果を指定できる。

主な属性値

属性値の種類 概要
dx ドロップシャドウの X X 軸のオフセット値。デフォルト値は2
dy ドロップシャドウの Y Y 軸のオフセット値。デフォルト値は2
stdDeviation ドロップシャドウのぼかしに使う標準偏差。デフォルト値は2
flood-color 影の色。デフォルト値はblack
flood-opacity 影の不透明度。0-1%で指定。デフォルト値は1

このほか、id,class,styleといった基本的な属性値や、フィルタのプリミティブな属性値も指定できるが、使用頻度が高くないので割愛する。

影の領域を広げるには

<feDropShadow>タグに限らず、フィルタ効果の描画範囲は<filter>タグの4つの属性値に左右される。

属性値の種類 概要
x 対象要素の左上の X X 座標に対する、フィルタ領域の左上の X X 座標。デフォルト値は-10%
y 対象要素の左上の X X 座標に対する、フィルタ領域の左上の Y Y 座標。デフォルト値は-10%
width 対象要素の幅に対する、フィルタ領域の相対的な幅。デフォルト値は120%
height 対象要素の高さに対する、フィルタ領域の相対的な高さ。120%

うまいこと説明できなかった。デフォルトでは、図形の幅と高さの上下10%分までフィルタ効果の範囲が及ぶ。そこから先はスパッと途切れてしまう。

そのため、大きな影をつけたいときはfeDropShadowの属性値だけでなくfilterの属性値も適宜調整する必要がある。

SVGシェイプの影ツクール

上記の内容をもとに、<feDropShadow>タグの各属性値の挙動を確かめるデモを作成した。copyボタンを押下すると、<filter>タグの中身がコピーされる。

html
<!DOCTYPE html><html><head><!-- D3.jsの読み込み --><script src="https://cdnjs.cloudflare.com/ajax/libs/d3/7.8.5/d3.min.js" integrity="sha512-M7nHCiNUOwFt6Us3r8alutZLm9qMt4s9951uo8jqO4UwJ1hziseL6O3ndFyigx6+LREfZqnhHxYjKRJ8ZQ69DQ==" crossorigin="anonymous" referrerpolicy="no-referrer"></script></head>
  <body>
    <svg width="300" height="300" style="border:solid 1px #ddd">
      <defs>
        <filter id="filter"><feDropShadow id="shadow"></feDropShadow></filter>
      </defs>
      <rect
        width="100"
        height="100"
        x="100"
        y="100"
        filter="url(#filter)"
        fill="gray"
      ></rect>
    </svg>
    <div class="slider-container">
      <button id="reset">reset</button>
      <button id="copy">copy</button>
      <fieldset>
        <legend>filter</legend>
        <label
          >x:<input type="range" id="x" min="-500" max="500" value="-10" /><span
            >-10</span
          >%</label
        ><br />
        <label
          >y:<input type="range" id="y" min="-500" max="500" value="-10" /><span
            >-10</span
          >%</label>
          <br />
        <label
          >width:<input
            type="range"
            id="width"
            min="-500"
            max="500"
            value="120"
          /><span>120</span>%</label
        ><br /><label
          >dx:<input
            type="range"
            id="height"
            min="-500"
            max="500"
            value="120"
          /><span>120</span>%</label
        ><br />
      </fieldset>
      <fieldset>
        <legend>feDropShadow</legend>
        <label
          >dx:<input type="range" id="dx" min="-100" max="100" value="2" /><span
            >2</span
          ></label
        ><br /><label
          >dy:<input type="range" id="dy" min="-100" max="100" value="2" /><span
            >2</span
          ></label
        ><br /><label
          >stdDeviation:<input
            type="range"
            id="stdDeviation"
            min="-100"
            max="100"
            value="2"
          /><span>2</span></label
        ><br /><label>flood-color:<input type="color" id="flood-color" /></label
        ><br /><label
          >flood-opacity:<input
            type="range"
            id="flood-opacity"
            min="0"
            max="1"
            value="1"
            step="0.01"
          /><span>1</span></label
        >
      </fieldset>
    </div>
    <script>
      ["x", "y", "width", "height"].forEach((p) => {
        d3.select(`#${p}`).on("input", function (e) {
          const attr = e.srcElement.id;
          d3.select("#filter").attr(attr, this.value + "%");
          this.nextSibling.innerHTML = this.value;
        });
      });

      ["dx", "dy", "stdDeviation", "flood-color", "flood-opacity"].forEach((p) => {
        d3.select(`#${p}`).on("input", function (e) {
          const attr = e.srcElement.id;
          d3.select("#shadow").attr(attr, this.value);
          this.nextSibling.innerHTML = this.value;
        });
      });

      d3.select("#reset").on("click", () => window.location.reload());
      d3.select("#copy").on("click", (e) => {
        let source = d3.select("#filter").node().outerHTML;
        console.log(source);
        navigator.clipboard
          .writeText(source)
          .then(() => {
            e.srcElement.innerHTML = "copied";
            setTimeout(() => {
              e.srcElement.innerHTML = "copy";
            }, 500);
          })
          .catch((err) => {
            e.srcElement.innerHTML = "failed...";
            setTimeout(() => {
              e.srcElement.innerHTML = "copy";
            }, 500);
          });
      });
    </script>
  </body>
</html>

参考資料