SVG で HTML の文字を CSS で擬似的に縁取りするのと同様の効果を得る方法

## HTML の文字を CSS で擬似的に縁取り

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/33891/1c0f0917-9132-e98e-3730-b2b41e73f65c.png)

このような HTML の文字に対する CSS での縁取りは次のように 8方向に `text-shadow` を施すと擬似的に再現できる。この手法では縁取り部分を僅かにぼかし柔らかな縁取りにしやすい。また、この手法は [Google Fonts / Material Symbols](https://fonts.google.com/icons) のようにフォント化され文字として扱える記号にも適用できるので、特にフレキシブルな設計で便利な場合もある。

（※この手法のブラウジング時の負荷は決して低くは無いが、近年の一般的なブラウジング環境ならばよほど乱用しない限りは問題なくなって久しい。）

```css
 /* HTML要素の「文字」を対象に擬似的に縁取りする CSS の例 */
 :root {
  /* 輪郭線にしたい色 */
  --shadow-color: #333;
  /* 輪郭線にしたい幅(長さ) */
  --shadow-thickness: 0.5mm;
  /* 輪郭線にしたい幅の2倍の + 向きの長さ = + 向きのオフセット値 */
  --shadow-offset-positive: calc(var(--shadow-thickness) * 2);
  /* 輪郭線にしたい幅の2倍の - 向きの長さ = - 向きのオフセット値 */
  --shadow-offset-negative: calc(var(--shadow-offset-positive) * -1);
  /* 8方向に影を出して擬似的に輪郭線を縁取る text-shadow 用の設定 */
  --shadow:
   /* 影付けの向き①↘: +x, +y = 右下 */
   var(--shadow-offset-positive) var(--shadow-offset-positive) var(--shadow-thickness) var(--shadow-color),
   /* 影付けの向き②↙: -x, +y = 左下 */
   var(--shadow-offset-negative) var(--shadow-offset-positive) var(--shadow-thickness) var(--shadow-color),
   /* 影付けの向き③➚: +x, -y = 右上 */
   var(--shadow-offset-positive) var(--shadow-offset-negative) var(--shadow-thickness) var(--shadow-color),
   /* 影付けの向き④↖: -x, -y = 左上 */
   var(--shadow-offset-negative) var(--shadow-offset-negative) var(--shadow-thickness) var(--shadow-color),
   /* 影付けの向き⑤→: +x,  0 = 右   */
   var(--shadow-offset-positive) 0 var(--shadow-thickness) var(--shadow-color),
   /* 影付けの向き⑥↓:  0, +y =   下 */
   0 var(--shadow-offset-positive) var(--shadow-thickness) var(--shadow-color),
   /* 影付けの向き⑦←: -x,  0 = 左   */
   var(--shadow-offset-negative) 0 var(--shadow-thickness) var(--shadow-color),
   /* 影付けの向き⑧↑:  0, -y =   上   */
   0 var(--shadow-offset-negative) var(--shadow-thickness) var(--shadow-color);
 }
```

## SVG で同様の縁取り効果を得る方法

次にこの記事の本題として HTML に SVG ファイルを `<img>` で読み出して使用する場合に上記の `text-shadow` と同様の縁取り効果を得たい場合にどうしたらよいのかを記述する。

### `stroke` を使う方法; 🆖🤔❓

SVG について少し知識があれば縁取りは `stroke` を使えばよいと思い当たる。しかし `stroke` では期待に近い表現となる場合/部分もあるが、例えば SVG に `<text>` が含まれる場合に意図しない表現となる場合もある。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/33891/3b0dd76d-e46f-4123-04ca-e18d451047d0.png)

