SVGとReactでフキダシを作って遊んでみた

この記事は [グラフィックス全般 Advent Calendar 2023](https://qiita.com/advent-calendar/2023/graphics) 5日目の記事です。

# きっかけ等

筆者は個人でウェブ技術を使った [お絵描きアプリ](https://warotarokz.sakura.ne.jp/illustmate/) を作っています。
そのアプリでは保存後に広告を出すのですが、その前後で保存ボタンの上にメッセージを出しています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/59191/148022db-046a-d764-fa79-0aaa09b5784c.png)


実装するとき、できたらフキダシにしたいと思ったんですけど、アレが面倒くさいんですよね。

![スクリーンショット 2023-12-05 181552.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/59191/aeb3d0b4-3174-ef4d-b18f-8402c807f1b7.png)

この、なんと呼ぶのか、しっぽみたいなモノがHTMLで実現するのが案外面倒くさいという記憶があったんです。

# HTMLでフキダシのアレを表現する方法

たしか、いにしえの時代にはGIF画像やPNG画像でやっている例がありましたが、昨今はあまり見ない気がします。今調べて出てくる情報はCSSでbeforeやafterを使った方法が多いようですね。調べた範囲では以下の方法がありました。

- 画像を使う
- divで四角を作りCSSでtransform:rotateZ(45deg)する
- CSSでbeforeやafterで要素を追加しborderの一部をtransparentにして三角にする（ボーダーはbeforeの上に背景色のafterを重ねてボーターっぽくしたりする）
- CSSで[clip-path](https://developer.mozilla.org/en-US/docs/Web/CSS/clip-path)を使う
- インラインSVGで描く

上の３つは単純簡単ですが、複数の要素を組み合わせる関係で半透明にすると重なっている部分が見えてしまうのがネックになる場合があります。clip-pathはSVGに似ていてかなり応用がききますが、ボーダーが表現しづらいという問題もあります。インラインSVGはコード量が多くなりがちなかわりに自由度が高いです（ただしサイズが固定になる）。

今回はかっこよく半透明にしてみたいのと、個人的にSVGはあまり手書きしたことがなかったので、この機会に勉強してみました。自分にとってアドベントカレンダーは年に一度だけ強制的に自分に勉強させる期間になってます（笑）

# SVGの基礎（超簡易版）


```html
<svg version="1.1" xmlns="http://www.w3.org/2000/svg"
  width="320" height="340" viewBox="-10 -10 120 140"
>
  <path stroke="#ccc" fill="#000" d="
    M 40.0 100.0 L 50.0 120.0 L 60.0 100.0
    L 90.0 100.0
    C 95.0 100.0 100.0 95.0 100.0 90.0
    L 100.0 10.0
    C 100.0 5.0 95.0 0.0 90.0 0.0
    L 10.0 0.0
    C 5.0 0.0 0.0 5.0 0.0 10.0
    L 0.0 90.0
    C 0.0 95.0 5.0 100.0 10.0 100.0
    Z
  "/>
  <text x="50" y="55" font-size="1em" fill="#fff" 
    text-anchor="middle" alignment-baseline="middle"
  >着信あり</text>
</svg>
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/59191/ca261d38-0196-6e19-e719-a6d01c62d8ba.png)

HTMLが分かる人であれば上のコードを見ただけでもなんとなく想像できる部分が多いのではと思います。分かりづらいのは、コード中央部分のpathタグのMとかLとか少数がわらわら書いてあるところでしょう。それぞれの次のような意味があります。

| コマンド | 意味 |
|:-----------|:------------|
| M x y    | (x, y) の位置に描画カーソルを移動する |
| L x y    | カーソル位置から (x, y) の位置まで直線を引く |
| C x1 y1 x2 y2 x y | (x1, y1)、(x2, y2) を制御点としてカーソル位置から(x, y)まで三次ベジエ曲線を引く |
| Z    | パスを閉じる |

要するに M は MoveTo で L は LineTo で、C は… C は…なんでしょうね… Curve とかかもしれませんね…。Z は…うーん、Zip とか？

ともかく、これだけでも基本的な図形はかなりなんでも描けます。また、pathはテキストを描画するときにpathに沿って描画することにも使えますし、アニメーションにも使うことができますので、非常に便利です。

ちなみに、pathの中のコマンドは空白などを極力詰めて書くことができ、同じ内容を次のように書くこともできます。ドローソフトで出力したものは多くが詰めた形になっているようです。

```html
M40 100L50 120L60 100L90 100C95 100 100 95 100 90L100 10C100 5 95 0 90 0L10 0C5 0 0 5 0 10L0 90C0 95 5 100 10 100Z
```

# 作ってみた

インタラクションがあることを前提として、Reactと組み合わせて作ってみました。

<p class="codepen" data-height="400" data-default-tab="result" data-slug-hash="rNPQrXO" data-user="warotarock" style="height: 300px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;">
  <span>See the Pen <a href="https://codepen.io/warotarock/pen/rNPQrXO">
  アドベントカレンダー２０２３「ＳＶＧで遊んでみる」</a> by 柏崎ワロタロ (<a href="https://codepen.io/warotarock">@warotarock</a>)
  on <a href="https://codepen.io">CodePen</a>.</span>
</p>
<script async src="https://cpwebassets.codepen.io/assets/embed/ei.js"></script>

ポイントは以下。

1. 半透明が問題なく使える
1. ボーダーも問題なく使える
1. 今回はサイズは固定
1. SVGにテキストボックスが無い（たぶん）のでpathに沿う機能で代替
1. idの管理はプリフィックスをつけるだけでよしとした

3と4は課題ですが、Reactと組み合わせて作り込めばなんとかなりそうな気がします。

それと、実は非常に重要なこととして、インラインSVGの中で要素に設定されたidはグローバルなidになります。そのためコンポーネントとして使い回す場合、idが重複しないようにする必要があります。ネットで調べてみるとランダムにユニークなIDを生成して使う方法が多いようです。しかし、今回は難しいことはしたくなかったので、SVG内のidにはプリフィックスを付けることにして、プリフィックスは決め打ちにしました。

# おわりに

SVG便利です。

課題が残ったのでつづくかも？（つづかないかも）→ [続きました。](https://qiita.com/warotarock/items/0142d6bee4838b7b46a4)