No Regrets in Bathing

カレーを週に一度食っていく

Vue.js + SVG練習4 : クリックで音の鳴るピアノ鍵盤

SVGをグラフィックエディタでいじる方針のリベンジをしたかったので、今回は下記のようなピアノを作った。

f:id:hashrock:20171109024220g:plain

下記から動いているところを試せる(音が出ます)

hashrock-sandbox.github.io

前回Inkscapeで撃沈したあと、色々試した所、Method DrawというオンラインSVGエディタがだいぶ良さそうだった。

Method Draw

f:id:hashrock:20171109014506p:plain

上記のグラフィックをサクッと作り、出力した結果が下記。

<svg width="20" height="100" xmlns="http://www.w3.org/2000/svg">
 <!-- Created with Method Draw - http://github.com/duopixel/Method-Draw/ -->
 <defs>
  <linearGradient id="svg_4" x1="0.410156" y1="0.417969" x2="0.320313" y2="0.144531">
   <stop stop-color="#fff" offset="0"/>
   <stop stop-color="#b7b7b7" stop-opacity="0.996094" offset="1"/>
  </linearGradient>
  <linearGradient id="svg_10" x1="0.558594" y1="0.785156" x2="0.144531" y2="0">
   <stop stop-color="#666666" offset="0"/>
   <stop stop-color="#ffffff" stop-opacity="0.996094" offset="1"/>
  </linearGradient>
 </defs>
 <g>
  <title>background</title>
  <rect fill="#fff" id="canvas_background" height="18.666661" width="5.333332" y="-1" x="-1"/>
  <g display="none" id="canvasGrid">
   <rect fill="url(#gridpattern)" stroke-width="0" y="0" x="0" height="100%" width="100%" id="svg_2"/>
  </g>
 </g>
 <g>
  <title>Layer 1</title>
  <rect stroke="#000" id="svg_1" height="100" width="20" x="-0.333333" fill="url(#svg_4)"/>
  <rect fill="#000000" stroke="#000" stroke-opacity="null" width="12" height="60" id="svg_5"/>
  <rect fill="#666666" stroke-width="0" stroke-opacity="null" x="11" y="0.333334" width="1" height="59" id="svg_6" stroke="#000"/>
  <rect fill="url(#svg_10)" stroke-width="0" stroke-opacity="null" x="0.833333" y="56" width="10.5" height="3.666667" id="svg_8" stroke="#000"/>
 </g>
</svg>

Inkscapeから比べると遥かにクリーンで言うことなし。あの苦労なんだったの…

あとはlayerやbackgroundが不要だったりなんだりで、削除すると2/3くらいにはなる。

Method Drawはsvg-editのforkなわけだが、かなりUIに改善が入っている。ただAuthorが飽きたのかずっと放置されており、バグもごろごろ残っているので注意。

Method Drawの良いところは、数値での座標・幅・高さ指定が非常に楽で、またすべての座標を数値指定することで、座標が整数にきっちり揃ったSVGが手に入る。これは座標計算をJS側でやる際に都合が良い。

完成品のコードは以下。

github.com

ポイント

  • Method Draw、こまめに保存を心がけたほうがよい
  • 鍵盤の座標計算は案外面倒で苦労させられた
  • クリックした鍵盤への色つけは、SVG要素へのCSS filter適用でなんとかしようと思ってたけど、Chromeのバグなのか全く反映されない。仕方なくstrokeで対処。
  • 音が出る部分は、minrollの流用。

Vue.js + SVG練習3 : こっそり戻ろうとするスライダー

3作目。こっそり戻るスライダーを実装しよう。

f:id:hashrock:20171108000803g:plain

素のSVG手書きするのちょっと辛すぎたんでInkscapeを使うことにした。 Inkscapeも辛くないということはまったくないけど…、うーん。Sketchの方が楽かな。Mac版は持ってるんだけど。

f:id:hashrock:20171107223623p:plain

こんな感じ。

で、動かしたい単位でグループ化し、地道に名前を付けていく。

f:id:hashrock:20171107223852p:plain

groupの移動はx, yではなくtranslateでやることになる(めんどくさい!)ので、 この時、translateの値は、変位する値でメモしておく。

始点:translate(-0.28348214,0.09449405)
終点:translate(89.296875,0.09449405)

なるほど、この範囲を動かせばいいわけだ。

inkscapeの安定版はいつでもこんな感じ。

