CoffeeScriptとJSフレームワークによるHTML5 Canvasアプリ

諸般の事情により、今までは少し遠目で見ていたCoffeeScriptに踏み込んでみることにしました。

とりあえず慣れとか開発のノウハウをためるのに、行ったことをメモ的に書いていこうかなと。

目標

簡単にWebで使えるマインドマップツールを作る

やりたいことに近いというのと、あると便利そう、というのが理由です。

開発環境の準備

プログラマーズ雑記帳さんのこのページを参考に、Windows環境でNode.jsとCoffeeScriptをインストール。

あとはエディタとして使っているSublime Text2にCoffeeScript用のプラグインをインストールしました。

ビルド環境を整える

今回はGradleは使うまでもない、というか適用しにくいので、自分でプロジェクトのレイアウトやCakefileを定義していきます。

プロジェクトの構成は、下のようにしました。

  Cakefile
  index.html
  coffee/
    main.coffee
    controllers/
      xxx.coffee
    models/
      yyy.coffee
    views/
      zzz.coffee
  js/
  css/
  • ビルドスクリプト(Cakefile)はプロジェクトの直下におくのが最近の好みなので、そうしてます。ビルドするとjsフォルダの下にまたjsフォルダが作られたり、といった失敗もありましたが、coffee実行時のオプションの指定を見直したりして、うまくいくようになりました。
  • 慣れるまではフレームワークを使わずに、色々と試してみます。野良MVCですが、coffee/の下はcontrollers, models, viewsとして分けてみました。
  • 慣れてきたらKnockout.jsかSpine.jsあたりを使ってみようと思います。

ビルドはプロンプトから「cake build」で実行します。「cake clean」で生成したjsを削除します。とりあえずタスクはこれだけ。

coffeeコマンド実行時にw(watch)オプションをつけておくことで、coffee/以下のファイルに変更があった場合に自動でjsが生成されます。後は、随時index.htmlをブラウザ(Chrome)で開いて確認します。今のところ、自分の環境では再ビルドがまったく気づかないレベルの時間で終えてくれるのでいい感じです。

諸々試しながら作ってみたのが、今のところ下のような画面です。

色々実験していたら、マインドマップでなくペイントツールになってしまっているという…。

  • 座標モードがグリッドの場合、Canvasがグリッド状になり、○や△などのオブジェクトは、グリッドの中心にしか置けないようになります。ベースのJavascript無職のプログラミングさんを参考にさせていただきました。
  • ペイント時の軌跡の描画は、なめらかになるように、マウスが押されている間に記録された座標をlineTo(), stroke()でつないでます。これくらいだとまだ重さは問題にならないみたいですね。
  • 今はマウスイベントだけですが、Canvas内のマウスイベントを状態によってうまくコントロールしなければならないので、昔ちょっとだけ遊んだゲームプログラミングのシーン制御みたいな感じで枠組みを作りました。

作ってる時に知ったメモなど

ユーティリティ関数を使う

グローバル関数っぽいことを書きたい。

square = (x) ->
    return x * x

これをそのままutil.coffeeみたいに保存します。そうすると、どのクラスからでも、square(2)のように呼びだせます。

coffeeファイルを複数に分けて別のcoffeeファイルのクラスや関数にアクセスする

coffee実行時のj(join)オプションのおかげで特に何も気にしなくていいみたいです。コンパイル対象の.coffeeファイルはreaddirSyncでディレクトリを指定してかき集めているだけなので、順番も適当ですが、問題なさそう。

メソッド引数

デフォルト引数、可変引数。

foo: (options={}) ->

bar: (num, options...) ->

次のように書くだけでインスタンス変数に引数のプロパティがセットされるのは便利だと思いました。

baz: (@a, @b) ->
条件が長くなる場合に複数行に分ける

次のような関数(ゲームプログラミングでありがちな矩形を使った当たり判定です)の条件式(ifの中)を複数行に分ける場合…

# OK
isHit: (other) ->
    if (@x - @w < other.x + @w) and (@x + @w > other.x - @w) and (@y - @h < other.y + @h) and (@y + @h > other.y - @h)
        return true
    else
        return false

CoffeeScriptではPythonと同じくインデントでブロックが表現されるため、下のような書き方はNG

# NG!
isHit: (other) ->
    if ((@x - @w < other.x + @w) and
        (@x + @w > other.x - @w) and
        (@y - @h < other.y + @h) and
        (@y + @h > other.y - @h))
        return true
    else
        return false

下のようにするとOK

# OK
isHit: (other) ->
    if (@x - @w < other.x + @w) and
    (@x + @w > other.x - @w) and
    (@y - @h < other.y + @h) and
    (@y + @h > other.y - @h)
        return true
    else
        return false

これもOK(こちらの方が、すべての判定が揃うので個人的には好きです)

# OK
isHit: (other) ->
    if \
    (@x - @w < other.x + @w) and
    (@x + @w > other.x - @w) and
    (@y - @h < other.y + @h) and
    (@y + @h > other.y - @h)
        return true
    else
        return false
サブクラスの名前を式展開した文字列で表示する
class Parent
    constructor: () ->

    toString: () ->
        @constructor.name

class Child extends Parent
    constructor: () ->

foo = new Child()
console log "***#{foo}***" # --> ***Child***

リポジトリ

https://github.com/tq-jappy/mind-salver

※ 目標がマインドマップなので、MindCanvasみたいな名前を考えましたが、他とかぶるため却下。というわけで、コーヒーから連想されるSalverを使ってMindSalverとしました。まだ全然マインドマップになってないですが…。

感想

はじめてCoffeeScriptを知った時と比べて、今はネットの情報も大分充実してきているので、文法やライブラリなんかは学習しやすいですね。

ただ、実際に実行した時にundefinedエラーが起きたりなんかすると、デバッグとしてコンパイル後のjsファイルの該当行とcoffeeファイルを照合したりする必要があるので、ソースが多くなってくる(生成後のjsファイルが長くなる)場合、大変になりそう。