全面的な変更
ツールで使用する「SVGのアイコン」が必要になると、かなりの確率で自製のアイコンを使う様になってきました。 フリーのSVGアイコンはそれなりにネット上にありますが、デザインが良くても「文字数」が多いものは困ります。 小規模のプログラムでSVGコードが幅を採るのは不釣り合いなので。
その制作に、必ず使うのが「Simple SVG」です。 先ず適当なアイコンデザインを描いて、256×256ドット程度のモノクロのpng画像を作ります。 それを、ネット上のフリーのSVG変換サイトに持ち込んで、SVG画像に変えます。 しかし、これをもう少しコンパクトに出来る可能性が多いので、「Simple SVG」で文字数を減らします。 上手い時は6~8割程度に減らせ、これを JavaScriptコードに書き込むという手順です。
自分用とはいえ使い難さが...
「Simple SVG」は、SVGのソースコードを最後は取得する必要から、てっとり早い方法と考えて、アメーバの編集画面を作業場所に利用していました。 しかし今回の更新で判ったのですが、特殊な編集枠の特性で処理ごとに出来る余分な空白行を削除したり、毎回「iframe」内のデータにアクセスしたりと、けっこう手間な操作が必要でした。
今回、編集画面を使わずに、普通の「div要素」を用意して処理場所にすると、なんとも簡潔なコードになりました。 結果が良いので、全てのコードを見直して作り換えました。 インターフェイスも、かなり判り易くなったと思います。
一番変わったのは、作業場所がどこでも良くなった事です。 アメーバブログの記事画面でも、YouTubeの動画画面でも、Googleの検索画面でも、普通のURLで開く場所ならどこでも良いという事です。
ブラウザが開いたページ上に黒背景を敷いて、その上でツールが動作しますが、操作を受け取るのは、これまでと同じ「Fn」キー入力だけです。 ただし、余り変な画面(ウェブページ)で操作するのは禁忌です。 ツールが受取らないキー入力が、見えていない画面への入力になり得るので。 問題が絶対に生じないのは、簡単な「htmlファイル」を作り、それをブラウザにドロップして作業する事です。 まあ、そこまでしなくてもアメブロの「自分のブログページ」で作業すると良いでしょう。
以下に、このツールでSVGアイコンを作る操作を紹介します。
SVG画像の制作手順
❶ 元になる画像を作る
素で描画しても良いですが、ここではネット上の画像をアイデア元に拝借します。
下の自動車用のステッカーの絵が、なかなかいいので使います。 自分で使うのですから著作権とか言わないでください。
スクリーンショットから加工して 150×150 dot の2色の「PNG画像」を作ります。
少しジャギーがありますが、アイコンはもっと小さく判らなくなります。「150」よりもっと小さくても可能ですが、次第に歪みが強くなるので、私はこの程度にしています。
SVG変換サイトで変換する
これを以下のSVG変換サイトで「PNG ➔ SVG」の変換をします。
このサイトのトップ画面で、用意した 150dot のファイルを選択して「Start」のボタンを押します。
SVG変換変換は数秒もかかりません。 下の結果画面で処理した画像が判ります。 前画面に戻って、処理方法を変更してやりなおしが出来ます。
結果に納得できれば、上のリンクボタンを押すと、ダウンロードフォルダーに変換したSVG画像が保存されます。 処理したSVGの容量は「1.74KB」となっています。
生成した SVG画像を「Simple SVG」でコンパクトにする
ここから「Simple SVG」の出番です。 適当なブログページで「Tampermonkey」のダッシュボードの「Simple SVG」を「ON」にして、ページをリロードします。
下の様に真っ黒な画面になります。
●「F8」キーを押すと、Windowsのファイル読込みのダイアログが表示されます。
先ほどダウンロードした SVG画像を読み込むと、下の様に左側の枠に表示されます。
左側の枠は、デフォルト画像で、右側に軽量化処理を試した結果が表示されます。
● 150×150dot のSVG画像は、そのまま 150×150pxのサイズで表示されるので、少し小さくて細かな部分が判り難いので、「F9」を押して拡大表示します。
ここから、SVGのソースコードの簡略軽量化を試します。
● 順番は決まっていませんが、最初に「F6」属性処理を試すのが判り易いです。
これは、ファイルの「xmlns」等の属性指定を削除します。 色々な不要な属性を省きますが、簡略化による画像の劣化は右側枠で確認できます。
下の様に、メニューの左端に、操作ごとの文字数の変化が表示されます。
● 次に「F7」スタイル削除を試します。 SVG画像の入手先によりますが、ファイルにインラインのスタイル指定が沢山付いて来る事があります。 これを削除すると、サイズや配置が変化する場合もありますが、全く無問題の場合もあります。「F7」を試して文字数が減って変化が無いなら、減る方の設定にします。
下は、「属性処理」「スタイル削除」の両方をONにしたところです。
● 次に「F1」~「F4」を順に押して、文字数の減少と、画像の劣化を観察します。 これは、場合によって大きく文字数と画質が変化します。
この画像の場合は、最も強い軽量化の「F1」でも、右側の画像が使用に耐えることが判りました。 文字数は「1526 ▷ 827」で、半分近い文字数にできました。
下は、「DevTools」で 処理済の SVGのソースコードを見たところです。
コードを見ると「#ffffff; stroke:none;」➔「#fff;」、「#010101; stroke:none;」➔「#000;」と減らせます。「stroke」プロパティは多くの場合に不要で、これも削除するコードを考えて見たいと思います。
SVGは、描画方法の特性でジャギーが出ないので、元にした画像より綺麗です。(微妙に歪みが出るかも知れませんが) これをアイコンとして使う時は、枠線をCSSで表示してアイコンのデザインにします。
アイコンの小型サイズで使うなら、元画像は 150dot は要らないですね。 もっと小型の元画像から SVGを作れば、更に文字数を減らせるでしょう。
今後の方針
現在の段階では、SVGのソースコードを「DevTools」から取得する事になります。 これはダウンロードでファイル出力できます。 必要なのは、SVGのソースコードですから、出来れば コードに記入する際の折り返し処理を自動で出来たらと思います。 これが現在一番欲しい機能で、この書換えはけっこう手間だからです。
「Simple SVG」を利用するには
このツールは Chrome / Edge / Firefox の拡張機能「Tampermonkey」上で動作します。 このツールは処理操作をする場合のみ「ON」にする非常駐型ツールです。 このツールが起動すると、ブラウザのほぼ全てのページをオーバーライドするので、通常のブラウザ操作が出来なくなります。
このツールを終了するには、ブラウザのツールバーの「Tampermonkey」のメニューから「Simple SVG」を停止してください。
以下に、このツールの導入手順を簡単に説明します。
❶「Tampermonkey」を導入します
使用しているブラウザに拡張機能「Tampermonkey」を導入する事が必要です。 以下のページに簡単な導入の説明があるので参照ください。
❷「Tampermonkey」にスクリプトを登録します
●「Tampermonkey」の「+」マークの「新規スクリプト」タブを開きます。
●「新規スクリプト」には、最初からテンプレートが記入されています。 これは全て削除して、完全に空白の編集枠に 下のコードをコピー&ペーストします。
〔コピー方法〕 軽量シンプルなツール「PreBox Button 」を使うと
コード枠内を「Ctrl+左Click」➔「Copy code 」を「左Click」
の操作で、掲載コードのコピーが可能になります。
● 最後に「ファイル」メニューの「保存」を押すと、ツールが使用可能になります。
〔 Simple SVG 〕 ver. 0.6
// ==UserScript== // @name Simple SVG // @namespace http://tampermonkey.net/ // @version 0.6 // @description 任意のブラウザ画面でpathコードを簡略化 // @author Ameba Blog User // @match https://*/* // @match file:///* // @grant none // ==/UserScript== let style= 'body { background: #000; } '+ '#base_board { position: fixed; top: 0; left: 0; width: 100vw; height: 100vh; '+ 'z-index: 20000; background: #000; } '+ '#work_contena { position: fixed; top: 0; width: 100%; } '+ '#work_contena #help { font: normal 16px Meiryo; padding: 4px 50px 2px; '+ 'margin: 20px -10px 30px; color: #fff; background: #5a7582; } '+ '#work_contena #panel { display: flex; color: #fff; padding-left: 40px; } '+ '#work_contena #help b, #work_contena #panel b { display: inline-block; height: 22px; '+ 'padding: 0 6px; color: #000; background: #97d4f3; border-radius: 3px; } '+ '#work_contena .p_atter, #work_contena .p_style, #work_contena .p_title, '+ '#work_contena .p_count { font: normal 16px Meiryo; height: 24px; margin-right: 10px; '+ 'padding: 12px 0 10px 15px; border: 1px solid #fff; } '+ '#work_contena .p_atter { width: 140px; flex-shrink: 0; } '+ '#work_contena .p_style { width: 172px; flex-shrink: 0; } '+ '#work_contena .p_title { min-width: 250px; padding-right: 15px; '+ 'white-space: nowrap; overflow: hidden; } '+ '#work_contena .p_count { width: 185px; flex-shrink: 0; } '+ '#work_contena .or_panel, #work_contena .n_panel { font: normal 16px Meiryo; '+ 'position: absolute; top: 172px; color: #fff; } '+ '#work_contena .or_panel { left: 8vw; } '+ '#work_contena .n_panel { left: 52vw; } '+ '#work_contena .or, #work_contena .n { position: absolute; top: 200px; width: 40vw; '+ 'height: 40vw; overflow: hidden; background: #fff; } '+ '#work_contena .or { left: 8vw; } '+ '#work_contena .n { left: 52vw; } '+ '#work_contena .file_input { display: none; }'; let control= '<div id="base_board">'+ '<div id="work_contena">'+ '<div id="help"><b>F8</b> SVG画像ファイルの読込み <b>F9</b> 画像の拡大表示</div>'+ '<div id="panel">'+ '<div class="p_count"> </div>'+ '<div class="p_atter"> </div>'+ '<div class="p_style"> </div>'+ '<div class="p_title"> </div></div>'+ '<div class="or_panel"> デフォルト画像</div>'+ '<div class="n_panel"> 処理適用画像</div>'+ '<div class="or"></div>'+ '<div class="n"></div>'+ '<input class="file_input" type="file">'+ '<style>' + style +'</style></div></div>'; document.body.insertAdjacentHTML('beforeend', control); let f_mode; // 四捨五入の処理形式 let attr_mode=1; // 属性整理の有無 let style_mode=0; // スタイル属性削除の有無 let dot2; // 💢 let dot3; // 💢 let mag=0; // 拡大指定の有無 catch_key(); function catch_key(){ disp_reset(); window.addEventListener('keydown', check_key); function check_key(event){ if(event.keyCode==112){ // ショートカット「F1」 event.preventDefault(); event.stopImmediatePropagation(); f_mode=1; svg_arrange(); disp_result(); count_c(); } // 💢 if(event.keyCode==113){ // ショートカット「F2」 event.preventDefault(); event.stopImmediatePropagation(); f_mode=2; svg_arrange(); disp_result(); count_c(); } // 💢 if(event.keyCode==114){ // ショートカット「F3」 event.preventDefault(); event.stopImmediatePropagation(); f_mode=3; svg_arrange(); disp_result(); count_c(); } // 💢 if(event.keyCode==115){ // ショートカット「F4」 event.preventDefault(); event.stopImmediatePropagation(); f_mode=4; svg_arrange(); disp_result(); count_c(); } // 💢 if(event.keyCode==116){ // ショートカット「F5」 リロード抑止 event.preventDefault(); event.stopImmediatePropagation(); } if(event.keyCode==117){ // ショートカット「F6」 event.preventDefault(); event.stopImmediatePropagation(); if(attr_mode==0){ attr_mode=1; if(f_mode==9){ f_mode=4; } svg_arrange(); disp_attr_mode(); count_c(); } else{ attr_mode=0; if(f_mode==9){ f_mode=4; } svg_arrange(); disp_attr_mode(); count_c(); }} if(event.keyCode==118){ // ショートカット「F7」 event.preventDefault(); event.stopImmediatePropagation(); if(style_mode==0){ style_mode=1; if(f_mode==9){ f_mode=4; } svg_arrange(); disp_style_mode(); count_c(); } else{ style_mode=0; if(f_mode==9){ f_mode=4; } svg_arrange(); disp_style_mode(); count_c(); }} if(event.keyCode==119){ // ショートカット「F8」 event.preventDefault(); event.stopImmediatePropagation(); file_read(); } if(event.keyCode==120){ // ショートカット「F9」 event.preventDefault(); event.stopImmediatePropagation(); if(mag==0){ mag=1; mag_fit(); center(); } else{ mag=0; mag_fit_off(); center(); }} }} // catch_key() function mag_fit(){ let style= '<style class="mag">#work_contena svg { width: 400px; height: auto; }</style>'; document.body.insertAdjacentHTML('beforeend', style); } function mag_fit_off(){ if(document.querySelector('.mag')){ document.querySelector('.mag').remove(); }} function center(){ let svg_img=document.querySelector('#work_contena .or svg'); let svg_width=svg_img.getBoundingClientRect().width; let svg_height=svg_img.getBoundingClientRect().height; let style= '<style class="mag_">'+ '#work_contena svg { margin-left: calc(20vw - '+ svg_width/2 +'px); '+ 'margin-top: calc(20vw - '+ svg_height/2 +'px); }</style>'; if(document.querySelector('.mag_')){ document.querySelector('.mag_').remove(); } document.body.insertAdjacentHTML('beforeend', style); } function set_svg(img){ let view_div=document.querySelector('#work_contena .or'); if(view_div){ view_div.innerHTML=img; let svg_img=document.querySelector('#work_contena .or svg'); viewbox_add(svg_img); }} // viewBox属性を追加 function file_read(){ disp_reset(); let svg_n=document.querySelector('#work_contena .n svg'); if(svg_n){ svg_n.remove(); } // 前処理のsvgを削除 let p_count=document.querySelector('#work_contena .p_count'); if(p_count){ p_count.textContent=' '; } // 前処理の文字数カウントをリセット let file_input=document.querySelector('#work_contena .file_input'); if(file_input){ file_input.click(); file_input.addEventListener('change' , function(){ if(!(file_input.value)) return; // ファイルが選択されない場合 let file_list=file_input.files; if(!file_list) return; // ファイルリストが選択されない場合 let file=file_list[0]; if(!file) return; // ファイルが無い場合 if(file.name.split('.').pop()=='svg'){ // ファイル名の確認 let file_reader=new FileReader(); file_reader.readAsText(file); file_reader.onload=function(){ let data_in=file_reader.result; set_svg(data_in); center(); }} else{ alert( "❌ 扱えるファイルではありません\n"+ " 扱えるファイルは「.svg」ファイルです"); } }); }} function svg_arrange(){ dot2=0; // 💢 dot3=0; // 💢 let svg_or; let path_or; let svg_n svg_or=document.querySelector('#work_contena .or svg'); // 処理元のSVG let n=document.querySelector('#work_contena .n'); if(svg_or && n){ // 元のSVG画像が無いと動作しない if(n.querySelector('svg')){ n.querySelector('svg').remove(); } svg_n=svg_or.cloneNode(true); n.appendChild(svg_n); if(attr_mode==1){ beautify(svg_n); } // 属性処理を実行 if(style_mode==1){ style_off(svg_n); } // スタイル属性削除を実行 path_or=svg_n.querySelectorAll('path'); // 処理元のSVGの全てのpath for(let k=0; k<path_or.length; k++){ let d_or=path_or[k].getAttribute("d"); path_or[k].setAttribute("d", path_trim(d_or)); } function path_trim(D_or){ let d_all=[]; if(select_type(D_or)){ // カンマ区切りの処理 d_all=D_or.split(','); } else{ d_all=D_or.split(' '); } if(f_mode<5){ let temp_d; temp_d=d_all.join(' '); if(f_mode==4){ return temp_d; } // 数値処理なしで出力 else{ return arrange_i_code(temp_d); }} function arrange_i_code(Temp_d){ // path全体の処理 let c_count=0; // 数として処理した文字数 let i_code=[]; // 全pathを1文字ずつにした配列 let i_code_new=[]; // 処理後の数と文字を格納する配列 i_code=Temp_d.split(''); // pathの全体を文字1個ずつに分解 for(let k=0; k<i_code.length; k++){ if(i_code[k].match(/[A-Za-z]| |-/g)){ if(c_count==0){ i_code_new.push(i_code[k]); } else{ i_code_new.push(arr_i_code(c_count, k)); c_count=0; }} else{ // 数字または「.」の場合 c_count+=1; }} return i_code_new.join(''); // 処理後のpath function arr_i_code(Count, K){ let tmp=[]; let tmp_num; // 再構成した数値(小数を含む) let tmp_round; // 四捨五入した数値 for(let i=0; i<Count; i++){ tmp.push(i_code[K-Count+i]); } if(count_dot(tmp)<2){ //「.」が1個以内 if(count_dot(tmp)==1){ tmp_num=parseFloat(tmp.join('')); if(f_mode==2 || f_mode==3){ if(tmp_num>=10 && f_mode==2){ // 🌐 tmp_round=Math.round(tmp_num); } else{ tmp_round=(Math.round(tmp_num*10))/10; }} else{ tmp_round=Math.round(tmp_num); }} else if(count_dot(tmp)==0){ tmp_round=parseInt(tmp.join('')); } return tmp_round.toString()+i_code[K]; } else if(count_dot(tmp)==2){ //「.」が2個 dot2+=1; // 💢 let sub_count=0; let sub_tmp1=[]; let sub_tmp2=[]; let tmp_num1; let tmp_num2; let r_tmp_num2; for(let i=0; i<tmp.length; i++){ if(sub_count==0){ sub_tmp1.push(tmp[i]); if(tmp[i]=="."){ sub_count=1; }} else if(sub_count==1){ if(tmp[i]!="."){ sub_tmp1.push(tmp[i]); } if(tmp[i]=="."){ sub_count=2; sub_tmp2.push(tmp[i]); }} else{ sub_tmp2.push(tmp[i]); }} tmp_num1=parseFloat(sub_tmp1.join('')); tmp_num2=parseFloat(sub_tmp2.join('')); r_tmp_num2=(Math.round(tmp_num2*10))/10; if(f_mode==2 || f_mode==3){ if(r_tmp_num2<1 && r_tmp_num2!=0){ if(tmp_num1>=10 && f_mode==2){ // 🌐 return (Math.round(tmp_num1)).toString()+ ' '+r_tmp_num2.toString().slice(1)+ i_code[K]; } else{ return ((Math.round(tmp_num1*10))/10).toString()+ ' '+r_tmp_num2.toString().slice(1)+ i_code[K]; }} else if(r_tmp_num2==0){ if(tmp_num1>=10 && f_mode==2){ // 🌐 return (Math.round(tmp_num1)).toString()+ ' 0'+i_code[K]; } else{ return ((Math.round(tmp_num1*10))/10).toString()+ ' 0'+i_code[K]; }} else{ if(tmp_num1>10 && f_mode==2){ // 🌐 return (Math.round(tmp_num1)).toString()+ ' 1'+i_code[K]; } else{ return ((Math.round(tmp_num1*10))/10).toString()+ ' 1'+i_code[K]; }}} else{ if(r_tmp_num2<1 && r_tmp_num2!=0){ return (Math.round(tmp_num1)).toString()+ ' '+r_tmp_num2.toString().slice(1)+ i_code[K]; } else if(r_tmp_num2==0){ return (Math.round(tmp_num1)).toString()+ ' 0'+i_code[K]; } else{ return (Math.round(tmp_num1)).toString()+ ' 1'+i_code[K]; }}} // 桁上がりして1.0になる else{ //「.」が3個以上 dot3+=1; // 💢 return tmp.join('')+i_code[K]; } function count_dot(arr){ let count=0; arr.forEach(function(item){ if(item=='.'){ count+=1; }}); return count; } } // arr_i_code } // arrange_i_code() } // path_trim }} // svg_arrange() function select_type(DD_or){ let regex=/,/; return regex.test(DD_or); } // カンマ有り true / 無し false function beautify(Svg){ Svg.removeAttribute("xmlns"); // xmlns属性を削除 Svg.removeAttribute("xml:space"); // xml:space属性を削除 Svg.removeAttribute("xmlns:xlink"); // xmlns:xlink属性を削除 Svg.removeAttribute("version"); // version属性を削除 Svg.removeAttribute("id"); // version属性を削除 Svg.removeAttribute("class"); // version属性を削除 } // beautify() function style_off(Svg){ Svg.removeAttribute("style"); // style属性を削除 } // style_off() function viewbox_add(Svg){ let viewbox=Svg.getAttribute("viewBox"); let width=Svg.getAttribute("width"); let height=Svg.getAttribute("height"); if(!viewbox && height && width){ let value='0 0 '+ width +' '+ height; Svg.setAttribute("viewBox", value); } // viewBox属性を追加 Svg.removeAttribute("x"); // x値属性を削除 Svg.removeAttribute("y"); // x値属性を削除 } // viewbox_add() function count_c(){ let svg1_c=0; let svg2_c=0; let svg_all=document.querySelectorAll('#work_contena svg'); if(svg_all[0]){ svg1_c=svg_all[0].outerHTML.length; } if(svg_all[1]){ svg2_c=svg_all[1].outerHTML.length; } let p_count=document.querySelector('#work_contena .p_count'); if(p_count){ p_count.textContent='文字数 '+svg1_c+' ▷ '+svg2_c; }} function disp_attr_mode(){ let p_atter=document.querySelector('#work_contena .p_atter'); if(p_atter){ if(attr_mode==1){ p_atter.innerHTML='<b>F6</b> 属性処理 ⚪'; } else{ p_atter.innerHTML='<b>F6</b> 属性処理 ➖'; }}} function disp_style_mode(){ let p_style=document.querySelector('#work_contena .p_style'); if(p_style){ if(style_mode==1){ p_style.innerHTML='<b>F7</b> スタイル削除 ⚪'; } else{ p_style.innerHTML='<b>F7</b> スタイル削除 ➖'; }}} function disp_result(){ let p_title=document.querySelector('#work_contena .p_title'); if(p_title){ let mode_text=' '; if(f_mode==1){ mode_text='<b>F1</b> S:通常の四捨五入 Dot2: '+dot2 +' Dot3: '+dot3; } else if(f_mode==2){ mode_text='<b>F2</b> M:10以下を小数第1位出力 Dot2: '+dot2 +' Dot3: '+dot3; } else if(f_mode==3){ mode_text='<b>F3</b> L:全数を小数第1位出力 Dot2: '+dot2 +' Dot3: '+dot3; } else if(f_mode==4){ mode_text='<b>F4</b> オリジナルのSVG画像 '; } else if(f_mode==9){ mode_text='<b>F1</b> S <b>F2</b> M <b>F3</b> L <b>F4</b> デフォルト'; } p_title.innerHTML=mode_text; } } // disp_result() function disp_reset(){ attr_mode=0; disp_attr_mode(); style_mode=0; disp_style_mode(); f_mode=9; disp_result(); }
「Simple SVG」最新版について
旧いバージョンの JavaScriptツールは、アメーバのページ構成の変更で動作しない場合があり、導入する場合は最新バージョンをお勧めします。
●「Simple SVG」の最新バージョンへのリンクは、以下のページのリンクリストから探せます。