SVG学習 7日目「イベント」

SVGを学習し始めて1週間になりますが、だいぶこなれてきました。
今ではSVGのみを使って簡単なHTMLサイトを構築できそうな勢いです。
難点はテキストの扱いが非常に苦手で、改行ポイントやレスポンシブデザインの対応などの座標計算などは
全ての要素に対してきちんと随時計算いなければいけないので、全てのサイトをコンバートできるかというとそうではないですが、利点としては、ページで使用するborderなどの罫線が割りかし柔軟なデザインで構築できそうです。
今回はよりインタラクティブなSVGを目指してイベント処理の扱いを学習してみます。
イベント一覧
svgはhtmlと同じdom構造のため、それぞれの要素に対して通常の要素イベントは、大体設置できるようです。
ちなみに、リファレンスページに書かれているのは以下の通りです。
【通常要素イベント】
onfocusin = “<anything>”
onfocusout = “<anything>”
onactivate = “<anything>”
onclick = “<anything>”
onmousedown = “<anything>”
onmouseup = “<anything>”
onmouseover = “<anything>”
onmousemove = “<anything>”
onmouseout = “<anything>”
そして、svgタグだけに記載できるのは、下記のイベント。
【svgタグ専用イベント】
onunload = “<anything>”
onabort = “<anything>”
onerror = “<anything>”
onresize = “<anything>”
onscroll = “<anything>”
onzoom = “<anything>”
また、アニメーション専用のイベントも用意されています。
【アニメーションタグ専用イベント】
onbegin = “<anything>”
onend = “<anything>”
onrepeat = “<anything>”
アニメーションの開始や終了タイミングを取得できるようですね。
コードで学習
svgタグ内にscriptタグを記載して、その関数を呼び出すというオーソドックスタイプでコーディングしてみました。
個別に管理できる利点はありますが、無名関数にできないデメリットがありますが、idやclassを割り当てなくてもダイレクトにイベント設置できるのは利点ですね。
クリックして拡大・縮小
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
<svg width="220" height="220" id="test_1" version="1.1"> <g transform="translate(4,4)" onclick="scaleEvent(this)"> <rect x="0" y="0" width="100" height="100" fill="skyblue" stroke="blue" stroke-width="4"></rect> <rect x="0" y="0" width="60" height="60" fill="tomato" rx="8" ry="8" transform="translate(20 20)"></rect> </g> <script> function scaleEvent(elm){ if(elm.getAttribute("data-flg")){ elm.setAttribute("transform" , "translate(4,4)"); elm.removeAttribute("data-flg"); } else{ elm.setAttribute("transform" , "translate(4,4) scale(2)"); elm.setAttribute("data-flg" , "1"); } } </script> </svg> |
表示されているオブジェクトをクリックすると、拡大したり縮小したりするようにイベント処理してみました。
ちなみに、拡縮状態をトグル対応するために、data-flg属性を付与して対応してます。
サンプル
マースオーバーしてアニメーションする
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
<svg width="220" height="220" id="test_1" version="1.1"> <g transform="translate(4,4)" onmouseover="scaleEventOn(this)" onmouseout="scaleEventOff(this)"> <rect x="0" y="0" width="100" height="100" fill="skyblue" stroke="blue" stroke-width="4"></rect> <rect x="0" y="0" width="60" height="60" fill="tomato" rx="8" ry="8" transform="translate(20 20)"></rect> </g> <script> function scaleEventOn(elm){ elm.setAttribute("transform" , "translate(4,4) scale(2)"); } function scaleEventOff(elm){ elm.setAttribute("transform" , "translate(4,4)"); } </script> </svg> |
マウスがオブジェクトに重なった時に発生するイベントは非常に有意義です。
サンプル
コードで学習2
次はjsだけでsvgを構築し、イベント対応してみます。
svgの基本ライブラリは、過去記事のSVG学習 3日目「Javascriptで描画」からバージョンアップさせていますので、下記ライブラリを使ってください。
svgライブラリ
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 |
;$$svg = (function(){ var $$ = function(selector , option , shapes){ var target = (selector) ? document.querySelector(selector) : document.body; if(!target){return;} var svg = target.querySelector("svg"); if(!svg){ svg = $$.prototype.make.svg(option); } target.appendChild(svg); for(var i=0; i<shapes.length; i++){ $$.prototype.make.shapes(svg , shapes[i]); } }; $$.prototype.add = function(target , shapes){ if(!target){return;} if(typeof target === "string"){ target = document.querySelector(selector); } if(!target){return;} for(var i=0; i<shapes.length; i++){ $$.prototype.make.shapes(target , shapes[i]); } }; $$.prototype.make = { namespace : "http://www.w3.org/2000/svg", xlink : "http://www.w3.org/1999/xlink", svg : function(option){ if(!option){return;} var svg = document.createElementNS(this.namespace , "svg"); for(var i in option){ svg.setAttribute(i , option[i]); } return svg; }, shapes : function(target , option){ if(!option){return;} var elm = document.createElementNS(this.namespace , option[0]); target.appendChild(elm); // attribute for(var i in option[1]){ if(i === "transform" && typeof option[1][i] === "object"){ option[1][i] = this.transform(option[1][i]); } elm.setAttribute(i , option[1][i]); } // string or childNodes if(typeof option[2] === "string" || typeof option[2] === "number"){ // elm.textContent = option[2]; elm.innerHTML = option[2]; } else if(option[2] && typeof option[2] === "object" && option[2].length){ for(var i=0; i<option[2].length; i++){ this.shapes(elm , option[2][i]); } } // event if(option[3] && typeof option[3] === "object" && option[3].length){ for(var i=0; i<option[3].length; i++){ $$event(elm , option[3][i][0] , option[3][i][1]); } } return elm; }, transform:function(option){ var transform = []; if(option.translate){ var tx = (option.translate.x) ? option.translate.x : 0; var ty = (option.translate.y) ? option.translate.y : 0; transform.push("translate("+ tx +" "+ ty +")"); } if(option.rotate){ transform.push("rotate("+ option.rotate +")"); } if(option.scale){ transform.push("scale("+ option.scale +")"); } if(transform.length){ return transform.join(" "); } else{ return ""; } } }; var $$event = function(target, mode, func){ if (typeof target.addEventListener !== "undefined"){ target.addEventListener(mode, func, false); } else if(typeof target.attachEvent !== "undefined"){ target.attachEvent('on' + mode, function(){func.call(target , window.event)}); } }; return $$; })(); |
オブジェクトをマウスでドラッグして移動させる
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
<div id="svg"></div> <script> new $$svg("#svg" , {width:300 , height:300 , version : "1.1" , style : "border:1px solid black;"} , [ ["g" , {id : "drag" , transform:{translate:{x:10,y:10}}} , [ ["rect" , {x:0 , y:0 , width:100 , height:100 , fill:"skyblue" , stroke:"blue" , "stroke-width":"4"}], ["rect" , {x:0 , y:0 , width:60 , height:60 , fill:"tomato" , rx:8 , ry:8 , transform:{translate:{x:20 , y:20}}}], ]] ]); var elm = document.getElementById("drag"); elm.onmousedown = function(e){ var target = this; target.setAttribute("data-drag-flg" , "1"); target.setAttribute("data-pageX" , e.pageX); target.setAttribute("data-pageY" , e.pageY); target.setAttribute("data-posX" , target.transform.baseVal[0].matrix.e); target.setAttribute("data-posY" , target.transform.baseVal[0].matrix.f); //console.log(target.transform.baseVal[0].matrix.e +"/"+ target.transform.baseVal[0].matrix.f); }; window.onmouseup = function(e){ var target = document.getElementById("drag"); if(!target.getAttribute("data-drag-flg")){return;} target.removeAttribute("data-drag-flg"); }; elm.onmousemove = function(e){ var target = this; if(!target.getAttribute("data-drag-flg")){return;} var pageX = Number(target.getAttribute("data-pageX")); var pageY = Number(target.getAttribute("data-pageY")); var posX = Number(target.getAttribute("data-posX")); var posY = Number(target.getAttribute("data-posY")); var diffX = pageX - e.pageX; var diffY = pageY - e.pageY; target.transform.baseVal[0].matrix.e = posX - diffX; target.transform.baseVal[0].matrix.f = posY - diffY; }; </script> |
ライブラリも長くなっているので、codepenに書いてembedしました。
サンプル
See the Pen svg – drag by YugetaKoji (@geta1972) on CodePen.
svgのtransform情報の取得とmatrix属性について
svgのそれぞれのシェイプのx,y座標やwidth,heightサイズなどを取得するのは、getAttributeで簡単に取得できますが、transform情報を取得するのは、少し手間がかかります。
文字列としてscale,translate,rotateなどを取得して、正規表現などで分解してもいいのですが、svgの座標記述などは複数の記述方法があり、なかなかめんどくさい作業になります。
そんな時に、matrixプロパティを取得して行う方法があるので、これを覚えておくと便利に値の取得ができます。
matrixのアクセス方法
エレメントのtransform属性の下にある”baseVal”の最初の配列にアクセスし、その直下にmatrixが存在します。
1 2 3 4 5 6 7 8 |
対象Element.transform.baseVal[0].matrix > SVGMatrix a:1 b:0 c:0 d:1 e:20 f:20 |
ここで取得できる情報がa~fまである事がわかります。
それぞれの情報は以下の通りです。
ちなみに、transformが設定されていない場合は、baseVal配列にはデータが無いため、それ以下は”undefined”になるので、事前に判定処理が必要です。
1 2 3 4 5 6 |
a:scale-x b:rotate-p c:rotate-m d:scale-y e:transform-x f:transform-y |
それぞれの値が細かく入っています。
座標を取得したい場合は、以下のような記述ですね。
1 2 |
対象Element.transform.baseVal[0].matrix.e; // X座標 対象Element.transform.baseVal[0].matrix.f; // Y座標 |
参考
https://triple-underscore.github.io/SVG11/script.html#EventHandling
次回予告
次回はシェイプ情報の”PATH”をちゃんと学習できていなかったので、”PATH”を学習します。