tomixy's biography

SVG Filterでinsetなbox-shadowを再現する

CSSの再現で学ぶSVG Filter入門

tomixy's avatar
tomixy Thursday, November 9, 2023

HTML/CSSではいとも簡単に実現できてしまう、こんな表示。

div {
  /* inset | offset-x | offset-y | blur-radius | color */
  box-shadow: inset 4px 6px 8px rgba(142, 172, 205, 0.7);
 
  width: 200px;
  height: 200px;
  background-color: #d2e0fb;
}

もしもSVGしか使えない(style属性も禁止!な)世界に転生したとしたら、この表示をどう実現しますか?

このチュートリアルで、そんな過酷な世界を一緒に生きてみましょう。

持ち物はなにも要りません!
SVG Filterを触ったことがなくても、きっと大丈夫です。

影のベースとなるぼかしを作る

影とは結局、要素の内側か外側に加えた、特定の色のぼかし領域。

そこで、まずはぼかしの作り方を考える。

<feGaussianBlur />は、ガウスぼかしを実現するためのフィルタである。

<feGaussianBlur/>では、stdDeviation属性でぼかしの強さ(標準偏差)を指定できる。

<svg width="250" height="250" viewBox="0 0 250 250" xmlns="http://www.w3.org/2000/svg">
  <defs>
    <filter id="blur">
      <feGaussianBlur stdDeviation="4" />
    </filter>
  </defs>
  <rect fill="#D2E0FB" filter="url(#blur)" x="25" y="25" width="200" height="200" />
</svg>

冒頭のCSSのbox-shadowでは、ぼかし半径の値を8pxに設定していたが、ここでのstdDeviationにはその半分の値4を指定している。

ガウスぼかしでは、ぼかし効果のほとんど(95%)が、元のピクセルから半径stdDeviation * 2px以内に広がる。
つまり、stdDeviation属性には、ぼかしたい半径の半分の値を指定すればよい。

ちなみにこのフィルタだけなら、CSSのfilter: blur(4px);で事足りる。

