SVG内のシェイプに、JavaScriptで順番に色をつけていく
まずは次の動画をご覧ください。
世界地図のSVGを用意したのですが、そこに、JSONの値を基準にJavaScriptで次々に色を塗っていく、というサンプルです。
※このサンプルでは、例として野生のカピバラの生息地に色をつけてみました。
↑こちら、いかがでしょうか。
扱う地図の種類を変えたり、
工夫次第では、いろいろなビジュアル表現に役立つ手法だと思います。
たとえば、「もっと地域をクローズアップした地図のSVG」を用意して、その地域の色を塗ってあげるなど。
旅行サイトの案件や、観光地を紹介する案件などでも利用ができそうですね。
SVGに順番に色をつけるしくみ
↑冒頭の動画ですが、
こちらがどういう仕組みで動いているかというと、
・色をつけたい場所を定義した、JSONファイルを用意する
・そのJSONをJavaScriptで読み込み、JSONの値の数だけ順番にclass名をつけていく
と言った仕組みになります。
(※ちなみに、JSONを使わない場合は、JavaScript内に同じ意味の配列を持たせてあげる形でも動きます。)
ただ、いきなり「複雑な形の地図SVG + そこに着色」だとわかりづらいと思いますので、
次のセクションからは、
「15個の単純な正方形が並んでいるSVG」に、「順番に色をつけていくサンプル」を例に、
この仕組みについて書いていきたいと思います。
単純な正方形が並ぶSVGで見る、順番に色をつけるしくみ
こちらの動画をご覧ください。
↑こちらは「15個の単純な正方形が並んでいるSVG」に、
「順番に色をつけていくサンプル」になります。
次のセクションから、
・JSON
・JavaScript
・html
・svg
・css
の順番に、
どういったコードが必要か書いていきたいと思います。
色をつけたい場所を定義した、JSONファイル
まずは、JSONを外部ファイルとして用意します。
このサンプルではとにかく「色をつけたい場所が定義」されていれば良いので、
たとえば次の様なJSONがあれば最低限大丈夫です。
(ちなみに便宜上、”id”: という項目も値として持たせてますが、
このサンプルでは使ってないのでなくても良いです。”shape”: の値だけがあれば最低限動きます)
※コードが長いので「…中略…」としていますが、
実際は6~14番目が、同じフォーマットで
「…中略…」の部分に入り、計15個の要素を持つJSONになります。
(※ちなみに、もしJSONを使わない場合は、JavaScript内に同じ意味合いの配列を持たせてあげる形でも動きます。)
{ "svgsample": [ { "id": 1, "shape": "shape1" }, { "id": 2, "shape": "shape2" }, { "id": 3, "shape": "shape3" }, { "id": 4, "shape": "shape4" }, { "id": 5, "shape": "shape5" }, ...(中略。実際はここに6~14番目が、同じフォーマットで入ります)... { "id": 15, "shape": "shape15" } ] }
↑こちらのJSONを外部ファイルとして
/assets/json/ というディレクトリに data-fifteen.json という名前で保存、
それを次のセクションで書いていくJavaScriptで読み込んで使用します。
(※ディレクトリ名やファイル名は、分かりやすいものであれば何でも良いです)
SVGに順番に色をつけるJavaScript
JavaScript側は、たとえば次の様に書きます。
最初の関数 liveColoring() は、JSONを読み込む関数です。
後半にある関数 liveColoringOrder() は、読み込んだJSONの値を元にして
色をつけたいSVGのシェイプを取得。順番に is-active というclassを付与してあげて、
次々に色をつけていく関数になっています。
(※ちなみに、もしJSONを使わない場合は、JavaScript内に同じ意味合いの配列を持たせてあげる形でも動きます。その場合は少し書き換えが必要ですが、
それはまた 次の記事 で取り扱いたいと思います。)
// JSONを読み込む記述 async function liveColoring(){ const jsonPath = "/assets/json/data-fifteen.json"; const jsonRequest = new Request(jsonPath); const resultRequest = await fetch(jsonRequest); const resultJson = await resultRequest.json(); liveColoringOrder(resultJson.svgsample); } // ループするたびにディレイをかける変数。初期値は必須 let delayNumber = 0; // JSON内の配列に対してループを記述する const liveColoringOrder = (elements)=> { elements.forEach((j) => { const attachmentShape = document.querySelector(`.${j.shape}`); setTimeout(() => { attachmentShape.classList.add("is-active"); }, delayNumber); delayNumber += 100; }); } // ページが読み込まれたらJSON読み込みの関数を実行 window.addEventListener("load", () => { liveColoring(); });
htmlの用意
htmlとしては、次の構成があれば最低限良いです。
重要なのは、「ここにSVGをコードとして埋め込みます」と記載のコメントアウト部分に、
この後のセクションでご紹介するSVGをコードとして配置する、という点だけです。
<section> <div class="shape-container"> <div class="shape-svg-parent"> <!--ここにSVGをコードとして埋め込みます--> </div> </div> </section>
SVGのコード
さて、SVGはこんな感じのものを用意します。
あらかじめ書いておきますが、「インラインSVGはコードが長い」です。
それはさておき、これを上のセクションの
「ここにSVGをコードとして埋め込みます」と記載のコメントアウト部分に貼り、
ブラウザで開くと白い正方形が15個並んだ状態が描画されます。
なお実際、こう言った用途のSVGを用意する時は、
Illustratorで希望する図形を配置したドキュメントを作り、それをSVG形式で書き出します。
そしてそのファイルをVSCodeで開き、コードとして取得する形が比較的スムーズで良いかと思います。
<svg id="layer" xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="0 0 1366 768"> <g> <rect class="shape1 default-fill" x="483.5" y="184.5" width="79" height="79" rx="9.5" ry="9.5"/> <path class="line1 cls-1" d="M553,185c4.96,0,9,4.04,9,9v60c0,4.96-4.04,9-9,9h-60c-4.96,0-9-4.04-9-9v-60c0-4.96,4.04-9,9-9h60M553,184h-60c-5.5,0-10,4.5-10,10v60c0,5.5,4.5,10,10,10h60c5.5,0,10-4.5,10-10v-60c0-5.5-4.5-10-10-10h0Z"/> </g> <g> <rect class="shape2 default-fill" x="563.5" y="184.5" width="79" height="79" rx="9.5" ry="9.5"/> <path class="line2 cls-1" d="M633,185c4.96,0,9,4.04,9,9v60c0,4.96-4.04,9-9,9h-60c-4.96,0-9-4.04-9-9v-60c0-4.96,4.04-9,9-9h60M633,184h-60c-5.5,0-10,4.5-10,10v60c0,5.5,4.5,10,10,10h60c5.5,0,10-4.5,10-10v-60c0-5.5-4.5-10-10-10h0Z"/> </g> <g> <rect class="shape3 default-fill" x="643.5" y="184.5" width="79" height="79" rx="9.5" ry="9.5"/> <path class="line3 cls-1" d="M713,185c4.96,0,9,4.04,9,9v60c0,4.96-4.04,9-9,9h-60c-4.96,0-9-4.04-9-9v-60c0-4.96,4.04-9,9-9h60M713,184h-60c-5.5,0-10,4.5-10,10v60c0,5.5,4.5,10,10,10h60c5.5,0,10-4.5,10-10v-60c0-5.5-4.5-10-10-10h0Z"/> </g> <g> <rect class="shape4 default-fill" x="723.5" y="184.5" width="79" height="79" rx="9.5" ry="9.5"/> <path class="line4 cls-1" d="M793,185c4.96,0,9,4.04,9,9v60c0,4.96-4.04,9-9,9h-60c-4.96,0-9-4.04-9-9v-60c0-4.96,4.04-9,9-9h60M793,184h-60c-5.5,0-10,4.5-10,10v60c0,5.5,4.5,10,10,10h60c5.5,0,10-4.5,10-10v-60c0-5.5-4.5-10-10-10h0Z"/> </g> <g> <rect class="shape5 default-fill" x="803.5" y="184.5" width="79" height="79" rx="9.5" ry="9.5"/> <path class="line5 cls-1" d="M873,185c4.96,0,9,4.04,9,9v60c0,4.96-4.04,9-9,9h-60c-4.96,0-9-4.04-9-9v-60c0-4.96,4.04-9,9-9h60M873,184h-60c-5.5,0-10,4.5-10,10v60c0,5.5,4.5,10,10,10h60c5.5,0,10-4.5,10-10v-60c0-5.5-4.5-10-10-10h0Z"/> </g> <g> <rect class="shape6 default-fill" x="483.5" y="264.5" width="79" height="79" rx="9.5" ry="9.5"/> <path class="line6 cls-1" d="M553,265c4.96,0,9,4.04,9,9v60c0,4.96-4.04,9-9,9h-60c-4.96,0-9-4.04-9-9v-60c0-4.96,4.04-9,9-9h60M553,264h-60c-5.5,0-10,4.5-10,10v60c0,5.5,4.5,10,10,10h60c5.5,0,10-4.5,10-10v-60c0-5.5-4.5-10-10-10h0Z"/> </g> <g> <rect class="shape7 default-fill" x="563.5" y="264.5" width="79" height="79" rx="9.5" ry="9.5"/> <path class="line7 cls-1" d="M633,265c4.96,0,9,4.04,9,9v60c0,4.96-4.04,9-9,9h-60c-4.96,0-9-4.04-9-9v-60c0-4.96,4.04-9,9-9h60M633,264h-60c-5.5,0-10,4.5-10,10v60c0,5.5,4.5,10,10,10h60c5.5,0,10-4.5,10-10v-60c0-5.5-4.5-10-10-10h0Z"/> </g> <g> <rect class="shape8 default-fill" x="643.5" y="264.5" width="79" height="79" rx="9.5" ry="9.5"/> <path class="line8 cls-1" d="M713,265c4.96,0,9,4.04,9,9v60c0,4.96-4.04,9-9,9h-60c-4.96,0-9-4.04-9-9v-60c0-4.96,4.04-9,9-9h60M713,264h-60c-5.5,0-10,4.5-10,10v60c0,5.5,4.5,10,10,10h60c5.5,0,10-4.5,10-10v-60c0-5.5-4.5-10-10-10h0Z"/> </g> <g> <rect class="shape9 default-fill" x="723.5" y="264.5" width="79" height="79" rx="9.5" ry="9.5"/> <path class="line9 cls-1" d="M793,265c4.96,0,9,4.04,9,9v60c0,4.96-4.04,9-9,9h-60c-4.96,0-9-4.04-9-9v-60c0-4.96,4.04-9,9-9h60M793,264h-60c-5.5,0-10,4.5-10,10v60c0,5.5,4.5,10,10,10h60c5.5,0,10-4.5,10-10v-60c0-5.5-4.5-10-10-10h0Z"/> </g> <g> <rect class="shape10 default-fill" x="803.5" y="264.5" width="79" height="79" rx="9.5" ry="9.5"/> <path class="line10 cls-1" d="M873,265c4.96,0,9,4.04,9,9v60c0,4.96-4.04,9-9,9h-60c-4.96,0-9-4.04-9-9v-60c0-4.96,4.04-9,9-9h60M873,264h-60c-5.5,0-10,4.5-10,10v60c0,5.5,4.5,10,10,10h60c5.5,0,10-4.5,10-10v-60c0-5.5-4.5-10-10-10h0Z"/> </g> <g> <rect class="shape11 default-fill" x="483.5" y="344.5" width="79" height="79" rx="9.5" ry="9.5"/> <path class="line11 cls-1" d="M553,345c4.96,0,9,4.04,9,9v60c0,4.96-4.04,9-9,9h-60c-4.96,0-9-4.04-9-9v-60c0-4.96,4.04-9,9-9h60M553,344h-60c-5.5,0-10,4.5-10,10v60c0,5.5,4.5,10,10,10h60c5.5,0,10-4.5,10-10v-60c0-5.5-4.5-10-10-10h0Z"/> </g> <g> <rect class="shape12 default-fill" x="563.5" y="344.5" width="79" height="79" rx="9.5" ry="9.5"/> <path class="line12 cls-1" d="M633,345c4.96,0,9,4.04,9,9v60c0,4.96-4.04,9-9,9h-60c-4.96,0-9-4.04-9-9v-60c0-4.96,4.04-9,9-9h60M633,344h-60c-5.5,0-10,4.5-10,10v60c0,5.5,4.5,10,10,10h60c5.5,0,10-4.5,10-10v-60c0-5.5-4.5-10-10-10h0Z"/> </g> <g> <rect class="shape13 default-fill" x="643.5" y="344.5" width="79" height="79" rx="9.5" ry="9.5"/> <path class="line13 cls-1" d="M713,345c4.96,0,9,4.04,9,9v60c0,4.96-4.04,9-9,9h-60c-4.96,0-9-4.04-9-9v-60c0-4.96,4.04-9,9-9h60M713,344h-60c-5.5,0-10,4.5-10,10v60c0,5.5,4.5,10,10,10h60c5.5,0,10-4.5,10-10v-60c0-5.5-4.5-10-10-10h0Z"/> </g> <g> <rect class="shape14 default-fill" x="723.5" y="344.5" width="79" height="79" rx="9.5" ry="9.5"/> <path class="line14 cls-1" d="M793,345c4.96,0,9,4.04,9,9v60c0,4.96-4.04,9-9,9h-60c-4.96,0-9-4.04-9-9v-60c0-4.96,4.04-9,9-9h60M793,344h-60c-5.5,0-10,4.5-10,10v60c0,5.5,4.5,10,10,10h60c5.5,0,10-4.5,10-10v-60c0-5.5-4.5-10-10-10h0Z"/> </g> <g> <rect class="shape15 default-fill" x="803.5" y="344.5" width="79" height="79" rx="9.5" ry="9.5"/> <path class="line15 cls-1" d="M873,345c4.96,0,9,4.04,9,9v60c0,4.96-4.04,9-9,9h-60c-4.96,0-9-4.04-9-9v-60c0-4.96,4.04-9,9-9h60M873,344h-60c-5.5,0-10,4.5-10,10v60c0,5.5,4.5,10,10,10h60c5.5,0,10-4.5,10-10v-60c0-5.5-4.5-10-10-10h0Z"/> </g> </svg>
サンプルcss
cssは、こんな感じのものを用意しておきます。
ポイントとしては、JavaScriptで付与する is-active というclassです。
これが順番に「0.数秒おき」に付与されることにより、
SVGに順番に色がついていく様に見えます。
自然な着色のアニメーションをさせるため、フェードイン効果として
transition: fill 0.1s, opacity 1s;
を与えています。
.shape-container { margin: 2rem auto; width: fit-content; } .shape-svg-parent { width: 1366px; } /* ここからSVG内のスタイル */ .cls-1 { fill: #fff; } /* SVG内の図形のデフォルトスタイル */ .default-fill { fill: #fff; opacity: 0; transition: fill 0.1s, opacity 1s; } /* この is-active がJSにより付与されると着色される */ .default-fill.is-active { fill: rgb(65, 175, 190); opacity: 1; }
以上で、次の動画の様なサンプルになります
ここまでのセクションで、
・JSON
・JavaScript
・html
・svg
・css
の順番に、必要な要素を書いてきました。
これらを組み合わせることにより、
「15個の単純な正方形が並んでいるSVG」に、「順番に色をつけていく」
次の動画の様なサンプルになります。
留意点:サンプルが動かない場合
単純に、この記事のサンプルをhtmlに貼り付けて試してみようとしても
貼り付けたhtmlをそのままブラウザで開くだけでは
ブラウザが持っているセキュリティ機能がはたらくため(「CORS」エラーが出る)
動きません。
・サーバーにアップして試してみる
・localサーバーを立ち上げる(これは、gulpを使ったり色々な方法があり検索すればすぐ出るため、この記事では割愛します)
・Safariで開き、「設定 > デベロッパー」から、一時的にクロスオリジンに関する項目を止めてリロード
などの方法で試してみることができます。
地図SVGへの応用をするには
ここまでのセクションの記述で、
「15個の単純な正方形が並んでいるSVG」に、「順番に色をつけていくサンプル」
ができたことになります。
さて、本題としてはこの仕組みを使って、冒頭の動画にある様な
「地図のSVGに順番に色をつける」と言う話です。
これに関しては、「15個の単純な正方形が並んでいるSVG」に、「順番に色をつけていくサンプル」と同じ仕組みで実現が可能になります。
必要な手順は次のとおりです。
1. 色をつけたい地図SVGをコードとして用意する(Illustrator等でSVG変換するのがスムーズかと思います)
2. 色をつけたい地図SVGのコード内に、正方形の場合と同様に、あらかじめclassを付けておく
3. この記事のサンプルで書いた様なJavaScriptを用意する
4. 正方形の場合と同様に、色をつけたい 2. のclass名と対応している外部JSONファイルを用意する
5. JavaScriptを実行すると色が順番につけることができる
という形で、地図SVGにも順番に色をつけていく表現が可能になります。
この記事のまとめ
今回は、冒頭の動画でご紹介した様な、
「SVGに順番に色をつける方法」について記事を書いてみました。
最初のセクションで書いた通りですが
扱う地図の種類を変えたり、
工夫のしかた次第では、いろいろなビジュアル表現に役立つ手法だと思います。
繰り返しになりますが、
たとえば「もっと地域をクローズアップした地図のSVG」を用意して、その地域の色を塗ってあげるなど。
旅行サイトの案件や、観光地を紹介する案件などでも利用ができそうですね。
この記事が皆さんのより良いWeb制作体験につながれば、嬉しく思います。
※この記事は内容の品質向上を目的に、随時更新を行う場合があります。