```xml
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="100%" y="100%"
 viewBox="-2 -2 48 58" style="enable-background:new 0 0 44 44;" xml:space="preserve">
 <style>
  #a {
   stroke: #333;
   stroke-opacity: 0.9;
   fill: #FFF;
  }
 </style>
 <g id="a">
  <path d="M22,0C9.869,0,0,9.869,0,22s9.869,22,22,22s22-9.869,22-22S34.131,0,22,0z M22,42C10.972,42,2,33.028,2,22S10.972,2,22,2
		s20,8.972,20,20S33.028,42,22,42z" />
  <path d="M33,24c-0.552,0-1,0.448-1,1c0,5.514-4.486,10-10,10s-10-4.486-10-10c0-0.552-0.448-1-1-1s-1,0.448-1,1
		c0,6.617,5.383,12,12,12s12-5.383,12-12C34,24.448,33.552,24,33,24z" />
  <path d="M13,14c1.654,0,3,1.346,3,3c0,0.552,0.448,1,1,1s1-0.448,1-1c0-2.757-2.243-5-5-5s-5,2.243-5,5c0,0.552,0.448,1,1,1
		s1-0.448,1-1C10,15.346,11.346,14,13,14z" />
  <path d="M31,12c-2.757,0-5,2.243-5,5c0,0.552,0.448,1,1,1s1-0.448,1-1c0-1.654,1.346-3,3-3s3,1.346,3,3c0,0.552,0.448,1,1,1
		s1-0.448,1-1C36,14.243,33.757,12,31,12z" />
  <text x="5.5" y="52" font-size="6">with-stroke</text>
 </g>
</svg>
```

この問題は SVG のドラフト規格の `stroke-alignment` が現実のブラウジング環境で実用可能な時代が訪れれば解決する可能性もあるが、少なくとも執筆現在の Chrome-104.0.5112.102 ではまだ `stroke-alignment` は機能しない。

### `<filter>` + `<deDropShadow>` を使う方法; 🙆‍♀😊👍

SVG でも `<filter>` + `<deDropShadow>` で HTML の文字に対する CSS での `text-shadow` と同様の影付けを行える。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/33891/6267aca2-9811-822e-2aa5-68ae678f4ffc.png)

```xml
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="100%" y="100%"
 viewBox="-2 -2 48 58" style="enable-background:new 0 0 44 44;" xml:space="preserve">
 <style>
  #a {
   fill: #FFF;
   filter: url(#shadow)
  }
 </style>
 <filter id='shadow' color-interpolation-filters="sRGB">
  <feDropShadow dx="+0.2" dy="+0.2" stdDeviation="0.1" />
  <feDropShadow dx="-0.2" dy="+0.2" stdDeviation="0.1" />
  <feDropShadow dx="+0.2" dy="-0.2" stdDeviation="0.1" />
  <feDropShadow dx="-0.2" dy="-0.2" stdDeviation="0.1" />
  <feDropShadow dx="+0.2" dy=" 0  " stdDeviation="0.1" />
  <feDropShadow dx=" 0  " dy="+0.2" stdDeviation="0.1" />
  <feDropShadow dx="-0.2" dy=" 0  " stdDeviation="0.1" />
  <feDropShadow dx=" 0  " dy="-0.2" stdDeviation="0.1" />
 </filter>
 <g id="a">
  <path d="M22,0C9.869,0,0,9.869,0,22s9.869,22,22,22s22-9.869,22-22S34.131,0,22,0z M22,42C10.972,42,2,33.028,2,22S10.972,2,22,2
		s20,8.972,20,20S33.028,42,22,42z" />
  <path d="M33,24c-0.552,0-1,0.448-1,1c0,5.514-4.486,10-10,10s-10-4.486-10-10c0-0.552-0.448-1-1-1s-1,0.448-1,1
		c0,6.617,5.383,12,12,12s12-5.383,12-12C34,24.448,33.552,24,33,24z" />
  <path d="M13,14c1.654,0,3,1.346,3,3c0,0.552,0.448,1,1,1s1-0.448,1-1c0-2.757-2.243-5-5-5s-5,2.243-5,5c0,0.552,0.448,1,1,1
		s1-0.448,1-1C10,15.346,11.346,14,13,14z" />
  <path d="M31,12c-2.757,0-5,2.243-5,5c0,0.552,0.448,1,1,1s1-0.448,1-1c0-1.654,1.346-3,3-3s3,1.346,3,3c0,0.552,0.448,1,1,1
		s1-0.448,1-1C36,14.243,33.757,12,31,12z" />
  <text x="3.5" y="52" font-size="6">with-shadow</text>
 </g>
</svg>
```

この手法では冒頭に示した「HTML の文字に対する CSS の `text-shadow` による擬似的な縁取り」とおおよそ同様の実装手法と効果が得られる。

## おまけ: 拡大縮小性の確認

1. HTML+CSS
2. SVG with `stroke` in HTML
3. SVG with `shadow` in HTML