rect {
  /** どちらも同じ効果になる */
  filter: url(#blur);
  filter: blur(4px);
}

SourceGraphicSourceAlpha

<filter />は、入力画像を変換した上で出力するものである。

<filter />の最初の子要素が受け取る入力画像は、このフィルタを指定した要素の描画結果である。

つまり、以下のコードでいえば、<feGaussianBlur />が受け取る入力画像は、filter="url(#blur)"を指定した<rect />要素の描画結果だ。

<svg width="250" height="250" viewBox="0 0 250 250" xmlns="http://www.w3.org/2000/svg">
  <defs>
    <filter id="blur">
      <feGaussianBlur stdDeviation="4" />
    </filter>
  </defs>
  <rect fill="#D2E0FB" filter="url(#blur)" x="25" y="25" width="200" height="200" />
</svg>

<feXXX />では、入力画像をin属性で指定することができる。

フィルタを指定した要素の描画結果は、SourceGraphicという名前で参照できる。

つまり、先ほどのコードの<feGaussianBlur />in="SourceGraphic"と指定しても、表示は変わらない。

<svg width="250" height="250" viewBox="0 0 250 250" xmlns="http://www.w3.org/2000/svg">
  <defs>
    <filter id="blur-in-src">
      <feGaussianBlur stdDeviation="4" in="SourceGraphic" />
    </filter>
  </defs>
  <rect fill="#D2E0FB" filter="url(#blur-in-src)" x="25" y="25" width="200" height="200" />
</svg>

一方で、in="SourceAlpha"と指定すると、次のような表示になる。

<svg width="250" height="250" viewBox="0 0 250 250" xmlns="http://www.w3.org/2000/svg">
  <defs>
    <filter id="blur-in-alpha">
      <feGaussianBlur stdDeviation="4" in="SourceAlpha" />
    </filter>
  </defs>
  <rect fill="#D2E0FB" filter="url(#blur-in-alpha)" x="25" y="25" width="200" height="200" />
</svg>

SourceAlphaは、filterプロパティを指定した要素の描画部分のと考えるとよい。

仮に、rect要素にfill-opacity属性を指定することで、描画部分を半透明にすると、SourceAlphaの結果である影も薄くなる。
要素が半透明になると、要素が完全に光を遮ることはなく、わずかに光が透過する…とイメージできる。

<svg width="250" height="250" viewBox="0 0 250 250" xmlns="http://www.w3.org/2000/svg">
  <defs>
    <filter id="blur-in-alpha">
      <feGaussianBlur stdDeviation="4" in="SourceAlpha" />
    </filter>
  </defs>
  <rect fill="#D2E0FB" fill-opacity="0.2" filter="url(#blur-in-alpha)" x="25" y="25" width="200" height="200" />
</svg>

まずは外側の影を作ってみる

SourceAlphaは、要素自身が落とす影だと捉えられる。

つまり、insetを指定しないbox-shadowdrop-shadowの影は、SourceAlphaをそのまま少しずらしたり、ぼかしたりすることで実現できる。

まずは、insetoffsetも指定しない真っ黒な影をbox-shadowを再現してみよう。
以下はHTMLとCSSで実現したもの。

div {
  /* offset-x | offset-y | blur-radius | color */
  box-shadow: 0 0 8px black;
 
  width: 200px;
  height: 200px;
  background-color: #d2e0fb;
}

上の例は、周囲をぼかした黒い四角形の上に、元の四角形を乗せることで再現できる。
ぼかし半径の分だけ、黒い四角形がはみ出して見えるので、その部分がbox-shadowを模倣するのだ。

さて、前の節で見たように、入力画像をSourceAlphaとして、<feGaussianBlur />を使えば、影用の四角形を作ることができる。

<svg width="250" height="250" viewBox="0 0 250 250" xmlns="http://www.w3.org/2000/svg">
  <defs>
    <filter id="box-shadow-behind">
      <feGaussianBlur in="SourceAlpha" stdDeviation="4" result="SHADOW" />
    </filter>
  </defs>
  <rect fill="#D2E0FB" filter="url(#box-shadow-behind)" x="25" y="25" width="200" height="200" />
</svg>

result属性によって、フィルタの出力に名前をつけることができる。
上の例では、影用の四角形にSHADOWという名前をつけている。

この影用の四角形(SHADOW)の上に、元の四角形(SourceGraphic)を重ねたい。

このようなニーズを叶えるのが、<feMerge />である。

<svg width="250" height="250" viewBox="0 0 250 250" xmlns="http://www.w3.org/2000/svg">
  <defs>
    <filter id="box-shadow">
      <feGaussianBlur in="SourceAlpha" stdDeviation="4" result="SHADOW" />
      <feMerge>
        <feMergeNode in="SHADOW" />
        <feMergeNode in="SourceGraphic" />
      </feMerge>
    </filter>
  </defs>
  <rect fill="#D2E0FB" filter="url(#box-shadow)" x="25" y="25" width="200" height="200" />
</svg>

<feMerge />を使うことで、複数のフィルタの出力画像を順番に重ねて出力することができる。

重ねたい出力画像は、<feMerge />の子として置いた<feMergeNode>in属性で指定する。
そして、<feMergeNode>を並べた順に上へと重なっていく。

ちなみに、上のコードは、次のように書いてもよい。

<svg width="250" height="250" viewBox="0 0 250 250" xmlns="http://www.w3.org/2000/svg">
  <defs>
    <filter id="box-shadow">
      <feGaussianBlur in="SourceAlpha" stdDeviation="4" />
      <feMerge>
        <feMergeNode />
        <feMergeNode in="SourceGraphic" />
      </feMerge>
    </filter>
  </defs>
  <rect fill="#D2E0FB" filter="url(#box-shadow)" x="25" y="25" width="200" height="200" />
</svg>

<filter />内に<feXXX />を並べていくことは、メソッドチェーンやパイプラインにたとえられる。
<filter />の子として並べられた各<feXXX />要素は、その直前の<feXXX />の出力を入力として受け取る。

<feMergeNode />in属性がない場合は、親である<feMerge />への入力がそのまま渡される。

今回の場合、<feMerge />の直前に<feGaussianBlur />を置いているので、result="SHADOW"in="SHADOW"で明示しなくても、<feGaussianBlur />の出力がそのまま渡されるのである。

影をずらす

insetキーワードを加えるのは最後の工程として、次はoffsetの指定を考えてみる。

この節で目指すのは、次のような表示だ。以下はHTML/CSSで描画したもの。

div {
  /* offset-x | offset-y | blur-radius | color */
  box-shadow: 4px 6px 8px black;
 
  width: 200px;
  height: 200px;
  background-color: #d2e0fb;
}

<feOffset />を使うことで、入力画像をずらすことができる。
xx方向の移動量はdx属性で、yy方向の移動量はdy属性で指定する。

<svg width="250" height="250" viewBox="0 0 250 250" xmlns="http://www.w3.org/2000/svg">
  <defs>
    <filter id="offset-shadow">
      <feGaussianBlur in="SourceAlpha" stdDeviation="4" />
      <feOffset dx="4" dy="6" />
      <feMerge>
        <feMergeNode />
        <feMergeNode in="SourceGraphic" />
      </feMerge>
    </filter>
  </defs>
  <rect fill="#D2E0FB" filter="url(#offset-shadow)" x="25" y="25" width="200" height="200" />
</svg>

<feOffset /><feGaussianBlur />の直後に置くことで、<feOffset />への入力画像が<feGaussianBlur />になるので、ぼかした影をずらすことになる。

そしてそのずらした結果は最初の<feMergeNode />にそのまま渡され、元の<rect />にずらした影を合成した結果が得られる。

影に色をつける

真っ黒な影を望む場合は、このままでも十分だろう。
しかし、そうでない場合が多いと思うので、影の色をカスタマイズする方法を探っていく。

以下はHTML/CSSで描画した、目標とする表示。

div {
  /* offset-x | offset-y | blur-radius | color */
  box-shadow: 4px 6px 8px rgba(142, 172, 205, 0.7);
 
  width: 200px;
  height: 200px;
  background-color: #d2e0fb;
}

まず、フィルタ内で色を使うためには、<feFlood />を使用する。

とはいえ、<feFlood />は、フィルタがかかる領域を超えて、全面を指定した色で塗りつぶすものなので、これだけでは<svg>要素全体に色が載ってしまう。

<svg width="250" height="250" viewBox="0 0 250 250" xmlns="http://www.w3.org/2000/svg">
  <defs>
    <filter id="use-flood">
      <feGaussianBlur in="SourceAlpha" stdDeviation="4" />
      <feOffset dx="4" dy="6" />
      <feFlood flood-color="rgb(142, 172, 205)" flood-opacity="0.7" />
      <feMerge>
        <feMergeNode />
        <feMergeNode in="SourceGraphic" />
      </feMerge>
    </filter>
  </defs>
  <rect fill="#D2E0FB" filter="url(#use-flood)" x="25" y="25" width="200" height="200" />
</svg>

特定の領域にのみ色を載せたい場合は、うまいことマスクしてあげる必要がある。

<feComposite />を使えば、in属性とin2属性で指定した2つの画像を、各部分でどちらの表示を採用するかを選択しながら合成することができる。

どちらの表示を採用するかの選択は、<feComposite />operator属性に指定した合成演算によって行われる。

中でも、operator="in"と指定すると、

  • in2画像の不透明な部分ではin画像を採用
  • in2画像の透明な部分ではin2画像を採用

という合成が行われる。

今回の場合は、in2に影用の四角形、in<feFlood />によって塗られた無限平面を指定すればよい。

  • 影用の四角形の内側は<feFlood />で指定した色で塗りつぶされる
  • 影用の四角形の外側では、<feFlood />の色は破棄される

という挙動になり、次のような表示が得られる。

<svg width="250" height="250" viewBox="0 0 250 250" xmlns="http://www.w3.org/2000/svg">
  <defs>
    <filter id="color-shadow">
      <feGaussianBlur in="SourceAlpha" stdDeviation="4" />
      <feOffset dx="4" dy="6" result="SHADOW" />
      <feFlood flood-color="rgb(142, 172, 205)" flood-opacity="0.7" />
      <feComposite in2="SHADOW" operator="in" />
      <feMerge>
        <feMergeNode />
        <feMergeNode in="SourceGraphic" />
      </feMerge>
    </filter>
  </defs>
  <rect fill="#D2E0FB" filter="url(#color-shadow)" x="25" y="25" width="200" height="200" />
</svg>

<feComposite />in属性を省略した場合、その直前の<feXXX />の出力がin属性として渡される。

いよいよinsetの再現へ

これで、insetを指定しなかった場合のbox-shadowが完成した。
いよいよ、insetと同じ効果を持つフィルタを加えていくフェーズである。

insetを再現する方法はいくつかあるので、それぞれの考え方を鑑賞してみよう。

方法1: xorinの合わせ技

<feComposite />をうまく使えば、影用の四角形が<rect />の内側でのみ見えるようにマスクすることができる。

<feComposite />での演算を考える際には、in属性の入力画像とin2属性の入力画像が、どのように重なっているかを考えるとよい。

以下の図では、グレーに塗られている部分が影となる四角形、赤い枠線内が元の<rect />部分(SourceAlphaの領域)を表している。

<feComposite />in属性を影の四角形、in2属性をSourceAlphaとしよう。

このとき、operator="xor"と指定すると、in画像とin2画像の重ならない部分のみが残る。

<svg width="250" height="250" viewBox="0 0 250 250" xmlns="http://www.w3.org/2000/svg">
  <defs>
    <filter id="xor">
      <feGaussianBlur in="SourceAlpha" stdDeviation="4" />
      <feOffset dx="4" dy="6" />
      <feComposite in2="SourceAlpha" operator="xor" />
    </filter>
  </defs>
  <rect fill="#D2E0FB" filter="url(#xor)" x="25" y="25" width="200" height="200" />
</svg>

外側の影と内側の影が両方とも残る結果になっている。

そこで、さらに<feComposite />を使って、外側の影を消してしまおう。

operator="in"と指定すると、in画像とin2画像の重なる部分のみが残るので、元の<rect />SourceAlpha)の範囲外の部分は消えることになる。

