これは何
- リロードする度に形状が変わるSVGを作ってみました
- ステップバイステップで作り方を説明します
- 完成系は以下です
なお、コードはGitHubに上げています。
はじめに:なぜそんなものを作ったか
ある日遊びでUIのモックアップを作っていて、雰囲気を出すために背景にモニョっとした形状をちりばめていました。
イメージとしてはこんな具合です。
普通にIllustratorやFigmaで作ったSVGを書き出して配置しても良いのですが……なんだか微妙に物足りない気持ち。
少し考えているうちに「リロードする度に微妙に形状が変わったら面白いんじゃないか?」と要らんことを思いついたのです。
SVGの理解
ランダムに作る前に、そもそもどういうルールで記載されているのかを知らないとお話になりません。
私はこちらの記事に助けていただきました。
見本の用意
いきなりコードだけでSVGを描き始めるのは無謀に感じられました。
そのためまずはFigmaで見本を用意して、それを少しずつ変えていく作戦でいきます。

こんな感じでそれっぽい形状を用意して、Copy as SVG
を実行→エディタに貼り付け。
現時点のコードはこのようになっています。
<svg width="400" height="400" viewBox="0 0 400 400" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="
M 320 128
C 220 178 258 50 134 32
C 10 14 -20 238 100 258
C 220 278 158 378 268 368
C 378 358 420 78 320 128
Z"
fill="#C4C4C4"
/>
</svg>
見やすくするためにインデントや改行を整形しています。
また、理解をしやすくするために各座標に小数点が入らないように最初に綺麗に作っておきました。
更に、これを単純なReact componentにしておきます。
(私が単によく使うのがReactなだけで、VueでもSvelteでもなんでも大丈夫です)
export const RandomSvg = (): JSX.Element => {
return (
<svg width="400" height="400" viewBox="0 0 400 400" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="
M 320 128
C 220 178 258 50 134 32
C 10 14 -20 238 100 258
C 220 278 158 378 268 368
C 378 358 420 78 320 128
Z"
fill="#C4C4C4"
/>
</svg>
);
};
ハンドルの座標を定数に置き換える
先ほどSVGのコードにはたくさんの数字が並んでいますが「アンカーポイントの座標」か「ハンドルの座標」のどちらかでしかありません。
後で説明しますが、まずはハンドルの座標から定数化していきます。

現在選択中のハンドルの座標は[220, 178]で、コードで言うと1つ目のC
の先頭から1つ目と2つ目の数値です。

同様にこちらのハンドルの座標は[258, 50]で1つ目のC
の先頭から3つ目と4つ目の数値です。
これらを踏まえて、まずはこのように置き換えました。
export const RandomSvg = (): JSX.Element => {
+ const firstPointX1: number = 220;
+ const firstPointY1: number = 178;
+ const firstPointX2: number = 258;
+ const firstPointY2: number = 50;
return (
<svg width="400" height="400" viewBox="0 0 400 400" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d={`
M 320 128
- C 220 178 258 50 134 32
+ C ${firstPointX1} ${firstPointY1} ${firstPointX2} ${firstPointY2} 134 32
C 10 14 -20 238 100 258
C 220 278 158 378 268 368
C 378 358 420 78 320 128
Z`}
fill="#C4C4C4"
/>
</svg>
);
};
この調子で、気合いで全ての数値を設定します。
export const RandomSvg = (): JSX.Element => {
const firstPointX1: number = 220;
const firstPointY1: number = 178;
const firstPointX2: number = 258;
const firstPointY2: number = 50;
+ const secondPointX1: number = 10;
+ const secondPointY1: number = 14;
+ const secondPointX2: number = -20;
+ const secondPointY2: number = 238;
+ const thirdPointX1: number = 220;
+ const thirdPointY1: number = 278;
+ const thirdPointX2: number = 158;
+ const thirdPointY2: number = 378;
+ const fourthPointX1: number = 378;
+ const fourthPointY1: number = 358;
+ const fourthPointX2: number = 420;
+ const fourthPointY2: number = 78;
return (
<svg width="400" height="400" viewBox="0 0 400 400" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d={`
M 320 128
C ${firstPointX1} ${firstPointY1} ${firstPointX2} ${firstPointY2} 134 32
- C 10 14 -20 238 100 258
- C 220 278 158 378 268 368
- C 378 358 420 78 320 128
+ C ${secondPointX1} ${secondPointY1} ${secondPointX2} ${secondPointY2} 100 258
+ C ${thirdPointX1} ${thirdPointY1} ${thirdPointX2} ${thirdPointY2} 268 368
+ C ${fourthPointX1} ${fourthPointY1} ${fourthPointX2} ${fourthPointY2} 320 128
Z`}
fill="#C4C4C4"
/>
</svg>
);
};
アンカーポイントの座標を計算する
次はアンカーポイントの座標ですが……こちらは既に出ている座標から求めた方が良いです。
具体的には、以下のアンカーポイントのX座標はfirstPointX2
とsecondPointX1
の平均をとり、Y座標も同様にfirstPointY2
とsecondPointY1
の平均をとった場所に位置するからです。

