SVG、扇子アニメーション2〜
好きな画像を貼り付ける。

〽️サイトをおめでたくしよう。 〽️構造を整理する。 〽️画像を貼り付けてクリップ。 〽️複製してみる。 〽️for文と配列で自動化。 〽️全体をアニメーション。 〽️好きな画像に差し替え。





サイトをおめでたくしよう。


こんにちは、「ふ」です。
前回の記事では、SVGで「扇子」の構造を作ってアニメーションさせました。今回はそこに、自由に画像を貼り付ける方法について紹介していきます。

コードは少し複雑なものとなりますが、1度作ってしまえば自由に画像を差し替えられることができます。
実装すれば、サイトが一気におめでたいものとなるので(笑)、ぜひ試してみてください。

SVGで扇子のアニメーション。

2つのキーフレームで実現。

当記事は前回実装した素材に手を加えていく形で、話を進めていきます。
「扇子アニメーションの基本形」については⬆︎を参考にしてください。

構造を整理する。


前回作成した骨と扇面、要の1セットをベースに作成していきます。
そのまま書き出したSVGコードがこちら⬇︎。

<svg viewBox="0 0 841.9 508.8"> <style type="text/css"> .st4{fill:#FFEDA2;stroke:#231815;stroke-width:1.7398;stroke-miterlimit:10;} .st5{「「4fill:none;」」stroke:#231815;stroke-width:1.4702;stroke-miterlimit:10;} .st6{fill:#231815;} </style> 「「3<!-- 骨 -->」」 <path class="st4" d="M423,485.4h-4.1c-6.2,0-11.2-5-11.2-11.2V26.8c0-6.2,5-11.2,11.2-11.2h4.1c6.2,0,11.2,5,11.2,11.2v447.4 C434.2,480.4,429.2,485.4,423,485.4z"/> 「「3<!-- 扇面左 -->」」 <polygon class="st5" points="420.9,15.6 531,30.1 457.9,302.8 420.9,298 "/> 「「3<!-- 扇面右 -->」」 <polygon class="st5" points="310.9,30.1 420.9,15.6 420.9,298 384,302.8 "/> 「「3<!-- 要 -->」」 <circle class="st6" cx="420.9" cy="440.9" r="7.3"/> </svg>

扇面には画像を貼るので、fill(塗り)は「none」としています。

ここがちょっと大変。
要素をこの⬆︎ような階層構造にします。青で示されているのが実際のオブジェクトで、骨のオブジェクトにはid「hone」を付けています。
紫の部分はグループに付けたidまたはclass名です。

<g id = 「「4"back"」」> bb<path id = 「「1"hone"」」 class="st4" d=..../> </g> <g id = 「「4"men"」」> bb<g id = 「「4"master"」」> bbbb<g class = 「「4"oritatami"」」> bbbb<polygon class="st5" points=..../> bbbb</g> bbbb<g class = 「「4"oritatami"」」> bbbb<polygon class="st5" points=..../> bbbb</g> bb</g> </g> <circle class="st6" cx="420.9" cy="440.9" r="7.3"/>

実装するアニメーション自体は前回と同じくシンプルなものなのですが、今回はtransformプロパティを要素に何重にも掛けていきます
指定値同士の汚染を避けるため、特に扇面の部分は深い階層を作って内包していく必要があります。

画像を貼り付けてクリップ。


■ omedeto_.svg サンプルとして使う画像、「omedeto_svg」です。
2つの「.oritatami」クラスの中に、1枚ずつ貼り付けましょう。

<g class = "oritatami"> 「「1<image href = "omedeto_.svg" width = "100%"/>」」 <polygon class="st5" points=..../> </g> <g class = "oritatami"> 「「1<image href = "omedeto_.svg" width = "100%"/>」」 <polygon class="st5" points=..../> </g>


現在の状態です。
画像がそのまま貼り付けられているので、扇面の形で切り抜きましょう。

左右の扇面のデータを複製して、クリップパスを生成します。
「#master」グループの直下に配置してください。<image>タグ内で参照し、クリップします。

<g id = "master"> <g class = "oritatami"> <image href = "omedeto_.svg" width = "100%" 「「1style = "clip-path:url(#r);"」」/> <polygon class="st5" points=..../>「「4// ◀︎ copy」」 </g> <g class = "oritatami"> <image href = "omedeto_.svg" width = "100%" 「「1style = "clip-path:url(#l);」」"/> <polygon class="st5" points=..../>「「4// ◀︎ copy」」 </g> <clipPath 「「1id = "r"」」> <polygon points=..../>「「4// ◀︎ paste」」 </clipPath> <clipPath 「「1id = "l"」」> <polygon points=..../>「「4// ◀︎ paste」」 </clipPath> </g>


扇面の形に切り取ることができました。

SVG領域に画像を配置する際の、縦横比のとり方について。
⬇︎の記事にて詳しく紹介しています。


SVGに画像をぴったりと配置〜
preserveAspectRatio。

縦横比が違っても安心。

originを一括指定。

ここでtransform-originを指定しておきましょう。

「「3<!-- 要のオブジェクト -->」」 <circle class="st6" cx="「「5420.9」」" cy="「「5440.9」」" r="7.3"/>

transformさせる要素の基準点はどのみち「要」であるオブジェクトの中心座標です。

svg * { transform-origin: 「「5420.9px 440.9px」」; }

各要素を逐一class名などでセレクトするのもめんどーなのでこう⬆︎しました。
「svg *」とすると、svg要素内の全ての子要素に適用されます。

複製してみる。


JavaScriptを使って複製・角度を付けていきます。

JavaScriptで行う作業の大きな流れとしては、

・#hone(骨オブジェクト)を複製して親である#backに追加
・#masterグループ(扇子面/画像/クリップパス)を複製して親の#menに追加

複製したcloneはそれぞれ角度を調整した上で、親要素に格納します。
はじめに、複製する要素とその親要素を取得しましょう。

■ JavaScript

const men = document.getElementById("men"); const master = document.getElementById("master"); const back = document.getElementById("back"); const hone = document.getElementById("hone");

それでは試しに、扇子面を複製、30°回転させて親要素に追加してみます。

「「3//masterグループを複製」」「「1 ..①」」 let clone = master.cloneNode(true); 「「3//子要素、孫要素を取得」」「「1 ..②」」 let child = clone.children; let mago0 = child[0].children; let mago1 = child[1].children; 「「3clipPathのidと参照を差し替え」」「「1 ..③」」 child[2].id = "r_1"; child[3].id = "l_1"; mago0[0].style.clipPath = "url(#r_1)"; mago1[0].style.clipPath = "url(#l_2)";



① masterグループを複製します。引数の「true」は子要素も含めて複製する、という指定です。

② 子要素と、

  さらに孫要素を取得します。
取得したものは「HTMLCollection」という型になり、配列のように添字[n]で呼び出すことができます。

③ クリップパスの参照先が重複しないように、id名を変更しています。
伴って、imageの参照先も変更しました。

「「3clipPathを回転」」「「1 ..④」」 child[2].style.transform = "rotate(30deg)"; child[3].style.transform = "rotate(30deg)"; 「「3imageを逆回転」」「「1 ..⑤」」 mago0[0].style.transform = "rotate(-30deg)" ; mago1[0].style.transform = "rotate(-30deg)" ; 「「3扇面全体を回転」」「「1 ..⑥」」 clone.style.transform = "rotate(30deg)"; 「「3回転したものを親要素に追加」」「「1 ..⑦」」 men.appendChild(clone);

④ 画像の切り取り位置をずらすため、<clipPath>を回転させます。

⑤ ④の処理を行なった時点では「切り取り位置」のみが回転している状態です。
このままでは、⑥で扇面全体を回転させるときに「ズレた状態」のまま回転してしまうので、<image>要素を逆回転させて元に戻しておきましょう。



⑥ つじつまが合ったところで、扇子面全体を回転。

⑦ 扇子面を親である#menに追加します。

ここまで処理をした状態⬆︎です。
「骨」オブジェクトも複製・回転させましょう。

「「3//骨を複製→回転→親要素に追加」」「「1 ..⑧」」 let honeclone = hone.cloneNode(true); honeclone.style.transform = "rotate(30deg)"; back.appendChild(honeclone);

骨グループは単純な構成になっているので、clone作業も簡単に済みます。


骨オブジェクトが追加されました。

for文と配列で自動化。


1つcloneを作るだけで結構な手間が掛かりました。
あと3つ、cloneを追加しなければいけません。ここは繰り返し処理を使いましょう。

let kakudo = ["30deg","60deg","-30deg","-60deg"];

cloneに与える回転角度については、scriptのグローバル部分に配列を置きました。
このようにしておけば、

・正回転  rotate(kakudo[i]) ・逆回転  rotate(kakudo[(i+2)%4])

と指定することで、<image>要素の逆回転にも対応できます。
前のセクションで作った複製処理を、for文に落とし込みましょう。

const men = document.getElementById("men"); const master = document.getElementById("master"); const back = document.getElementById("back"); const hone = document.getElementById("hone"); 「「3//回転角度用の配列」」「「1 ..⑪」」 let kakudo = ["30deg","60deg","-30deg","-60deg"]; 「「3//for文に落とし込む」」「「1 ..⑫」」 for(i=0;i<kakudo.length;i++) { 「「3//扇面の処理」」 let clone = master.cloneNode(true); let child = clone.children; let mago0 = child[0].children; let mago1 = child[1].children; child[2].id = "r_" + String(i); child[3].id = "l_" + String(i); mago0[0].style.clipPath = "url(#" +child[2].id + ")"; mago1[0].style.clipPath = "url(#" +child[3].id + ")"; child[2].style.transform = "rotate(" + kakudo[i] + ")"; child[3].style.transform = "rotate(" + kakudo[i] + ")"; mago0[0].style.transform = "rotate(" + kakudo[(i+2)%4] + ")" ; mago1[0].style.transform = "rotate(" + kakudo[(i+2)%4] + ")" ; clone.style.transform = "rotate(" + kakudo[i] + ")"; men.appendChild(clone); 「「3//骨の処理」」 let honeclone = hone.cloneNode(true); honeclone.style.transform = "rotate(" + kakudo[i] + ")"; back.appendChild(honeclone); }

文字列の連結はめんどくさい💧ですが、がんばりましょう。

複製の自動化に成功しました。
が、何か違和感が.........................

重ね順がおかしい。

appendChildで要素を追加すると、親要素内の1番後ろにどんどん追加されていきます。
骨の部分を見ると分かりますが、片側から順に重なっていません。

順番を並び替える。

上に重なってしまっている要素を、親要素内の先頭に移し替えましょう。そうすると表示上は再背面となります。
「irekae」関数を定義します。

「「3//irekae関数を定義」」「「1 ..⑬」」 function irekae(parent) { parent.insertBefore(parent.children[3],parent.children[0]); parent.insertBefore(parent.children[4],parent.children[0]); } 「「3//扇面と骨に適用」」「「1 ..⑭」」 irekae(men); irekae(back);

関数の実行はすべての複製が終わったあとに行うため、scriptの1番後ろに記述してください。
扇面についても今の静止状態では分かりませんが、重ね順がちぐはぐです。このままアニメーションさせるとうまくいきません。
irekae関数に1度通しておきましょう。

オブジェクトが順番通りに重なりました。



全体をアニメーション。


前後の親骨をここで追加します。
#honeオブジェクトのデータを2つ複製。1つは再背面に、もう1つは最前面の「要」のすぐ後ろに配置します。

<path class="st4 「「5oya」」" 「「1style = "transform:rotate(-75deg);"」」 d=..../> <g id = "back"> bb<path id = "hone" class="st4" d=..../>「「4// ◀︎ copy」」 bb 〜 略 〜 </g> <g id = "men">  〜 略 〜 </g> <path class="st4 「「5oya」」" 「「1style = "transform:rotate(75deg);"」」 d=..../> <circle class="st6" cx="420.9" cy="440.9" r="7.3"/>

#honeを複製したものは、id名の指定はキャンセルしてclass名「oya」を追加しています。
また親骨の回転は、タグ内で指定しました。

これで全ての部品が出揃いました。
前回のようにCSSを使って、「扇子全体の開閉」「扇子面1セットの折りたたみ」の2つのアニメーションを指定します。

「「3/* 開閉アニメーション */」」 #hone,#men #master,.oya{ animation:zentai 4s ease-out alternate infinite; } @keyframes zentai { from { transform:rotate(0deg); } } 「「3/* 折りたたみアニメーション */」」 .oritatami { animation:oritatami 4s ease-out alternate infinite; } @keyframes oritatami { from { transform:scaleX(0); } }

これでついに完成です。お疲れ様でした!
最後のセクションで、画像の差し替え方法を紹介します。

好きな画像に差し替え。


■kagami.svg それではこちら⬆︎の画像に差し替えてみましょう。
と言っても、#oritatamiグループの中にある<image>要素の参照先を変更するだけです。

<g class = "oritatami"> <image href = "「「1kagami.svg」」" width = "100%" style = "clip-path:url(#r)"/> <polygon class="st5" points= ..../> </g> <g class = "oritatami"> <image href = "「「1kagami.svg」」" width = "100%" style = "clip-path:url(#l)"/> <polygon class="st5" points= ..../> </g>

はい、できました ♬

<image bbhref = "画像のurl" bbx = "x座標" bby = "y座標" bbwidth = "幅" />

貼り付ける画像の微調整は、<image>タグ内で指定することができます。前半に紹介したpreserveAspectRatio属性の記事も参考にしてください。
また、元の扇面オブジェクトの塗りは「none」になっているので、状況に合わせて着色したり、画像との重ね順を入れ替えたりしてください。

最後までお読みくださり、ありがとうございました。
今回は過去に紹介してきた色んな実装を施し、「集大成」的なチュートリアルになったかもしれません。

長くて複雑なチュートリアルとなってしまいました(←すいません)が、特別な動画編集ソフトも使わず、無料のコードエディタとブラウザだけでここまでのことができます。今後もSVGを含めたコーディングを学習して、楽しいwebサイトを作っていきましょう!

ではまた〜 ♪



SVGアニメーション、 回転の中心を指定する。

2021.03.09
座標を取得して%変換。












「ふ」です。

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