<svg width="250" height="250" viewBox="0 0 250 250" xmlns="http://www.w3.org/2000/svg">
  <defs>
    <filter id="xor-and-in">
      <feGaussianBlur in="SourceAlpha" stdDeviation="4" />
      <feOffset dx="4" dy="6" />
      <feComposite in2="SourceAlpha" operator="xor" />
      <feComposite in2="SourceAlpha" operator="in" />
    </filter>
  </defs>
  <rect fill="#D2E0FB" filter="url(#xor-and-in)" x="25" y="25" width="200" height="200" />
</svg>

これで目的の位置の影が得られた🎉

完成形はこちら。

<svg width="250" height="250" viewBox="0 0 250 250" xmlns="http://www.w3.org/2000/svg">
  <defs>
    <filter id="inset-shadow-1">
      <feGaussianBlur in="SourceAlpha" stdDeviation="4" />
      <feOffset dx="4" dy="6" />
      <feComposite in2="SourceAlpha" operator="xor" />
      <feComposite in2="SourceAlpha" operator="in" result="SHADOW" />
      <feFlood flood-color="rgb(142, 172, 205)" flood-opacity="0.7" />
      <feComposite in2="SHADOW" operator="in" />
      <feMerge>
        <feMergeNode in="SourceGraphic" />
        <feMergeNode />
      </feMerge>
    </filter>
  </defs>
  <rect fill="#D2E0FB" filter="url(#inset-shadow-1)" x="25" y="25" width="200" height="200" />