それぞれのハンドルの座標の平均以外の位置にアンカーポイントを配置してしまうと、次のような見た目になって出力されてしまう可能性もあります。
凹んでしまう | 角ができてしまう |
---|---|
![]() |
![]() |
これはこれで面白い形状かもしれませんが、今回の狙いからは外れてしまうのでNG。
結果的にコードは次のようになります。
export const RandomSvg = (): JSX.Element => {
const firstPointX1: number = 220;
const firstPointY1: number = 178;
const firstPointX2: number = 258;
const firstPointY2: number = 50;
const secondPointX1: number = 10;
const secondPointY1: number = 14;
const secondPointX2: number = -20;
const secondPointY2: number = 238;
const thirdPointX1: number = 220;
const thirdPointY1: number = 278;
const thirdPointX2: number = 158;
const thirdPointY2: number = 378;
const fourthPointX1: number = 378;
const fourthPointY1: number = 358;
const fourthPointX2: number = 420;
const fourthPointY2: number = 78;
+ const firstPointX: number = (firstPointX2 + secondPointX1) / 2;
+ const firstPointY: number = (firstPointY2 + secondPointY1) / 2;
+ const secondPointX: number = (secondPointX2 + thirdPointX1) / 2;
+ const secondPointY: number = (secondPointY2 + thirdPointY1) / 2;
+ const thirdPointX: number = (thirdPointX2 + fourthPointX1) / 2;
+ const thirdPointY: number = (thirdPointY2 + fourthPointY1) / 2;
+ const fourthPointX: number = (fourthPointX2 + firstPointX1) / 2;
+ const fourthPointY: number = (fourthPointY2 + firstPointY1) / 2;
return (
<svg width="400" height="400" viewBox="0 0 400 400" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d={`
- M 320 128
- C ${firstPointX1} ${firstPointY1} ${firstPointX2} ${firstPointY2} 134 32
- C ${secondPointX1} ${secondPointY1} ${secondPointX2} ${secondPointY2} 100 258
- C ${thirdPointX1} ${thirdPointY1} ${thirdPointX2} ${thirdPointY2} 268 368
- C ${fourthPointX1} ${fourthPointY1} ${fourthPointX2} ${fourthPointY2} 320 128
+ M ${fourthPointX} ${fourthPointY}
+ C ${firstPointX1} ${firstPointY1} ${firstPointX2} ${firstPointY2} ${firstPointX} ${firstPointY}
+ C ${secondPointX1} ${secondPointY1} ${secondPointX2} ${secondPointY2} ${secondPointX} ${secondPointY}
+ C ${thirdPointX1} ${thirdPointY1} ${thirdPointX2} ${thirdPointY2} ${thirdPointX} ${thirdPointY}
+ C ${fourthPointX1} ${fourthPointY1} ${fourthPointX2} ${fourthPointY2} ${fourthPointX} ${fourthPointY}
Z`}
fill="#C4C4C4"
/>
</svg>
);
};
ランダムに変わるようにする
現段階のSVGはこのようになっています。
当たり前ですが、リロードしてもうんともすんとも言いません。
それぞれのX1, Y1, X2, Y2がランダムに変わるようにコードを書きます。
export const RandomSvg = (): JSX.Element => {
+ const randomize = (min: number, max: number) => {
+ return Math.random() * (max - min) + min;
+ };
- const firstPointX1: number = 220;
- const firstPointY1: number = 178;
- const firstPointX2: number = 258;
- const firstPointY2: number = 50;
- const secondPointX1: number = 10;
- const secondPointY1: number = 14;
- const secondPointX2: number = -20;
- const secondPointY2: number = 238;
- const thirdPointX1: number = 220;
- const thirdPointY1: number = 278;
- const thirdPointX2: number = 158;
- const thirdPointY2: number = 378;
- const fourthPointX1: number = 378;
- const fourthPointY1: number = 358;
- const fourthPointX2: number = 420;
- const fourthPointY2: number = 78;
+ const firstPointX1: number = randomize(0.6, 1.2) * 220;
+ const firstPointY1: number = randomize(0.6, 1.2) * 178;
+ const firstPointX2: number = randomize(0.6, 1.2) * 258;
+ const firstPointY2: number = randomize(0.6, 1.2) * 50;
+ const secondPointX1: number = randomize(0.6, 1.2) * 10;
+ const secondPointY1: number = randomize(0.6, 1.2) * 14;
+ const secondPointX2: number = randomize(0.6, 1.2) * -20;
+ const secondPointY2: number = randomize(0.6, 1.2) * 238;
+ const thirdPointX1: number = randomize(0.6, 1.2) * 220;
+ const thirdPointY1: number = randomize(0.6, 1.2) * 278;
+ const thirdPointX2: number = randomize(0.6, 1.2) * 158;
+ const thirdPointY2: number = randomize(0.6, 1.2) * 378;
+ const fourthPointX1: number = randomize(0.6, 1.2) * 378;
+ const fourthPointY1: number = randomize(0.6, 1.2) * 358;
+ const fourthPointX2: number = randomize(0.6, 1.2) * 420;
+ const fourthPointY2: number = randomize(0.6, 1.2) * 78;
const firstPointX: number = (firstPointX2 + secondPointX1) / 2;
const firstPointY: number = (firstPointY2 + secondPointY1) / 2;
const secondPointX: number = (secondPointX2 + thirdPointX1) / 2;
const secondPointY: number = (secondPointY2 + thirdPointY1) / 2;
const thirdPointX: number = (thirdPointX2 + fourthPointX1) / 2;
const thirdPointY: number = (thirdPointY2 + fourthPointY1) / 2;
const fourthPointX: number = (fourthPointX2 + firstPointX1) / 2;
const fourthPointY: number = (fourthPointY2 + firstPointY1) / 2;
return (
<svg width="400" height="400" viewBox="0 0 400 400" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d={`
M ${fourthPointX} ${fourthPointY}
C ${firstPointX1} ${firstPointY1} ${firstPointX2} ${firstPointY2} ${firstPointX} ${firstPointY}
C ${secondPointX1} ${secondPointY1} ${secondPointX2} ${secondPointY2} ${secondPointX} ${secondPointY}
C ${thirdPointX1} ${thirdPointY1} ${thirdPointX2} ${thirdPointY2} ${thirdPointX} ${thirdPointY}
C ${fourthPointX1} ${fourthPointY1} ${fourthPointX2} ${fourthPointY2} ${fourthPointX} ${fourthPointY}
Z`}
fill="#C4C4C4"
/>
</svg>
);
};
これで完成です。
冒頭に貼ったgifのように、リロードする度に形状が変わるSVGを生成することができました。
あとは上手いこと背景に敷き詰めたり色を変えたりすれば、良い感じにスタイルを組めるはずです。
なんかもう少し良い方法があるような気がしますし、ランダム度合いやアンカーポイントの数の違いをpropsで制御できるくらいのコンポーネントにできれば尚使い勝手が良いように思いますが、今回はここまで。
最後まで読んでいただいてありがとうございました。