花火のアニメーションをSVGで作ろう。

〽️画像作成。 〽️動かすのはArray。 〽️ひと花火打ち上げる。 〽️ランダム発生。 〽️シーズン問わず使おう。





⬆︎SVGついてのまとめページ、CSSアニメーションの基本コーナーはこちら。

こんにちは、「ふ」です。
今回はSVGを使って、シンプルな花火のアニメーションを作っていきます。

花火1つぶんのイメージ⬆︎です。
何もないところから花火が放射状に広がっていき、最後に消えていく。ランダムな「位置/大きさ/色」のものを複製し、時間差で発生させます。

この動き、stroke-dash(破線)を動かすことで実装できそうです。

そして今回のポイントは、「真ん中に空洞を作りながら広がっていく」というところ。
単なる放射状の広がりを作るのであればstroke-dashoffsetを変化させれば良いのですが、隙間を作りながら線を伸ばしていくにはどうすればいいのでしょうか?
後ほど紹介しますが、ちょっとした変則ワザを使うことになりました。

定番の「stroke-dash(破線)を使った描画アニメーション」はこちら⬇︎の記事でくわしく紹介しています。

SVG、線を描くアニメーション。

2020.09.23
〽️ 破線のプロパティをつかいます。

画像作成。


元画像の段階では、フツーに放射状に配置された線分を用意します。

イラレやVectornatorで正方形のアートボードを作成、中央で分断するように直線を引きます。

真ん中をはさみツールでカット。
これで2つの直線オブジェクトと成増。

切った後の2つの線分を両方選択し、少し回転させたものを複製。「ふ」は15°にしました。

同じことを繰り返して、1周します。

これで形状は完成。線幅はお好みで。
SVGで書き出す前に、もう1つやることがあります。

パスの方向を揃える。

stroke-dashoffsetを動かすアニメーションは全ての線分に対して共通のキーフレームを使いたい。そのためにはオブジェクトのパス方向をすべて外側に向けて置かないと、うまく花火が開きません。
確認してみましょう。

全てのオブジェクトを選択し、一方のパスの先端をマーキングしてみます。終点に矢印を付けてみました。

見ると、概ね右半分にあるパスは外側を向いているのでOKなのですが、左半分のパスは内側を向いてしまっています。
内側を向いているオブジェクトを選択し、パスの向きを逆にしておきましょう。

アートボードは正方形に。

なおアートボードを正方形にする理由。
幅と高さの同じviewBoxにおいて、stroke-dasharrayやstroke-dashoffsetの値を「%」で指定した場合、viewBox(=アートボード)の高さを100%として算出されます

「線分2つ分 = アートボードの高さ」としておけば、こまかな座標指定をせずとも、線分1つ分の長さを「50%」として扱うことができます。

ただし、viewBoxの縦横比が同じではない(正方形ではない)場合はその限りではありませんでした。詳しい原因は調査中ですが、いまのところ正方形のアートボードから画像を作成しておくのが無難です。

動かすのはArray。





扨(さて)ここから、「空洞を作りながら広がっていく」ための工夫をします。
長さが50%の線分に対して、

長さ50%の線分があったとします。画像の部分が線分の範囲(実際に描画・表示される範囲)です。

ここで、

stroke-dasharray:10% 40%;

と指定すると、この⬆︎ように。
破線本体が10%、隙間部分が40%の配分となりました。

今回のSVGアニメーションは、stroke-dashoffsetではなくstroke-dasharrayを変化させます
線分要素のidを「line1」とします。現在の状態から、stroke-dasharrayを50%にしてみましょう。

#line1 { stroke-dasharray:10% 40%; animation:line1 2.5s linear infinite; } @keyframes line1 { to { stroke-dasharray:50%; } }

結果はこの⬆︎ように。
ちょっと考えてみてください。変化前「破線 : 隙間」が「1:4」だったのを「5:5」に変化させています。しかし両者が均等に広がっていくのではなく、破線部分だけが一方的に押し出されているような印象です。