</svg>

ちなみに、外側に影をつけたときとは<feMergeNode />の順序が異なっている。

<svg width="250" height="250" viewBox="0 0 250 250" xmlns="http://www.w3.org/2000/svg">
  <defs>
    <filter id="inset-shadow-1">
      <feGaussianBlur in="SourceAlpha" stdDeviation="4" />
      <feOffset dx="4" dy="6" />
      <feComposite in2="SourceAlpha" operator="xor" />
      <feComposite in2="SourceAlpha" operator="in" result="SHADOW" />
      <feFlood flood-color="rgb(142, 172, 205)" flood-opacity="0.7" />
      <feComposite in2="SHADOW" operator="in" />
      <feMerge>
        <feMergeNode in="SourceGraphic" />
        <feMergeNode />
      </feMerge>
    </filter>
  </defs>
  <rect fill="#D2E0FB" filter="url(#inset-shadow-1)" x="25" y="25" width="200" height="200" />
</svg>

内側に影をつける場合は、<feMergeNode in="SourceGraphic" />を先に置くことで、影の方が上に重なるようにしている。
そうしなければ、要素の内側の影は要素の塗りに隠れてしまうからだ。

方法2: arithmeticで差集合をつくる

方法1では、<feComposite />を2回繰り返して使うことになり、あまりスマートとはいえない。

<feComposite />operatorには、arithmeticという値を指定することもできる。
この値を指定すると、色の選び方をより細かいパラメータk1k2k3k4属性で制御できるようになる。

具体的には、各ピクセルで次のような計算が行われる。
i1in画像の計算対象ピクセルの色の値、i2in2画像の計算対象ピクセルの色の値を表している。

out = k1 * i1 * i2 + k2 * i1 + k3 * i2 + k4

k1k2k3k4属性は、指定しなければデフォルト値として0が使われる。

仮に、k2 = -1k3 = 1とし、それ以外は0のままにしておくと、おもしろい式が得られる。

