CanvasアニメーションをHerokuで公開しようよ!
もしあなたが暇で暇でしようがなくて、一日中時計をぼーっと眺めるのも悪くない、と考えているのなら、次のリンクをクリックしてください。3分くらいならあなたの時間をつぶせるかもしれません。
もしあなたがRubyを使っていて、JavaScriptのことはよく知らないけれども、HTML5のCanvasに興味がでてきて、その成果物をネットで簡単に公開できればうれしいかも、と考えているのなら、以下の記事を読む価値があるかもしれません。もちろん何の保証もありませんが..
Canvasを使ったWebアプリケーションの構築
この記事は先のリンクで示した、接近する時計のWebアプリケーションを構築する手順を書いています。HTMLはhamlとscssを使って、JavaScriptはjQueryを使って記述しています。WebフレームワークSinatraを使ってHerokuにデプロイしています。OSはMac OSX Tiger..です
ディレクトリ構成
最終的なファイル構成は以下のようになります。
.
├── Gemfile
├── Gemfile.lock
├── clock.rb
├── config.ru
├── public
│ └── javascripts
│ └── clock.js
└── views
├── index.haml
├── layout.haml
└── style.scss
Sinatraのためにclock.rb layout.haml index.haml style.scssが必要になります。時計を描画するJavaScriptはclock.jsに記述します。Herokuにデプロイするために更にconfig.ru Gemfileが必要になります。Gemfile.lockはbundler installコマンドで自動生成されます。
以下では一つずつファイルを用意する必要がありますが、僕のような無精者のために、Sinatra版scaffold ease_sinatra.rbを用意しました。
カレントディレクトリでWebApp::ease_sinatraすれば、Sinatraのテンプレートファイルが得られます。かなりいい加減な作りであることをご了承下さい..
clock.rb
まずWebフレームワークのコントローラとなるclock.rbを書きます。
require 'sinatra'
require 'haml'
require 'sass'
configure do
APP_TITLE = "Approaching Clock"
CREDIT = ['hp12c', "http://d.hatena.ne.jp/keyesberry"]
end
get '/' do
haml :index
end
get '/style.css' do
scss :style
end
configureブロックはアプリ立ち上げ時に一度だけ呼ばれます。get ‘/’でルートが呼ばれた(GETされた)ときの挙動を記述します。ここでは、hamlで記述されたviews/index.hamlが返されるよう指定しています。get ‘/style.css’でlink属性でstyle.cssが呼ばれたときに、scssで記述されたvews/style.scssが返されるよう指定しています。
layout.haml
次にlayout.hamlを記述します。Sinatraではlayoutという名のテンプレートが存在する場合、各テンプレートの読み出しに先立ってそれが自動で読み出されます。
!!! 5
%html
%head
%meta{:'http-equiv' => 'Content-type', :content => 'text/html', :charset => 'utf-8'}
%title= APP_TITLE
%link{:rel => 'stylesheet', :href => '/style.css', :type => 'text/css'}
%script{:type => "text/javascript", :src => "https://ajax.googleapis.com/ajax/libs/jquery/1.5.0/jquery.min.js", :charset => "utf-8" }
%script{:type => "text/javascript", :src => "/javascripts/clock.js", :charset => "utf-8" }
%body
= yield
titleタグに先ほどのconfigureで定義したAPP_TITLEを指定します。hamlでは=(イコール)以降をRubyのコードとして評価します。jQueryはGoogleが提供するものを使っています。時計を記述するclock.jsを指定します。bodyタグの中身はyieldで実体ファイル(index.haml)に委ねます。
index.haml
次にindex.hamlを記述します。
%header
#main
%canvas#clock{:width => '1000px', :height => '500px'}Only for HTML5 adapted browsers
%footer
%a{:href => CREDIT[1]}= CREDIT[0]
mainのcanvasタグにclockというid名を付けサイズを指定します。HTML5非対応ブラウザのためのメッセージを記述します。footerタグにCREDITのリンクを貼ります。
style.scss
次にstyle.scssを記述します。scssはsassy css(sassライクなcss)を意味するcssの拡張言語です。scssを使用することによりcssの文法に沿って、sassの拡張を取り入れることができます。
$font_color: #D0FFD0;
$bg_color: #325F82;
$canvas_color: #FFFFFF;
@mixin rounded($topl:32px, $topr:32px, $btmr:32px, $btml:32px) {
border-radius: $topl $topr $btmr $btml;
-moz-border-radius: $topl $topr $btmr $btml;
}
* {
margin: 0;
padding: 0;
font-family: Trebuchet ms, Verdana, Myriad Web, Syntax, sans-serif
}
body {
color: $font_color;
background-color: $bg_color;
width: 1000px;
margin: 60px auto;
}
header {
display:block;
}
#main {
canvas#clock {
border: thin solid #444;
background-color: $canvas_color;
@include rounded();
}
}
footer {
display: block;
height: 30px;
text-align: center;
margin-top: 20px;
a {
text-decoration: none;
color: $font_color;
&:visited {
color: $font_color;
}
}
}
$varnameでグローバル変数を定義できます。セレクタをネストできます。@mixin-@includeでセレクタブロックを関数ライクに使えます。ここではrounded()で角丸にミックスインを使っています。
clock.js
メインとなるclock.jsを記述します。JavaScript初学者なので書き方に問題があるかもしれません。間違いをご指摘頂けるとうれしいです。少し長いので分けて説明します。
var canvas = {};
$(document).ready(function(){
canvas.c = $("canvas#clock");
canvas.ctx = canvas.c[0].getContext('2d');
canvas.width = canvas.c.width();
canvas.height = canvas.c.height();
canvas.ctx.translate(canvas.width/2, canvas.height/2);
const min = 60;
var x = min;
var sp = 2;
clock(x);
setInterval(
function(){
clock(x);
x += sp;
if (x > canvas.width*0.6 || x < min) { sp = -sp };
},
500
);
})
グローバルに参照できるcanvasオブジェクトを定義します。$(document).ready..はHTMLドキュメントの読み込みの完了を待って、その引数の関数が実行されることを保証します。そのなかで最初にcanvasオブジェクトにcanvasの情報をオブジェクト・プロパティとしてセットします。JavaScriptではオブジェクト・プロパティは先行する宣言が不要です。
canvasへの描画はcanvasオブジェクトの2Dコンテキストに対し行います。そのためgetContext(‘2d’)しますが、jQueryではcanvasオブジェクトはArrayを返すので注意が必要です。
時計の描画は中心点を基準に行うほうがやり易いので、ctx.translateで座標軸をcanvasの中心に移動します。
Canvasにおけるアニメーションの描画にはsetIntervalを使います。setIntervalは第2引数に指定した周期で第1引数に渡した関数を関数の実行スタックに繰り返し登録します。setIntervalの第1引数にはclock関数を包んだ匿名関数を渡します。時計を描画するclock関数はそのサイズxを引数にとります。サイズxは匿名関数が呼ばれる度にspだけ増分されてclockに渡されるので、呼び出しの度に時計のサイズは大きくなっていきます。サイズxが任意の値を超えると(ここではcanvas.width*0.6)、時計は今度は徐々に小さくなっていきます。
続いてclock関数の中身を見ていきます。
function clock (radius) {
canvas.ctx.clearRect(-canvas.width/2,-canvas.height/2,canvas.width,canvas.height);
var unit = radius/75;
drawFrame(radius, '#325FA2');
canvas.ctx.save();
canvas.ctx.rotate(-Math.PI/2); //set start angle at twelve o'clock
drawHand('hr', radius*0.5, unit*3, unit*5, 'black');
drawHand('min', radius*0.9, unit*2, unit*10, 'black');
drawHand('sec', radius*0.9, unit, unit*5, 'red');
canvas.ctx.restore();
}
ctx.clearRectでキャンバスをクリアします。drawFrame関数で時計の文字盤を描画し、drawHand関数で針を描画します。針の描画はctx.rotateでキャンバスの座標系を回転させながら行うので、最初に初期位置を12時の位置に合わせています。ctx.save() ctx.restore()は動かした座標系を元に戻すために使います2。saveでそれ以前の状態を保存し座標系を動かして描画を行った後、restoreで元に戻します。各描画サイズはclockに渡されるサイズxに対する比で規定することによって、時計のサイズが変わってもそのバランスが崩れないようにします。
次にdrawFrame関数の中身をみます。
function drawFrame (radius, color) {
drawCircle(radius, radius*0.1, color, false);
drawPitchLines(radius*0.9, 2, 1);
drawNumbers(radius/5, radius*0.7, color);
}
function drawCircle (distance, width, color, filly) {
var ctx = canvas.ctx;
ctx.beginPath();
ctx.lineWidth = width;
ctx.strokeStyle = color;
ctx.arc(0, 0, distance, 0, 2*Math.PI, true);
filly ? ctx.fill() : ctx.stroke();
}
function drawPitchLines (distance, length, width) {
var ctx = canvas.ctx;
ctx.save();
for (var i=0; i < 60; i++) {
ctx.beginPath();
ctx.strokeStyle = "black";
ctx.lineWidth = width;
ctx.lineCap = 'round';
var len = i%5==0 ? length*3 : length;
ctx.moveTo(0, -distance);
ctx.lineTo(0, -(distance-len));
ctx.stroke();
ctx.rotate(2*Math.PI/60);
};
ctx.restore();
}
function drawNumbers (size, distance, color) {
var ctx = canvas.ctx;
ctx.font = size + "px Helvetica";
ctx.fillStyle = color;
for (var i=1; i <= 12; i++) {
ctx.save();
ctx.translate(distance*Math.sin(Math.PI/6*i), -distance*Math.cos(Math.PI/6*i));
ctx.fillText(i, -size/3, size/3);
ctx.restore();
};
}
drawFrame関数では外円とインデックスバーと数字を描画するdrawCircle drawPitchLines drawNumbersを呼びます。線の描画はbeginPathで開始宣言し、moveToで開始点lineToで終了点を決めて、strokeで実際に描画します。円はarcで描画します。引数には中心座標 半径 描画角始点終点、および描画方向を指定します。数字の描画はfillTextで行います。
次に時計の針を描画するdrawHand関数をみます。
function drawHand (unit, length, width, offset, color) {
var now = new Date();
var hr = now.getHours(), min = now.getMinutes(), sec = now.getSeconds();
hr = hr >= 12 ? hr-12 : hr;
var _360 = 2*Math.PI;
var angle;
if (unit=='hr') {
angle = hr*_360/12 + min*_360/(12*60) + sec*_360/(12##60);
} else if (unit=='min') {
angle = min*_360/60 // + sec*_360/(60*60);
} else {
angle = sec*_360/60;
};
var ctx = canvas.ctx;
ctx.save();
ctx.strokeStyle = color;
ctx.lineWidth = width;
ctx.rotate(angle);
ctx.beginPath();
ctx.moveTo(-offset,0);
ctx.lineTo(length,0);
ctx.stroke();
ctx.restore();
}
Dateオブジェクトを使って現在の時・分・秒を取得します。針の種類によって一度に進む量angleが異なるので場合分けします。ここでは時針は時刻の進行で少しずつ移動しますが、分針は60秒ごとに一気に1つ進むようにしています。
clock.jsは以上です。
ローカルでの起動
これでローカルで実行する環境が整いました。早々立ち上げてみましょう。
/Users/keyes/aclock% ruby clock.rb
== Sinatra/1.1.2 has taken the stage on 4567 for development with backup from Thin
>> Thin web server (v1.2.7 codename No Hup)
>> Maximum connections set to 1024
>> Listening on 0.0.0.0:4567, CTRL+C to stop
Ruby1.9.2でshotgunを利用する場合、カレントパスをロードする必要があるかもしれません。
/Users/keyes/aclock% shotgun -I. clock.rb
== Shotgun/WEBrick on http://127.0.0.1:9393/
[2011-02-18 18:28:47] INFO WEBrick 1.3.1
[2011-02-18 18:28:47] INFO ruby 1.9.2 (2010-12-25) [i386-darwin8.11.1]
[2011-02-18 18:28:47] INFO WEBrick::HTTPServer#start: pid=1613 port=9393
Herokuへのデプロイ
http://localhost:4567で問題なくアプリケーションが起動したら、Herokuにデプロイするためにconfig.ruとGemfileを用意します。
config.ru
require "bundler"
Bundler.require
$LOAD_PATH << File.expand_path(File.dirname(__FILE__))
require 'clock'
run Sinatra::Application
Gemfile
source :rubygems
gem "sinatra"
gem "haml"
Herokuに必要なgemsをインストールするために、BundlerというGem管理ツールを使います。Gemfileに必要なgemsを羅列し、config.ruではbundlerをrequireしてこれらを読み込むよう指定します。
Bundlerをインストールしてinstallコマンドを実行します。
/Users/keyes/aclock% gem install bundler
/Users/keyes/aclock% bundle install
これでGemfileに記述したgemsがアプリケーションで使えるようになります。同時にGemfile.lockが生成され、ローカルとHerokuで使われるgemsのバージョンの一致が保証されます3。
HerokuへのデプロイはgitとHeroku gemを使います。初回はSSHキーのセットアップなどが必要になりますが、説明は他サイトに譲ります4。
/Users/keyes/aclock% git init
/Users/keyes/aclock% git add .
/Users/keyes/aclock% git commit -m 'initial'
Heroku側にアプリケーションのレポジトリを用意し、git pushでデプロイします。
/Users/keyes/aclock% heroku create myclock
/Users/keyes/aclock% git push heroku master
早々アプリケーションを立ち上げましょう。
/Users/keyes/aclock% heroku open
うまくいかない場合はlogを見てみましょう。
/Users/keyes/aclock% heroku logs
さあ、あなたもCanvasを使ったサイトを立ち上げましょう!
enjoy your Canvas life!
ソースコードは以下にあります。 Approaching-Clock
blog comments powered by Disqus