事の発端
ASP.NET MVC5 を使った開発で、フロントエンドに Vue.js を使うことにした。
その際、SVGのコンポーネントにデータをバインドしたかったが、外部ファイルをVue.jsでマウントする例があまり見つからなかったので一例として残すことにした。
要件
- SVGで作ったコンポーネントの色や形を動的に変更したい
- SVGの種類は複数存在する
- SVGはインラインではなくファイルとして保持1し、URLパラメータに対応するファイルを読み込む
- SVGにはマウントする前に、バインドをするための
v-bind:style
属性をJavascriptで動的に付与する必要がある
まずはSVGを表示しよう
HTML5でのSVGファイル操作のおさらいを見てみると下記のように書かれている。
IMGタグやCSSで表示してしまうと、表示されたSVG形式ファイルに対してJavaScript等で一切操作ができないので、インラインSVGタグとして埋め込むか、イベントハンドラ処理のJavaScriptをSVG(XML文書)内に直接埋め込んだSVGファイルをOBJECTタグで埋め込むかの二択になる。
外部からファイルを読み込みたいから、HTMLにSVGを直接書くわけにはいけない。
ならば、objectタグだ!
<objectid="logo"type="image/svg+xml"data="~/Resources/logo.svg"></object>
よし、これならJavascriptで色や形を変更することも出来そうだ。
constsvgDoc=document.getElementById('linear').contentDocument;constsvg=svgDoc.getElementsByTagName("svg")[0];constcircles=svgDoc.getElementsByTagName("circle");for(constcircleof[...circles]){circle.setAttribute("style","fill: blue; stroke: black");}
Vue.jsを使ってみる
実験なので、ASP.NET MVCのViewでインラインスクリプトにVue特有の属性を動的に付与し、Vueのインスタンスを生成し、SVGをマウントしてみる。
@section scripts{
<script type="text/javascript">
window.addEventListener("load", function(event) {
const svgDoc = document.getElementById('linear').contentDocument;
const svg = svgDoc.getElementsByTagName("svg")[0];
const circles = svgDoc.getElementsByTagName("circle");
for(const circle of [...circles]){
circle.setAttribute("v-bind:style", "fill: fillColor; stroke: strokeColor");
}
}
const viewModel = new Vue({
el: "#logo",
data: {
fillColor: "blue",
strokeColor: "black"
},
methods: {
testChangeColor : function(){
this.fillColor: "red";
this.strokeColor: "black";
}
}
}
</script>
}
<button v-click:on="testChangeColor">色が変わるよ</button>
<object id="logo" type="image/svg+xml" data="~/Resources/logo.svg"></object>
残念ながらボタンをクリックしても色は変わらず。
エラーもでていないので、そもそも外部のリソースはVueでマウントすることは出来ないのでは…?と思い至った。
(ご存知の方がいらっしゃればコメント頂ければ幸いです)
どうすれば良い?
そもそも外部リソースにしないで、インラインSVGとして読み込めないのか?とここで気づいた。
そして、試しにobjectタグを使わずに下記のように書き直した。
+ @Html.Raw(File.ReadAllText(Server.MapPath("~/Resources/logo.svg"))));
- <object id="logo" type="image/svg+xml" data="~/Resources/logo.svg"></object>
C#
やASP.NET MVC
を知らない方向けにざっくり説明するとサーバーのローカルファイルから読み取ったテキストをHTMLタグとして組み込んでいるだけだ。
これでHTML上にSVGタグが組み込まれた!
が、私の場合はSVGファイルにstyleタグが含まれていたため、Vueがマウントできないと怒っていた。
そこで、(邪道な気がするが)正規表現でstyleタグを抽出してマウントされない場所に移すことにした。
結果下記のようなコードになった。
@{
var regex = new System.Text.RegularExpressions.Regex("<style.*>(.*)</style>");
var svgDoc = System.Text.RegularExpressions.Regex.Replace(
File.ReadAllText(Server.MapPath("~/Resources/logo.svg")),
@"\t|\n|\r",
""
);
var css = regex.Match(svgDoc).Groups[0].Value;
var svgDocWithoutCss = svgDoc.Replace(css,"");
css = css.Replace("<![CDATA[", "").Replace("]]>", "");
}
@section styles{
@Html.Raw(css)
}
@section scripts{
<script type="text/javascript">
window.addEventListener("load", function(event) {
const svgDoc = document.getElementById('linear').contentDocument;
const svg = svgDoc.getElementsByTagName("svg")[0];
const circles = svgDoc.getElementsByTagName("circle");
for(const circle of [...circles]){
circle.setAttribute("v-bind:style", "fill: fillColor; stroke: strokeColor");
}
}
const viewModel = new Vue({
el: "#logo",
data: {
fillColor: "blue",
strokeColor: "black"
},
methods: {
testChangeColor : function(){
this.fillColor: "red";
this.strokeColor: "black";
}
}
}
</script>
}
<button v-click:on="testChangeColor">色が変わるよ</button>
@Html.Raw(svgDocWithoutCss)
@section styles
や@section scripts
は、Shared/_Layout.cshtmlでマウント外の領域で@RenderSection
している。
これによって@section styles
や@section scripts
内の内容は全て@RenderSection
した領域にコンテンツが埋め込まれる。
SVGはVisioで作ったファイルをそのまま吐いています ↩