わりとやりたそうな事なのに、ワタシの乏しい既存の知識ではマッタク出来ず、情報もあまり多くはなく、一筋縄ではいかなかった件の覚え書きです。
ダウンロードしたい!
SVG形式でダウンロード
単純に一番外側のsvg要素のouterHTML
が保存できればいいんですよね~。
これをクリップボードに入れるとかでもいいかなとは思ったんですが、ちゃんとsvgファイルとしてダウンロードするには? と調べてみると、何やらぶろぶ? なるものを作らねばならないラシイ。
ここでまさに!
比較的新しく追加されたなでしこさんの命令に、BLOB作成
がありますよ☆
SVGはXMLベースなので、オプションには"type":"application/xml"
を指定します。
そして、コレ。
URL.createObjectURL() 静的メソッドは、引数で指定されたオブジェクトを表す URL を含む DOMString を生成します。 URL の寿命は、それを作成したウィンドウ内の document と結び付けられています。 新しいオブジェクト URL は、指定された File オブジェクトか Blob オブジェクトを表します。
チョットナニイッテルカワカンナイ;
でもま、呪文ですよ、呪文(スミマセン💧)
とにかく、BLOB作成で作ったぶろぶから、オブジェクトURLを作成する命令なんですよね?
あとはそのURLを仮のa
タグのhref
に設定してclick
を送ってやると、download
に指定したファイル名でダウンロードされるって寸法です。
仮のaタグは最後消すの忘れず。
オブジェクトURLも、URL.revokeObjectURL()
で解放しなきゃなのかな?
●(ファイル名でsvgを)SVGダウンロード
もし、ファイル名=空ならば、ファイル名=「nako3_SVG.svg」
SVGテキスト=svg["outerHTML"]。
BLOB=SVGテキストを{"type":"application/xml"}でBLOB作成。
URL=「URL.createObjectURL」を[BLOB]でJS関数実行。
A=「a」のDOM部品作成。# 仮のaタグ。
Aの「download」にファイル名をDOM属性設定。
Aの「href」にURLをDOM属性設定。
Aの「click」を空でJSメソッド実行。
DOM親要素からAをDOM子要素削除。# 消して終了。
「window.URL.revokeObjectURL」を[URL]でJS関数実行。# 解放して終了。
ここまで。
できました!
PNG形式でダウンロード
なんてことも出来るらしいと知ったのでやってみる!
どうやらbase64のデータURLに変換して、それを仮のキャンバスに描画するらしい。
これまた完全に呪文をJS実行です。ほんとスミマセン💧
●(svgを)SVGキャンバス描画
SVGテキスト=svg["outerHTML"]。
IMGデータ=「"data:image/svg+xml;charset=utf-8;base64,"+btoa(unescape(encodeURIComponent('{SVGテキスト}')))」をJS実行。
IMGデータの画像読んだ時には、
[0,0]に対象を画像描画。
ここまで。
ここまで。
とりあえずコレで、描画中キャンバスに指定したSVG画像が描画されます。
そして、それを描画ダウンロードしてやればpng形式でダウンロードされるって寸法です。
ただ、描画ダウンロードはファイル名の指定ができないのでSVGダウンロードと同様にaタグを作り描画ダウンロードリンク作成
する……と思ったらファイル名がないって言われるよ!
うーん、画像読んだ時には
の中だからか……
ならばと、先にaタグを作成してみたら、今度はAが無いって言われたよ><
むーぅ、には
構文の中からは、親関数の引数も変数もみんな見えなくなっちゃうのは不便にゃあ
なでしこ1では、関数の中でも宣言しないで初めて使った変数はグローバルになった気がするんだけど、なでしこ3では宣言してもしなくてもローカルになるっぽいのねん。
そして、には構文
は無名関数
なので、別関数の中ということになるのでローカル変数は見えなくなっちゃう。
普通の関数なら引数として渡して引き継げばいいんだけど、この場合は? 一体どうしたらいいんにゃぁ~…………
紆余曲折の末、こうなった。
●(svgをファイル名で|ファイル名へ|ファイル名に)PNGダウンロード
もし、ファイル名=空ならば、ファイル名=「nako3_SVG.png」
SVGテキスト=svg["outerHTML"]。
IMGデータ=「"data:image/svg+xml;charset=utf-8;base64,"+btoa(unescape(encodeURIComponent('{SVGテキスト}')))」をJS実行。
w=svgの「width」をSVG属性取得。
h=svgの「height」をSVG属性取得。
[w,h]のキャンバス作成して描画開始。# 仮のキャンバス
A=「a」のDOM部品作成。# 仮のaタグ
Aの「download」にファイル名をDOM属性設定。# ファイル名を仮設定。
IMGデータの画像読んだ時には、
[0,0]に対象を画像描画。# SVGのデータスキームを仮のキャンバスに描画。
A=「nadesi-dom-{DOM部品個数-1}」のDOM要素ID取得。
ファイル名=Aの「download」をDOM属性取得。
Aに描画ダウンロードリンク作成。# 「download」が「canvas.png」に設定されてしまう。
Aの「download」にファイル名をDOM属性設定。# ファイル名を再設定。
Aの「click」を空でJSメソッド実行。
DOM親要素からAをDOM子要素削除。# 全部消して終了。
DOM親要素から描画中キャンバスをDOM子要素削除。
ここまで。
ここまで。
先にaタグを作成してdownload
にファイル名を仮設定し、画像読んだ時には
の中ではDOM要素ID取得
でIDからaのDOM要素を取得。ファイル名はdownload
のDOM属性取得しておいて、描画ダウンロードリンク作成
後に再設定。
ちなみに、なぜこんなコトが必要かというと、描画ダウンロードリンク作成すると自動的に「download」属性に「canvas.png」が設定されるので、好きなファイル名でダウンロードするためにはその後で書き換えなきゃだからです。
なんかもっとしゅっと出来る手立てはないものか(描画ダウンロードを自前でJS実行でやるとかそうゆうことではなく)
とりあえず、テスト。
!『https://n3s.nadesi.com/plain/SVGplus.nako3』を取り込む。
母艦=DOM親要素。
SVG=[100,100]のSVG親要素作成。
「pink」にSVG塗り色設定。
[50,50]へ30のSVG円描画。
黒色にSVG塗り色設定。20にSVG文字サイズ設定。
[50,10]へ「なでしこ」をSVG縦書き文字描画。
# UI作成
母艦にDOM親要素設定。
改行作成。
svgダウンロードボタン=「svgダウンロード」のボタン作成。
svgダウンロードボタンをクリックした時には、
「nadesiko.svg」でSVGをSVGダウンロード。
ここまで。
pngダウンロードボタン=「pngダウンロード」のボタン作成。
pngダウンロードボタンをクリックした時には、
「nadesiko.png」でSVGをPNGダウンロード。
ここまで。
できました☆
キャンバスに描画したい!
先ほど[0,0]への描画は簡単にできましたが、任意の位置に描画できるようにしたいと思ったところが、これまた先ほどと同じく画像読んだ時には
の中では関数の引数に指定したxyが見えなくなってしまう。
うーん、そりゃグローバルに、なんか作業用の仮変数でも作っておけば出来るけど、あんまりかっこよくはないですよねー。プラグインの時は特に、なるべく関数の中で完結させたい感なんですが。
さっきはダウンロードようのAタグを作ったからそれを活用したけど……おぉ! なんか仮のタグでも作ればいいじゃない!
# 描画中キャンバスの[x,y]へ描画する
●(svgをxyへ|xyに)SVGキャンバス描画
もし、xy=空ならば、xy=[0,0]。
SVGテキスト=svg["outerHTML"]。
IMGデータ=「"data:image/svg+xml;charset=utf-8;base64,"+btoa(unescape(encodeURIComponent('{SVGテキスト}')))」をJS実行。
仮=空のラベル作成。
仮の「xy」にxyをDOM属性設定。
IMGデータの画像読んだ時には、
仮=「nadesi-dom-{DOM部品個数-1}」のDOM要素ID取得。
xy=仮の「xy」をDOM属性取得。
x,y=xy。
[x,y]に対象を画像描画。
DOM親要素から仮をDOM子要素削除。
ここまで。
ここまで。
できました!
でも、明らかに美しくは無いですよね。作業用のグローバル変数の方がマシでしょうか? 誰か賢い人、たすけて
しかし、とりあえずこれで、アドベントカレンダー 7日目の記事『なでしこさんで縦書きしたい!』で積み残しにした件は解決しそうです。
!『https://n3s.nadesi.com/plain/SVGplus.nako3』を取り込む。
30にSVG文字サイズ設定。
SVG=[30,250]のSVG親要素作成。
[15,0]へ『「こんにちはー。」』をSVG縦書き文字描画。
SVGを[135,20]へSVGキャンバス描画。
できました。
あ、でもキャンバスの方を使いたいわけだから、SVGの方は描画後消さなきゃでしたかね?
!『https://n3s.nadesi.com/plain/SVGplus.nako3』を取り込む。
30にSVG文字サイズ設定。
SVG=[30,250]のSVG親要素作成。
SVGの「hidden」にオンをSVG属性設定。# 隠す
[15,0]へ『「こんにちはー。」』をSVG縦書き文字描画。
SVGを[135,20]へSVGキャンバス移動。
●(svgをxyへ|xyに)SVGキャンバス移動
もし、xy=空ならば、xy=[0,0]。
SVGテキスト=svg["outerHTML"]。
IMGデータ=「"data:image/svg+xml;charset=utf-8;base64,"+btoa(unescape(encodeURIComponent('{SVGテキスト}')))」をJS実行。
svgの「id」に「移動元SVG」をDOM属性設定。
仮=空のラベル作成。
仮の「xy」にxyをDOM属性設定。
IMGデータの画像読んだ時には、
svg=「移動元SVG」のDOM要素ID取得。
親=svgのDOM親要素取得。
仮=「nadesi-dom-{DOM部品個数-1}」のDOM要素ID取得。
xy=仮の「xy」をDOM属性取得。
x,y=xy。
[x,y]に対象を画像描画。
親からsvgをDOM子要素削除。
ここまで。
ここまで。
こんな感じで行けそう?
重なり順を変えたい!
通常のHTML5のDOMならz-index
を設定すれば、表示の重なり順を変えたい放題ですよねー。
ところが、SVG画像ではそれが出来ないようなんです!
えーっ、マジかー。なんで~?
いや、まあ、確かにね、複雑な画像になればなるほど、z-indexの順番を追うより、描いた順……タグの並び順通りに重なっていますよって方が、圧倒的に分かりやすいには違いないですよ。
キャンバスへの描画だって当然先に描いたものは後から描いたものの下になりますし、あとから重なり順を動的に入れ替えたいとなったら1回消して再描画する事になるわけだけど、なにしろSVGはタグですから。何も再描画しなくても、しゅっと順番入れ替えられたらそれで良さそうなもんですよね。
なんとかならんのか? といろいろ調べた結果、じゃばすくりぷとでは、DOMの前の要素や次の要素を取得することが出来るし、参照先のDOMの前の位置に子要素を挿入したりする命令もあるらしい。
というわけで、タグの並び順自体はしゅっと入れ替えられそう。
他でもいろいろ使えそうな命令だから、使いやすいように関数にしておこうっと。
こんなかんじ?
# 記述した順に重なっていくので、最後に追加すると最前面になり、最初に挿入すると最背面となる。
●(svgを)SVG最前面
親=svgのDOM親要素取得。
svgを親にDOM子要素追加。
ここまで。
●(svgを)SVG最背面
親=svgのDOM親要素取得。
最初=親のDOM初子要素取得。
親の最初へsvgをDOM子要素挿入。
ここまで。
●(svgを要素の)SVG背面
親=svgのDOM親要素取得。
もし、要素=NULLならば、要素=親のDOM初子要素取得。
親の要素へsvgをDOM子要素挿入。
ここまで。
●(svgを要素の)SVG前面
親=svgのDOM親要素取得。
次要素=要素のDOM次要素取得。
親の次要素へsvgをDOM子要素挿入。
ここまで。
##DOMプラス
●(DOMの)DOM親要素取得
DOM["parentNode"]で戻る。
ここまで。
# 要素中の最初の子要素
●(DOMの)DOM初子要素取得
DOM["firstChild"]で戻る。
ここまで。
# 要素中の最後の子要素
●(DOMの)DOM末尾子要素取得
DOM["lastChild"]で戻る。
ここまで。
# 同じ階層の前の要素
●(DOMの)DOM前要素取得
DOM["previousSibling"]で戻る。
ここまで。
# 同じ階層の次の要素
●(DOMの)DOM次要素取得
DOM["nextSibling"]で戻る。
ここまで。
●(親要素の参照先へ|参照先に子要素を)DOM子要素挿入
親要素の「insertBefore」を[子要素,参照先]でJSメソッド実行。
ここまで。
テスト。
!『https://n3s.nadesi.com/plain/SVGplus.nako3』を取り込む。
母艦=DOM親要素。
黒色にSVG線色設定。
「黄色の四角の重なりを変更します」のラベル作成。改行作成。
[100,100]のSVG親要素作成。
青色にSVG塗り色設定。
青四角=[0,0,80,80]へSVG四角描画。
黄色にSVG塗り色設定。
黄四角=[10,10,80,80]へSVG四角描画。
赤色にSVG塗り色設定。
赤四角=[20,20,80,80]にSVG四角描画。
# UI作成
母艦にDOM親要素設定。改行作成。
最前面ボタン=「最前面」のボタン作成。
前面ボタン=「前面」のボタン作成。
背面ボタン=「背面」のボタン作成。
最背面ボタン=「最背面」のボタン作成。
# イベント
最前面ボタンをクリックした時には、
黄四角をSVG最前面。
ここまで。
最背面ボタンをクリックした時には、
黄四角をSVG最背面。
ここまで。
前面ボタンをクリックした時には、
次要素=黄四角のDOM次要素取得。
黄四角を次要素のSVG前面。
ここまで。
背面ボタンをクリックした時には、
前要素=黄四角のDOM前要素取得。
黄四角を前要素のSVG背面。
ここまで。
できました! よきーよきー♪
ドラッグ&ドロップしたい!
ドラッグ&ドロップもあまり情報が無くて困った。
とりあえず動かす
!『https://n3s.nadesi.com/plain/SVGplus.nako3』を取り込む。
# 描画
親SVG=[400,400]のSVG親要素作成。
四角=[0,0,80,80]へSVG四角描画。
# 変数
移動フラグ=オフ。
元X=空。元Y=空。移動X=空。移動Y=空。
# イベント
四角をマウス押した時には、
移動フラグ=オン。
元X=マウスX。
元Y=マウスY。
ここまで。
四角をマウス移動した時には、
もし、移動フラグ=オンならば、
移動X=マウスX-元X。
移動Y=マウスY-元Y。
X=四角の「x」をSVG属性取得。
Y=四角の「y」をSVG属性取得。
X=X+移動X。
Y=Y+移動Y。
四角の「x」にXをSVG属性設定。
四角の「y」にYをSVG属性設定。
ここまで。
ここまで。
四角をマウス離した時には、
移動フラグ=オフ。
ここまで。
一見動いたんですけれどね。
マウス押した時のイベントは、円だの四角だの動かしたい要素上で取得しますけど、マウス移動した時のイベントは、その要素上じゃダメなんですよね。
ゆーっくり動かせば一見動くんだけど、マウス移動した時のイベントは1px動くごと発生するってワケじゃ無いんで、マウスをしゅっと動かして要素から外れてしまったら、止まっちゃう。
というわけで、マウス移動した時とマウス離した時は親SVGのイベントにしてみたんですが、それもうまくいきませんでした。
マウスX
とマウスY
じゃダメだ・・・
マウスXとマウスYは、マウスが四角の上にある時は、四角内でのマウスの位置を、四角を外れると親SVG内でのマウスの位置を返してくれるみたい。
親SVGをマウス移動した時
のイベントでも、対象
は実際にマウスが乗っている要素になるってワケですね。そして、マウスX,マウスYは、対象イベント
のclientX
,clientY
から、対象
要素の左端,上端を引いたやつと言うことラシイ。
今回の場合は、対象イベント
のclientX
,clientY
を直接参照すればよさそう?・・・と思ったんですが、ドラッグしながらホイールでスクロールしてもだいじょぶなようにpageX
,pageY
にしてみました。
こんな感じ?
!『https://n3s.nadesi.com/plain/SVGplus.nako3』を取り込む。
※四角だけ動かせます。
# 描画
親SVG=[400,400]のSVG親要素作成。
赤色にSVG塗り色設定。
四角=[0,0,80,80]へSVG四角描画。
青色にSVG塗り色設定。
円=[100,100]へ50のSVG円描画。
# 変数
移動フラグ=オフ。
元X=空。元Y=空。移動X=空。移動Y=空。
マウス履歴=空。
# イベント
四角をマウス押した時には、
移動フラグ=オン。
元X=対象イベント["pageX"]。
元Y=対象イベント["pageY"]。
ここまで。
親SVGをマウス移動した時には、
もし、移動フラグ=オンならば、
移動X=対象イベント["pageX"]-元X。
移動Y=対象イベント["pageY"]-元Y。
元X=対象イベント["pageX"]。
元Y=対象イベント["pageY"]。
X=四角の「x」をSVG属性取得。
Y=四角の「y」をSVG属性取得。
X=X+移動X。
Y=Y+移動Y。
四角の「x」にXをSVG属性設定。
四角の「y」にYをSVG属性設定。
ここまで。
ここまで。
親SVGをマウス離した時には、
移動フラグ=オフ。
ここまで。
できました!
マウスをしゅっと動かしても、上に別の図形が被ってもだいじょぶ☆
でもですよ、色々検索して、このように四角(rect)のx
とy
を変化させて動かしてるとこや、円(circle)のcx
とcy
を変化させて動かしてるとこはありましたけど、図形によって変化させたい値が違うって面倒すぎる! しかも多角形とかましてやパスの場合はどーしますか? って話ですよ。
結論から言えば、Translate
すればいいじゃない! ってことになりました。
transformで描画移動
とりあえずはコレ!
transform
属性のtranslate
に直接移動量を設定すればよさそう。
!『https://n3s.nadesi.com/plain/SVGplus.nako3』を取り込む。
# 描画
親SVG=[400,400]のSVG親要素作成。
赤色にSVG塗り色設定。
ハート=[10,10,80,80]のSVGハート描画。
青色にSVG塗り色設定。
円=[100,100]へ50のSVG円描画。
# 変数
移動フラグ=オフ。
元X=空。元Y=空。移動X=空。移動Y=空。既存T=空。
マウス履歴=空。
# イベント
ハートをマウス押した時には、
移動フラグ=オン。
既存T=ハートの「transform」をSVG属性取得。
もしそれがNULLならば、既存T=空。
元X=対象イベント["pageX"]。
元Y=対象イベント["pageY"]。
ここまで。
親SVGをマウス移動した時には、
もし、移動フラグ=オンならば、
移動X=対象イベント["pageX"]-元X。
移動Y=対象イベント["pageY"]-元Y。
ハートの「transform」に「translate({移動X},{移動Y}) {既存T}」をSVG属性設定。
ここまで。
ここまで。
親SVGをマウス離した時には、
移動フラグ=オフ。
ここまで。
できました!
これならパスでも何でも動かせます☆
また、translateは既存のtransform属性値の後ではなく、前に追加するのがポイントでした。
transformは、前(左)から順に適用されていくのですが、たとえば先に回転してから移動とすると、移動するx軸y軸自体回転してしまうのです!
マウスはを横に動かしてるのに図形は斜めや縦に動いちゃうって事が発生しちゃいます。
描画の起点自体を先に移動させておくことで、回転とか傾きとかのかかった図形も見た目通りの向きへ移動できました。
transformをまとめる
できた~♪ と思ったんですが、図形の見た目上は一見問題ないんですけど、これだと何度も部品の移動を繰り返すと、translateがいくつもつながって、transform属性の中身が大変なことに~!!!
気ままに10回ほど動かしたら、こんなんなってた
あな恐ろしや・・・
<circle cx="50" cy="50" r="40" transform="translate(-76,25) translate(55,51) translate(42,-41) translate(-119,-11) translate(33,-51) translate(54,82) translate(62,-51) translate(25,42) translate(-54,8) translate(63,20) "></circle>
とゆうわけで、おまとめしたい。
当初、getCTM
とかでまとめられるかと思ったんだけど、一見出来たような気がしたんだけど、これもワタシがSVGの仕様をよく分かってなかったせいでダメでした。
getCTM
は、g
のグループとかでtransformが上の階層からもかかっていたらそれも込みの座標になるし、viewBox
も反映されるので、それをmatrixとして戻してしまったらずれちゃう。ていうかそもそも、そうゆう全体像としての座標を取得するやつなんだからしょうがない。
そんなこんなで、まあまあな艱難辛苦の末こうなった。
●(svgの)SVG変換マトリクス統合
変形リスト=svg["transform"]["baseVal"]。
変形リスト要素数=変形リスト["numberOfItems"]。
仮SVG=「document」の「createElementNS」を['http://www.w3.org/2000/svg','svg']でJSメソッド実行。
MTX=仮SVGの「createSVGMatrix」を空でJSメソッド実行。
変形リスト要素数回
変形リストの「getItem」を回数-1でJSメソッド実行。
MTX=MTXの「multiply」をそれ["matrix"]でJSメソッド実行。
ここまで。
matrix=「matrix({MTX["a"]},{MTX["b"]},{MTX["c"]},{MTX["d"]},{MTX["e"]},{MTX["f"]})」
svgの「transform」にmatrixをSVG属性設定。
ここまで。
SVGTransformListオブジェクト
なるものを取得すると、文字通りtransform属性の値がmatrixに変換された状態でリストになってる。
これは配列ではなくgetItem
しなきゃいけないラシイ。む~・・・。
そしてそれらをmultiply
どんどん合成(乗算)していくって寸法です。
transformにtranslateを追加した後にSVG変換マトリクス統合すれば・・・
親SVGをマウス移動した時には、
もし、移動フラグ=オンならば、
移動X=対象イベント["pageX"]-元X。
移動Y=対象イベント["pageY"]-元Y。
ハートの「transform」に「translate({移動X},{移動Y}) {既存T}」をSVG属性設定。
ハートのSVG変換マトリクス統合。
ここまで。
ここまで。
10回と言わず何回動かしたってこのとーり☆
<circle cx="50" cy="50" r="40" stroke="black" transform="matrix(1,0,0,1,189,29)"></circle>
すっきり! やったね!!
使いやすくする
プラグインでは、DOMイベント追加
を使って、動かしたい要素にSVGドラッグオン
を指定し、移動範囲となる要素(通常は大本のSVG要素かdocumentと思います)にSVGドラッグ許可
を指定したらドラッグ&ドロップ出来るようにしました☆
!『https://n3s.nadesi.com/plain/SVGplus.nako3』を取り込む。
親SVG=[300,300]のSVG親要素作成。
親SVGのSVGドラッグ許可。
赤色にSVG塗り色設定。
丸=[150,150]へ40のSVG円描画。
丸のSVGドラッグオン。
こんだけ!
おわります
なでしこのアドベントカレンダーなのに、なんとなくJavascriptを頑張る話が多めの印象になっちゃいましたが、最終的なコードはなでしこなのでゆるして💧
このようにJS実行
、JS関数実行
、JSメソッド実行
などの特殊命令を使っていけば、なでしこさんだけでは出来なさそげないろいろなことも出来ちゃうよ! ということの参考にでもなればです。
というわけで、SVGのプラグインを作るネタは一応これで終了です。
まとめて書いたのですっかり長くなってしまいました。
カレンダーがピンチだったら分割してこれで引っ張ろうかとも思っていたのですが、なでしこ成分少なめだしなーと躊躇してるうち、皆様のご尽力でカレンダーもイイ感じに埋まってきたのでよかったです。
SVGの機能としては他に、グラデーションとかフィルターとかアニメーションとかとか、色々あるのですが、プラグインにするに当たってのネタはあまりないので、それは別途何か作りながらでも紹介できたらというつもりでいます。
それらも含めたSVGの追加版のプラグインをなでしこ貯蔵庫に公開しています。
!『https://n3s.nadesi.com/plain/SVGplus.nako3』を取り込む。
追加版を取り込めば、基本版も一緒に取り込まれます。
また、同じものはGitHubにも置いてあります。
紹介しきれてない色々な機能のテストなどが置いてあるので、よければ見てみてください☆