以上の3つの手法を実装し、 2. と 3. については 60mm 幅、 120mm 幅で拡大縮小性についても感知液に確認する HTML を記述した。

(※`<img>` の `src` 属性で読み出している .svg ファイルはそれぞれ先に紹介した `stroke` と `deDropShadow` での SVG 実装例をそのままファイルとして保存したもの。)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/33891/1b2afc70-7cdb-6e0d-68e0-8ff8afd14c2b.png)

```html
<!DOCTYPE html>

<style>
 /* HTML要素の「文字」を対象に擬似的に縁取りする CSS の例 */
 :root {
  /* 輪郭線にしたい色 */
  --shadow-color: #333;
  /* 輪郭線にしたい幅(長さ) */
  --shadow-thickness: 0.5mm;
  /* 輪郭線にしたい幅の2倍の + 向きの長さ = + 向きのオフセット値 */
  --shadow-offset-positive: calc(var(--shadow-thickness) * 2);
  /* 輪郭線にしたい幅の2倍の - 向きの長さ = - 向きのオフセット値 */
  --shadow-offset-negative: calc(var(--shadow-offset-positive) * -1);
  /* 8方向に影を出して擬似的に輪郭線を縁取る text-shadow 用の設定 */
  --shadow:
   /* 影付けの向き①↗: +x, +y = 右上 */
   var(--shadow-offset-positive) var(--shadow-offset-positive) var(--shadow-thickness) var(--shadow-color),
   /* 影付けの向き②↖: -x, +y = 左上 */
   var(--shadow-offset-negative) var(--shadow-offset-positive) var(--shadow-thickness) var(--shadow-color),
   /* 影付けの向き③↘: +x, -y = 右下 */
   var(--shadow-offset-positive) var(--shadow-offset-negative) var(--shadow-thickness) var(--shadow-color),
   /* 影付けの向き④↙: -x, -y = 左下 */
   var(--shadow-offset-negative) var(--shadow-offset-negative) var(--shadow-thickness) var(--shadow-color),
   /* 影付けの向き⑤→: +x,  0 = 右   */
   var(--shadow-offset-positive) 0 var(--shadow-thickness) var(--shadow-color),
   /* 影付けの向き⑥↑:  0, +y =   上 */
   0 var(--shadow-offset-positive) var(--shadow-thickness) var(--shadow-color),
   /* 影付けの向き⑦←: -x,  0 = 左   */
   var(--shadow-offset-negative) 0 var(--shadow-thickness) var(--shadow-color),
   /* 影付けの向き⑧↓:  0, -y =   下   */
   0 var(--shadow-offset-negative) var(--shadow-thickness) var(--shadow-color);
 }

 body * {
  border: hsla(0, 50%, 50%, 0.5) solid 4mm;
 }

 body {
  background-color: #888;
  color: #fff;
  font-size: 20mm;
 }

 #html-text {
  background-color: yellow;
  color: #fff;
  text-shadow: var(--shadow);
 }

 img {
  background-color: yellow;
  width: 60mm;
 }

 .x2{
  width: 120mm;
 }
</style>

<!-- 例A: HTML の文字列要素 + CSS による擬似的な縁取り -->
<p id="html-text">"This 😊 is a happy symbol."</p>

<!-- 例B: HTML に img で読み込む SVG + SVG の内部で stroke で縁取り -->
<img id="with-stroke" src="with-stroke.svg" />

<!-- 例C: HTML に img で読み込む SVG + SVG の内部で shadow で縁取り -->
<img id="with-shadow" src="with-shadow.svg" />

<br/>

<img class="x2" id="with-stroke" src="with-stroke.svg" />
<img class="x2" id="with-stroke" src="with-shadow.svg" />
```

### repos

- https://gist.github.com/usagi/d774894241257ef0b6faad02079fba39

## 参考

- https://developer.mozilla.org/en-US/docs/Web/CSS/text-shadow
- https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/stroke
- https://developer.mozilla.org/en-US/docs/Web/SVG/Element/feDropShadow
- https://alexwlchan.net/2021/03/inner-outer-strokes-svg/
- https://svgwg.org/specs/strokes/#SpecifyingStrokeAlignment

## License

- [CC0](https://creativecommons.org/choose/zero/)