f:id:hashrock:20171107224243p:plain

まぁ使えるのでいいとする。 で、そのままSVGで出力すると、inkscape独自属性とかが山盛りで出力されてしまうので、プレーンSVGというので出す。 最適化SVGというのもあったけど、エラーになって出せなかった(そんなもんだ)

使う部分だけ切り出す。

f:id:hashrock:20171107224546p:plain

metadataとかは不要かな。defもいらない。xml定義周りもいいや。 svg要素以下を.vueファイルにペーストするとこんな感じに。

f:id:hashrock:20171107225033p:plain

こーいうところを

<g
  transform="translate(-0.28348214,0.09449405)"
  id="chara">

こうする。

<g
  :transform="charaPosition"
  id="chara">
  computed:{
    charaPosition(){
      const x = -0.28348214 + 89.296875 * this.val / 100
      return `translate(${x},0.09449405)`
    }
  }

これで、this.valが0-100の間を取ったときに動くようになる。

(追記)あとあと見てみると、ここの計算は間違っている。最終的にはここ消したんだけど

あとはドラッグ。面倒なものに着手してしまったことをだんだん後悔してきたぞ。

で、ここまでやったんですけど、yak shavingに遭遇したので、説明を放棄します。

  1. layer全体にtranslateがかかっていて、e.offsetX, e.offsetYの値がズレる
  2. 上記を解決しても、viewBoxの値が謎の値になっていて、e.offsetX, e.offsetYの値がズレる
  3. 上記を修正するために、viewBoxとsvgのwidth, heightの値をあわせ、オブジェクトをリサイズした所、移動にtranslateではなくmatrixが使われてしまう。リサイズしたらそうなるのか…座標計算で考えることが増えそう…という感じでどんどん不安になる

感想としては、見た目の辻褄があっていればOK、という感じのSVGが作られている。パーツ個々はともかく、UI全体をInkscapeから吐いたSVGで作るのはつらそう。

何かの機能を使わないように気をつけて作るとか、あるいは何らかの手段でSVGをclean up出来る、などの対処方法は存在するのかもしれない。Inkscapeマスター誰か…

完成品。

Slider.vue

github.com

ポイント

  • 自分でこの手のドラッグものを作るときは、下記のものを実装している。
    • startDrag, onDrag, stopDragの3種のイベント。onDragはグラブする要素に。startDragとstopDragはラッパー要素に付ける。
    • offsetとdragの2つの状態。offsetは「グラブ要素の掴んだ位置」で、今回はx座標のみにして手抜きしてるけど、通常x, y座標を持たせる。dragも今回は2値だけど、リサイズとかある場合には複数の状態を取れるようにする。
    • もはやtouch eventに付き合ってられないなと思うので躊躇なくpointer eventを使う。メジャーなライブラリは大抵touch eventとmouse eventの変換処理を地道にやってるのでこういう仕事の人はがんばるしかない
  • scaleとinvertの2種類の変換関数
    • 今回は雑に書いたけど、算数が苦手な自分のようなプログラマーはd3-scaleを利用するのが良いと思う。

疲れた、次はもっと簡単なやつにしよう。

ちょっとVue component試作したいときにpoiを使う

Vue.jsは「vue-cli」という便利なCLIツールがあって、下記のコマンドでvueファイルの単体起動をすることが出来た。

vue build MyComponent.vue

で、この機能なんだけど、いつの間にか削除されていたようだ。

f:id:hashrock:20171107213821p:plain

もともとegoistさんという方がvbuildというツールを作っていて、その機能がvue-cliに取り込まれたという経緯だったかと記憶してる(その辺曖昧なので間違ってるかも)。

で、poiの方はegoistさんのプロジェクトなので、vue-cliになんでも盛り込むのではなく、興味があってゴリゴリ開発する人に移譲するというのはいい方向だとは思う。poiはVue専用でもなくてReactとかにも対応している(ElectronやPWAなんてのもある。ただこのあたりはボイラープレート戦国時代みたいに群雄割拠しているので、自分も何を使ったらいいのか全くわからない)。

github.com

で、SVGでVueコンポーネント作るのに、webpack-simpleテンプレート使っていたけど、どうせwebpack.config.jsいじりたくはないし、poiを使ってスリムダウンすることにする。

移行前

f:id:hashrock:20171107214507p:plain

移行後

f:id:hashrock:20171107214545p:plain