これからわかること。
stroke-dasharrayを大きくしたとき。破線と隙間が平均的に広がるのではなく、破線のスタート位置から押し出されるのです。

ではもしstroke-dashoffset(線分に対する破線のスタート位置)をもっと後ろにするとどうなるのでしょう?

#line1 { stroke-dasharray:10% 40%; animation:line1 2s linear infinite; 「「1stroke-dashoffset:50%;」」 }

破線のスタート位置から押し出されるので、実際表示されている線分エリアにおいては、「隙間を作りつつ破線も広がっていく」ように見せることができます。
作成した画像の線分すべてにこのCSSを指定すれば、「空洞を作りつつ広がっていく」花火が実装できますね!

ひと花火打ち上げる。


では1つあたりの花火アニメーションを作ってみます。
作成した画像をSVGで書き出し、HTMLファイルに流し込みます。

<body> <svg viewBox="0 0 595.3 595.3"> 「「1<g id = "hitohanabi">」」 <line class="st0" x1= .../> <line class="st0" x1= .../> <line class="st0" x1= .../>  〜 略 〜 「「1</g>」」 </svg> </body>

CSSで規定値とアニメーションを指定します。
画像作成時に花火とアートボードの高さを揃えておいたので、値は%指定で楽チンです。

#hitohanabi { stroke-dasharray:10% 40%; stroke-dashoffset:50%; animation:hitohanabi 2.5s ease-out infinite; } @keyframes hitohanabi { to { stroke-dasharray:50%; } }

各線分に対し一括でアニメーションを指定したいので、全ての<line>要素をグループ化しておきます。id名は「hitohanabi」としました。

いい感じになっています。
ここからもう少し、花火っぽくするためにさりげないキーフレームを追加しましょう。

#hitohanabi { stroke-dasharray:10% 40%; stroke-dashoffset:50%; animation:hitohanabi 2.5s linear infinite; } @keyframes hitohanabi { 60% { 「「1transform:translateY(0%);」」 } 90% { 「「1opacity:1;」」 } to { stroke-dasharray:50%; 「「1transform:translateY(20%); opacity:0;」」 } }

60%のところから花火全体が少し加工するようにしています。実物の花火動画を見ているとそうだったので。
また、90%から最後にかけて不透明度を落とし、火花が消えていく様子を表現してみました。

意識しないとわかりませんが、かすかに不透明度と位置が変化しています。
次はこのSVG要素をランダムに発生させましょう。

ランダム発生。


HTML上に新たなSVG領域を確保し、作成したSVGアニメーションをランダムに発生させます。

■ HTML

<svg 「「1id = "outer" 」」viewBox="0 0 400 300"> 「「3/* overflowと位置を修正 */「「2 ..①」」」」 <svg 「「5id = "inner" 」」viewBox="0 0 595.3 595.3" style = "overflow:visible" x = "-100%"> <g id = "hitohanabi"> <line class="st0" x1= .../> <line class="st0" x1= .../> <line class="st0" x1= .../>  〜 略 〜 </g> </svg> </svg>

svg領域を新たに作成し、その中に花火要素を入れてしまいます。それぞれにid名「outer」「inner」を指定しました。

また①の部分。
先ほど#hitohanabiのアニメーションを作る際、アニメーションの後半で全体が少し下降するようにしました。そのまま表示させると、viewBoxからはみ出した部分が非表示になってしまいます。
そこでoverflowを「visible(可視化)」に指定。

また、現在入れ子になっている#innerは、これから複製するマスターとなる要素です。そのままにしておくとアニメーションさせたとき、常に最初このマスターから始まってしまうことに成増。これでは「ランダム」とは言えません。
ちょっぴり力技ではありますが、位置を100%ずらして#outerのviewBoxの外側に追いやっておきました。

outerの中にinnerをランダムに発生させる流れとしては、

innerを複製 ▼
色や大きさをランダムに指定 ▼
outerの子要素に追加 ▼
animation終了を検知し、要素を削除

のように成増。
追加しっぱなしでは要素が無限増殖してしまうので、アニメーション終了後に削除する処理が必要です。