out = k1 * i1 * i2 + k2 * i1 + k3 * i2 + k4
    = 0 * i1 * i2 + (-1) * i1 + 1 * i2 + 0
    = i2 - i1

つまり、in2画像とin画像の差集合をとる演算になる。

集合in2 - inは、in2の領域のうち、inが重ならない部分を表す。

下の図でいえば、赤枠内のうち、影が乗っていない白い部分だけが残ることになる。

つまり、こうなる。(差集合はあくまでもイメージで、実際にはアルファ値の引き算を行っているため、ぼかしにより半透明になっている部分は、その透明度に応じて残ることになる。)

<svg width="250" height="250" viewBox="0 0 250 250" xmlns="http://www.w3.org/2000/svg">
  <defs>
    <filter id="arithmetic">
      <feGaussianBlur in="SourceAlpha" stdDeviation="4" />
      <feOffset dx="4" dy="6" />
      <feComposite in2="SourceAlpha" operator="arithmetic" k2="-1" k3="1" />
    </filter>
  </defs>
  <rect fill="#D2E0FB" filter="url(#arithmetic)" x="25" y="25" width="200" height="200" />
</svg>

この演算を利用した完成形はこちら。

<svg width="250" height="250" viewBox="0 0 250 250" xmlns="http://www.w3.org/2000/svg">
  <defs>
    <filter id="inset-shadow-2">
      <feGaussianBlur in="SourceAlpha" stdDeviation="4" />
      <feOffset dx="4" dy="6" />
      <feComposite in2="SourceAlpha" operator="arithmetic" k2="-1" k3="1" result="SHADOW" />
      <feFlood flood-color="rgb(142, 172, 205)" flood-opacity="0.7" />
      <feComposite in2="SHADOW" operator="in" />
      <feMerge>
        <feMergeNode in="SourceGraphic" />
        <feMergeNode />
      </feMerge>
    </filter>
  </defs>
  <rect fill="#D2E0FB" filter="url(#inset-shadow-2)" x="25" y="25" width="200" height="200" />
</svg>

方法3: 内側に影を落とすための壁を作る(Safari・iOS非対応)

要素の外側の影は、要素自身が落とす影である。

しかし、inset指定で実現される要素の内側の影は、その要素を取り囲む壁が落とす影だと考えられる。
そこで、まずは要素の周りに壁をつくりたい。

SourceAlphaでは、塗られている部分は真っ黒に設定されてしまうが、塗りの外側の部分は透明なままである。
この透明部分と黒い部分を反転させることができれば、塗りの外側の部分が黒く塗りつぶされるので、これが要素を取り囲む壁となる。

入力画像の色を加工する<feComponentTransfer />をうまく使うことで、SourceAlphaの反転を実現できる。

まずは<feComponentTransfer />の使い方の一例を見てみよう。

<svg width="550" height="240" viewBox="0 0 550 240" xmlns="http://www.w3.org/2000/svg">
  <defs>
    <filter id="half-opacity">
      <feComponentTransfer in="SourceGraphic">
        <feFuncA type="table" tableValues="0 0.5" />
      </feComponentTransfer>
    </filter>
  </defs>
  <rect fill="#D2E0FB" x="10" y="20" width="200" height="200" />
  <rect fill="#D2E0FB" filter="url(#half-opacity)" x="300" y="20" width="200" height="200" />
</svg>

<feComponentTransfer />は、子要素として<feFuncR />, <feFuncG />, <feFuncB />, <feFuncA />を並べることで、入力画像の色のR, G, B, Aそれぞれを個別に操作して色を加工するフィルタ。

<feFuncX />では、type属性で操作の種類を指定する。
type="table"と指定すると、tableValues属性で指定した値に従って、色の範囲を変換することができる。

上の例では、フィルタを適用した右側の<rect />だけ、半透明になっている。
半透明にする指示を表しているのが、<feFuncA />の部分だ。

<feFuncA />tableValues属性には、0 0.5と指定している。
本来、RGBAのA値(アルファチャネル、不透明度)は、0から1の値を取りうるが、それを0から0.5の範囲に圧縮しているのだ。
結果、元の不透明度が1の場合は0.5に、0.5の場合は0.25に…と、不透明度が半分になるように変換されている。

さて、透明な部分と塗りの部分を反転させるには、tableValues属性の値をどう設定すべきか、予想がついただろうか?