webpack.config.jsと.babelrcが消えた。

babelやwebpackはpoiのデフォルト設定で稼働するので、設定が気に食わなければ、poi.config.jsや.babelrcの直置きで変更することになる。

デプロイも簡単で、poi buildするとdist/以下に下記のようにminifyしたファイルが吐かれる。

f:id:hashrock:20171107214734p:plain

なお、poi MyComponent.vueみたいなのは通らない。下記のindex.jsを用意してあげる必要がある。

index.js

import Vue from 'vue'
import App from './components/App.vue'

new Vue({
  el: '#app',
  render: h => h(App)
})

このくらいなら、別に用意するのは苦ではない。

あと、poiを使ってコンポーネント集作れそうなボイラープレートを作っておいた。 もし、まかり間違ってelement-uiのようなガッツリしたコンポーネント集を作る案件に遭遇してしまったら、使えるかもしれない。

github.com

Vue.js + SVG練習2 : Sparkline

f:id:hashrock:20171107020024p:plain

あまり体調がよくない(言い訳)ので、今日は軽め。

何の変哲もないsparkline。可愛くもなんともない。

Sparkline.vue

<template>
  <svg width="300" height="50">
    <polyline fill="none" stroke="#793" :points="points"></polyline>
  </svg>  
</template>

<script>
export default {
  data(){
    return {
      dat: []
    }
  },
  computed:{
    points(){
      return this.dat.map((item, i)=>{
        return `${i},${item}`
      }).join(" ")
    }
  },
  mounted(){
    //ランダムデータ
    for(let i =0; i < 300; i++){
      this.dat.push(
        (Math.random() + Math.random() + Math.random() + Math.random()) / 4 * 50
      )
    }
  }

}
</script>

ポイント

  • 乱数はいわゆる「コクのある乱数」
  • そもそもpolylineって要素があるのを初めて知った
  • Vueの場合だと、pointsにバインドして、半角スペースで座標列をjoinすればおわり
    • こういう時computedのありがたみを感じる
  • 何かインタラクション入れたかったけど、まぁ寝よう

考え

  • d3を始めとするチャートライブラリとVueを接続している例をよく見るけど、d3なんかはVueと機能がかぶっている(DOMのバインディングと生成)ところがあるので、そもそもVue単体でチャートを描くのは別に苦じゃないのではと思っている
  • けど、座標軸の描画とかズームとか、d3の蓄積と便利さ、抽象化の見事さは無視できない、そのあたりを勉強がてら真似てみるかなと思う

Vue.js + SVG練習1

Vue + SVGで色々UIを作っているんだけど、素振りが必要だと感じたので、変なものをこしらえて引き出しを増やしていこうと思う。

一作目。

f:id:hashrock:20171106030121g:plain

コードはグダグダですみません…

CuteButton.vue

<template>
  <svg width="400" height="80" @mousemove="over">
    <rect width="200" height="50" rx="8" ry="8" x="20" y="20">
    </rect>
    <text x="120" y="54" text-anchor="middle">I am Button!</text>
    <circle r="10" cx="100" cy="20" class="eye-outer"></circle>
    <circle r="10" cx="130" cy="20" class="eye-outer"></circle>
    <circle r="5" :cx="100 + dx" :cy="20 + dy" class="eye-inner"></circle>
    <circle r="5" :cx="130 + dx" :cy="20 + dy" class="eye-inner"></circle>
  </svg>
</template>
<script>
export default {
  data(){
    return {
      dx: 2,
      dy: 2
    }
  },
  methods: {
    over(ev){
      const cx = 100
      const cy = 20
      const dx = ev.offsetX - cx
      const dy = ev.offsetY - cy
      this.dx = dx / 400 * 4
      this.dy = dy / 80 * 4
      console.log(dx, dy )
    }
  }
}
</script>
<style scoped>
rect{
  fill: white;
  stroke: #793;
  stroke-width: 5px;
  cursor: pointer;
}
rect:hover{
  fill: #EFE;
}

.eye-outer{
  fill: white;
  stroke: #793;
  stroke-width: 5px;
}
text{
  cursor: pointer;
  pointer-events: none;
}

.eye-inner{
  fill: #793;
}
</style>

クリックした時に目がびっくりすると面白いかもしれないねぇ。 3回くらい続いたら、リポジトリにしてアップするけど、飽きるかどうか五分五分