■ CSS

#hitohanabi { stroke-dasharray:10% 40%; stroke-dashoffset:50%; 「「3/*infiniteを削除*/」」 「「4animation:hitohanabi 2.5s linear;」」 }

無限増殖といえば、先につくっておいた#hitohanabiのアニメーションのiteration-count(繰り返し回数)が「infinite」になっていました。これについてもアニメーション終了イベントが発生しなくなってしまうので、無限増殖が起こります。
指定を外しておきましょう。自動的に初期値の「1」に差し変わります。

■JavaScript

「「3//内外のSVG要素を取得」」「「1 ..①」」 const outer = document.getElementById("outer"); const inner = document.getElementById("inner"); 「「3//6色用意した」」「「1 ..②」」 const iro = ["red","blue","yellow","green","orange","purple"]; 「「3//一連の処理を1つの関数に」」「「1 ..③」」 function random() { 「「3//innerを複製」」「「1 ..④」」 let clone = inner.cloneNode(true); 「「3//アニメーション終了時に削除」」「「1 ..⑤」」 clone.children[0].addEventListener("animationend",function() { clone.remove(); }); 「「3//各属性値をランダムに生成」」「「1 ..⑥」」 let horizon = String(Math.random()*100 - 25) + "%"; let vertical = String(Math.random()*100 - 50) + "%"; let randomcolor = iro[((Math.random()*6)|0)]; let ookisa = String(Math.random()*20 +30) + "%"; 「「3//属性をセット」」「「1 ..⑦」」 clone.setAttribute("x",horizon); clone.setAttribute("y",vertical); clone.setAttribute("width",ookisa); clone.setAttribute("height",ookisa); clone.children[0].style.stroke = randomcolor; 「「3//outerに追加」」「「1 ..⑧」」 outer.appendChild(clone); } 「「3//0.5sごとに実行」」「「1 ..⑨」」 setInterval (random,500);

① 内部と外部のSVGを取得します。

② 花火の色を6色準備して、配列に格納しています。のちほど添字をランダムに生成して呼び出します。

③ innerの複製→属性セット→outerに追加までの処理を関数「random」にまとめました。

④ cloneNodeでinnerを複製します。

⑤ アニメーションしている#hitohanabiはinnerの直属の子要素です。children[0]で呼び出し、アニメーション終了時に自身の親要素ごと削除するイベントハンドラを実装しました。

⑥ 複製したinnerのX/Y座標、大きさ、#hitohanabiの色を乱数で作成し、文字列化しています。
座標や大きさについては結果をプレビューしながらMath.random( )の値をいろいろと丸めました。
色に関してはグローバルで用意した配列「iro」の添字をランダムに参照させています。

⑦ innerのクローンにランダム生成した属性をセット。

⑧ outerにinnerのクローンを追加します。

⑨ 一連の処理をまとめた関数randomを0.5sごとに実行。

結果。

〽️ 玉屋〜
〽️ 鍵屋〜

これで完成です。お疲れ様でした!
うまくできましたか?しばらく眺めていたいですね♩

<svg id = "outer" viewBox = "0 0 400 300" 「「1style = "background-color:black;"」」>

見やすいように外側のSVGの背景色を黒く⬆︎しています。

シーズン問わず使おう。


最後までお読みくださり、ありがとうございました。
今回はSVGを使った「空洞を作りながら放射状に線が伸びていく」アニメーションを作ってみました。この動き、花火以外にも使えそうですね。

また夜に合った色、昼間に合った色を配列にセットすれば、もっと綺麗なアニメーションが作れると思います。stroke-dasharrayの値も色々試してみたいところですね!
運動会シーズンや花火シーズンにぜひお使いください!ではまた〜 ♪



雨のアニメーションをSVGで作ろう。

2021.05.30
webページ上に雨を降らせます。

SVGでボタンアニメーション【Web Animations API】。

2020.12.19
event.targetからオブジェクトを動かす。











「ふ」です。

swift、web、ガジェットなど。役立つ情報や観ていてたのしいページを書いていきたいと思います。