<svg width="550" height="240" viewBox="0 0 550 240" xmlns="http://www.w3.org/2000/svg">
  <defs>
    <filter id="outside">
      <feComponentTransfer in="SourceAlpha">
        <feFuncA type="table" tableValues="1 0" />
      </feComponentTransfer>
    </filter>
  </defs>
  <rect fill="#D2E0FB" x="10" y="20" width="200" height="200" />
  <rect fill="#D2E0FB" filter="url(#outside)" x="300" y="20" width="200" height="200" />
</svg>

tableValues属性の値は、数が小さい順に並べなければならないわけではない。
むしろ最大値と最小値を入れ替えることで、反転を実現することができる。

透明(不透明度0)な部分と塗り(不透明度1)の部分を反転させたい場合は、tableValues属性の値を1 0と指定すればよい。
すると、元の不透明度が0の場合は1に、1の場合は0に…と、不透明度が反転する。

これで、<rect />を取り囲む壁ができた。

<rect />の範囲外を黒く塗りつぶすことができているのは、SVGのフィルタ操作は、元画像より10%大きいボックスを用意して行われるためだ。
(しかし、SafariやiOSでは挙動が異なるようで、<rect />の範囲外の塗りの部分は見えなくなってしまっているはずだ。)

さて、これまで作ってきた影は、入力画像がSourceAlphaであるため、<rect />自身が落とす影になっていた。
たった今作った壁を入力画像に変えることで、この壁が落とす影を生成できる。

そのためには、<feGaussianBlur />in属性の値が、<feComponentTransfer />の結果(壁)になるようにすればよい。

<svg width="250" height="250" viewBox="0 0 250 250" xmlns="http://www.w3.org/2000/svg">
  <defs>
    <filter id="blur-outside">
      <feComponentTransfer in="SourceAlpha">
        <feFuncA type="table" tableValues="1 0" />
      </feComponentTransfer>
      <feGaussianBlur stdDeviation="4" />
      <feOffset dx="4" dy="6" result="SHADOW" />
      <feFlood flood-color="rgb(142, 172, 205)" flood-opacity="0.7" />
      <feComposite in2="SHADOW" operator="in" />
    </filter>
  </defs>
  <rect fill="#D2E0FB" filter="url(#blur-outside)" x="25" y="25" width="200" height="200" />
</svg>

つぎに、<rect />の外側の壁を消して、影だけを残したい。

特定の領域にのみ色を載せたい場合のマスク処理は、<feComposite operator="in" />を使うことで実現できたことを覚えているだろうか。

今回は、in="SHADOW"in2="SourceAlpha"とすることで、

  • <rect />の塗りの部分ではinを採用し、影のみ残る
  • <rect />の範囲外ではin2を採用し、何も残らない(<rect />範囲外のin2は透明)

という挙動を実現する。

<svg width="250" height="250" viewBox="0 0 250 250" xmlns="http://www.w3.org/2000/svg">
  <defs>
    <filter id="inset-shadow-only">
      <feComponentTransfer in="SourceAlpha">
        <feFuncA type="table" tableValues="1 0" />
      </feComponentTransfer>
      <feGaussianBlur stdDeviation="4" />
      <feOffset dx="4" dy="6" result="SHADOW" />
      <feFlood flood-color="rgb(142, 172, 205)" flood-opacity="0.7" />
      <feComposite in2="SHADOW" operator="in" />
      <feComposite in2="SourceAlpha" operator="in" />
    </filter>
  </defs>
  <rect fill="#D2E0FB" filter="url(#inset-shadow-only)" x="25" y="25" width="200" height="200" />
</svg>

最後に、元の<rect />と影を合成するための<feMerge />を再度追加しよう。

<svg width="250" height="250" viewBox="0 0 250 250" xmlns="http://www.w3.org/2000/svg">
  <defs>
    <filter id="inset-shadow-3">
      <feComponentTransfer in="SourceAlpha">
        <feFuncA type="table" tableValues="1 0" />
      </feComponentTransfer>
      <feGaussianBlur stdDeviation="4" />
      <feOffset dx="4" dy="6" result="SHADOW" />
      <feFlood flood-color="rgb(142, 172, 205)" flood-opacity="0.7" />
      <feComposite in2="SHADOW" operator="in" />
      <feComposite in2="SourceAlpha" operator="in" />
      <feMerge>
        <feMergeNode in="SourceGraphic" />
        <feMergeNode />
      </feMerge>
    </filter>
  </defs>
  <rect fill="#D2E0FB" filter="url(#inset-shadow-3)" x="25" y="25" width="200" height="200" />
</svg>

完成!!🎉