ポイント

  • rectでhover時に背景色を変えたいのだが、textにhoverを奪われてしまう。そこでtext側にpointer-events: noneを設定するとスルー出来る。
  • 本当はレスポンシブにしたり、props経由でラベルを自由に設定したり出来るようにしたいが、寝る前で眠いと厳しい

プレーンテキストから4コママンガに変換出来るツールを作った

f:id:hashrock:20170628000458p:plain

作りました。

https://mangadown.netlify.app/

ここの所、Markdownからすべてを生成することを目指した「anydownプロジェクト」というのをやってます。

その流れで4コマもテキストから生成できそうだな、と思いついたので作りました。別にMarkdownじゃないんですが、それなりに書きやすいんじゃないかと思います。 大層なことにそれらしいWebフォントを使っていたり、縦書き頑張っていたりするので、白ハゲ漫画を作るだけじゃなくて、普通に4コマのテンプレート生成ツールとしても十分使えると思います。

白ハゲというのは一体何なのか説明すると、「Twitter上で白いキャラクターに持論を代弁させる」というのが何故か微妙に流行り、それを揶揄する用語として「白ハゲ」という言葉が生まれたようなのですが、若干ハイコンテキストです。 なので、白ハゲ4コマメーカーという名前にしていますが「Mangadown」が正式名称です。

※個人的には、Twitterはもともと万人が聞かれてもいないのに持論を展開して楽しむ所だと考えているので、別に白ハゲ漫画に対して特段の意見はありません。好きな表現方法を選ぶべきです。

※あえて言うなら、自分は色白の上に薄毛の兆候があるため、あまり白ハゲ白ハゲ言っていると自分自身が白ハゲになりそうです。

明日Node学園に遊びに行くので、そのためのネタ仕込みという側面もあります。

作っていて驚いたのは、Fabric.jsの高機能さです。canvas要素にアタッチするだけで、リサイズUIの付いた図形やら画像やらを放り込むことができます。更にisDrawingModeというフラグをtrueにするだけで、canvas上に手書きが出来て、書いた結果はオブジェクトになる。実際このツールの半分くらいの機能はFabric.js組み込み機能でまかなえている気がします。

さて、次は何をテキストから生成すべきでしょうかね。

P.S.

スタンプ募集中です。GitHubにIssueを立ててあります。

github.com

一緒にanydownやってくれる仲間も募集中です。

Markdownから日本企業っぽいメールに変換出来るツールを作った

DEMO Source

かなりバグが残っている(ひどいのは、インライン要素は全部消えてしまう)ので、胸をはれるような代物ではないけど(※追記:直しました)、Markdownから業務メール書式への変換ツールを書いた。

自分の考える業務メール書式とは、以下のようなもの:

  • 長文は適度に折り返される(半角76文字ぐらいで折り返されることが望ましいらしい)
  • 行を開けることでセクションが表現されている
  • 全角記号で飾られている

経緯としては、書類をなんでもかんでもMarkdownで書いているので、メールに貼り付けるときに手で整形する必要があってつらかったために作成した。

自分の作業のみ効率化すっかと思ったのだけれど、何故かこの手のツールに需要があるらしく、意外とみんな手で書式整えてるようだ。

自分はこれに加えて、段落のインデントまでやるんだけど、作ってる最中に「これ完全に自分しかやってないメールしぐさだ」と気づいて、今日からやめることにした。

あと単語ごとに折り返すやつとか、もはや無理やり実装したけど、これもやってるの自分だけなのではないかという気分がしてきた。

メールはもはや前世紀の遺物だと思っていて、大抵のことはイシュートラッカーの方が上手く情報を蓄積できるのだし、自動通知以外の用途に使うべきではないという認識だけど、それでもみんなが使っているから、ずっと使い続けなければならず辛いところ。

メールやめよう運動みたいなのが起きないかなぁと思っています。

おまけ

anydownという、Markdownライクな記法からなんでも生成してやるぞという一人プロジェクトをやっていて、

みたいな成果物があります。こちらも興味があれば見ていってください。 Excelやメールによるコストの高いコミュニケーションをいつか撲滅したい。

追記 2017/05/03

インラインに関するバグは直しました。わかっているバグはもう一つあって、ネストされたリストはレンダリング出来ません。 ASTの扱い自体があまりわかっていないのが原因で、今もなんとなく雰囲気で動いている感じです。 このツール自体も結局あまり使われないような気もしてきたので、何かない限りそのままです。