はじめましてこんにちは。
KRAYアルバイトの浅海です。
html5のcanvasを使ってお絵かき投稿サイトを作ってみようと思います。
初めてブログ記事を書くということで気合が入りました。
ちょっと長めですがお付き合い下さい。
機能
お絵かき投稿サイトの必要最低限な機能って?
ざっと下のような機能を入れてみます。
→ 完成見本はこちら完成見本の公開は終了致しました。
絵を描ける
HTML5のcanvasにマウスの軌道に線を引いていくわけです。
canvasでのマウス軌道の描き方は、
・mousemoveイベント発生時に点をプロット
という手段が真っ先に思い浮かぶと思いますが、これは、以下の様になります。
これではお話になりません。
なので、点ではなく線を引くことにします。
これは下のようになります。
この方法でもまだ、曲線の時に粗が目立ちます。
そこで、線と線の間に点をプロットする、今までの2つの方法を合わせて使います。
すると下のように、綺麗な線が描けます。
※canvasのプロパティに、lineJoinというものがありまして、これに’round’を入れておくとパスとパスの間を滑らかに連結してくれるようです。
しかし、考えるのが面倒だったため全ての線は個別に描画しました。
※11/21
.lineCapでいけそうです・・
controller
railsアプリを作成してからpictures controllerを作ります。
$ rails new canvas
$ cd canvas
$ rails generate controller
ルーティング
ルーティングを設定します。
pictures controllerのnew actionで絵を書くことにします。
# config/routes.rb
root :to => 'pictures#new'
erb
何の変哲もないcanvasタグを書きます。
CSSだけでwidthとheightの設定をすると調子が悪いので、
必ずcanvasタグ内でサイズ指定して下さい。
# app/views/pictures/new.html.erb
<canvas id="draw-area" width="300" height="300"></canvas>
coffee script
canvasのcontextを取得して、点を描画する機能と線を描画する機能を追加します。
// app/assets/javascripts/pictures.js.coffee
canvas = $('#draw-area')
ctx = canvas[0].getContext('2d')
ctx.lineWidth = 1
ctx.putPoint = (x, y)-> # x,yに点を描画
@.beginPath()
@.arc(x, y, @.lineWidth / 2.0, 0, Math.PI*2, false)
@.fill()
@.closePath()
ctx.drawLine = (sx, sy, ex, ey)-> # 始点(sx, sy) から 終点(ex, ey)に線を描画
@.beginPath()
@.moveTo(sx, sy)
@.lineTo(ex, ey)
@.stroke()
@.closePath()
そして、canvasをクリックしたときに点を描画します。
mousedownというフラグ変数を用意して、マウスを押している状態か判定しています。
// app/assets/javascripts/pictures.js.coffee
mousedown = false
canvas.mousedown (e)->
ctx.prevPos = getPointPosition(e)
mousedown = true
ctx.putPoint(ctx.prevPos.x, ctx.prevPos.y)
マウスが動いている間は点と点を線で繋ぎます。
始点は一つ前のイベント発生時の座標、
終点は現在のイベント発生時の座標です。
// app/assets/javascripts/pictures.js.coffee
canvas.mousemove (e)->
return unless mousedown
nowPos = getPointPosition(e)
ctx.drawLine(ctx.prevPos.x, ctx.prevPos.y, nowPos.x, nowPos.y)
ctx.putPoint(nowPos.x, nowPos.y)
ctx.prevPos = nowPos
マウスの押下をやめた時と、canvasからポインタが出た時に描画を終了します。
// app/assets/javascripts/pictures.js.coffee
canvas.mouseup (e)->
mousedown = false
canvas.mouseout (e)->
mousedown = false
絵を消せる
キャンバス上の絵を全消去できるようにします。
erb
消去のボタンを作成します。
CSSのデザイン設定等は省略します。
<span id="clear-button">消去</span>
coffee script
#clear-buttonが押された時、context.clearRectでcanvas全体を指定して消去します。
// app/assets/javascripts/pictures.js.coffee
$("#clear-button").click ->
ctx.clearRect(0, 0, canvas.width(), canvas.height())
描画を一回分戻れる
人間は失敗するものです。
描画を一回分戻れるようにしてあげます。
erb
戻すボタンを作成します。
<span id="return-button" class="controll-button">戻す</span>
coffee script
contextに現在の状態を保存する機能を追加します。
getImageDataを使えば指定領域の描画情報を取得することができます。
// app/assets/javascripts/pictures.js.coffee
ctx.savePrevData = ->
@.prevImageData = @.getImageData(0, 0, canvas.width(), canvas.height())
canvas.mousedownに描画前の状態を保存する以下の一行追加します。
// app/assets/javascripts/pictures.js.coffee
canvas.mousedown (e)->
ctx.savePrevData() # この一行を追加
ctx.prevPos = getPointPosition(e)
mousedown = true
ctx.putPoint(ctx.prevPos.x, ctx.prevPos.y)
ボタンを押したとき、contextのputImageDataを使って、保存してあったprevImageDataをcanvasに上書きします。
// app/assets/javascripts/pictures.js.coffee
$("#return-button").click ->
ctx.putImageData(ctx.prevImageData, 0, 0)
これで描画をやり直せます。
思う存分間違って大丈夫です。
線の太さを変えられる
線の太さは現在1pxの固定になっています。
これではつまらない!
これをHTML5のスライダーを使って変更できるようにします。
erb
HTML5のスライダーは、Railsのrange_field_tagヘルパーがあるので使います。
初期値1、最小値1、最大値20で作成しました。
その下には、現在の太さを表示するための領域を用意しました。
<%= range_field_tag 'pen-width-slider', 1, :min => 1, :max => 20 %>
<span id="show-pen-width">1</span>px
coffee script
スライダーの操作はchangeイベントで捕捉できます。
// app/assets/javascripts/pictures.js.coffee
$("#pen-width-slider").change ->
ctx.lineWidth = $(@).val()
$("#show-pen-width").text(ctx.lineWidth)
これで線の太さを変えられるようになりました。
色を変えられる
「黒一色じゃつまらないです。いろんな色で書きたいです。」
「やりましょう。」
erb
R,G,Bそれぞれのスライダーで実装します。
#preview-colorという、色をプレビューするボックスも用意しまいした。
<%= range_field_tag 'pen-color-red-slider', 0, :min => 0, :max => 255 %>
<span class="red">R</span> <span id="show-pen-red">0</span><br />
<%= range_field_tag 'pen-color-green-slider', 0, :min => 0, :max => 255 %>
<span class="green">G</span> <span id="show-pen-green">0</span><br />
<%= range_field_tag 'pen-color-blue-slider', 0, :min => 0, :max => 255 %>
<span class="blue">B</span> <span id="show-pen-blue">0</span><br />
<div id="preview-color"></div>
scss
現在の色をプレビューするボックスのスタイルです。
ただの四角です。
/* app/assets/stylesheets/pictures.css.scss */
#preview-color{
width: 30px;
height: 30px;
background-color: rgb(0, 0, 0);
border: solid 1px black;
}
coffee script
contextに色変更の機能を加えます。
strokeStyleとfillStyleにスライダーの色を設定するだけです。
// app/assets/javascripts/pictures.js.coffee
ctx.setColor = ->
color = "rgb(#{red_slider.val()},#{green_slider.val()},#{blue_slider.val()})"
@.strokeStyle = color
@.fillStyle = color
preview_color.css('background-color', color)
スライダーの操作を取得します。
// app/assets/javascripts/pictures.js.coffee
red_slider = $("#pen-color-red-slider")
green_slider = $("#pen-color-green-slider")
blue_slider = $("#pen-color-blue-slider")
preview_color = $("#preview-color")
red_slider.change ->
ctx.setColor()
$("#show-pen-red").text($(@).val())
green_slider.change ->
ctx.setColor()
$("#show-pen-green").text($(@).val())
blue_slider.change ->
ctx.setColor()
$("#show-pen-blue").text($(@).val())
これで色を変えられるようになりました。
絵を投稿できる
さて、ついに絵をサーバ側に保存します。
仕様は適当に、
・public/imagesディレクトリに保存
・100件まで。超えたら古いものから削除していく。
です。
erb
保存するボタンを作ります。
<span id="save-button" class="controll-button">保存</span>
coffee script
canvasでtoDataURLすると、base64エンコードされた画像文字列が取得できます。
これをpictures controllerのcreate actionにポストします。
// app/assets/javascripts/pictures.js.coffee
$("#save-button").click ->
url = canvas[0].toDataURL()
$.post '/pictures', {data: url}
ruby
画像データ受信元のルーティングを追加します。
# config/routes.rb
resources :pictures, only: [:create] # この一行を追加
ピクチャーモデルを作成します。
実際はmysqlの設定などしていますが、省略です。
今回、カラムはidしか使わないためマイグレーションはいじらずにさっさとdb:migrateしました。
$ rails generate model picture
$ rake db:migrate
コントローラの動作です。
ブラウザから送られてきた画像データから、
最初の文字列を削除してbase64デコードすると画像として保存できます。
# app/controllers/pictures_controller.rb
def create
path = 'public/images/'
picture = Picture.create
File.open("#{Rails.root}/#{path}/#{picture.id}.png", "wb") { |f|
f.write Base64.decode64(params[:data].sub!(data:image/png;base64,', ''))
}
if Picture.count > 100
picture = Picture.order(:id).first
begin
File.unlink("#{Rails.root}/#{path}/#{picture.id}.png", "wb")
rescue
# 適当な処理
end
picture.destroy
end
render :nothing => true
end
これで画像を投稿できるようになりました。
投稿された画像の一覧を表示できる
画像は投稿できるようになったけれども、
投稿された画像を見ることができないので見れるようにします。
erb
ただの表示用div要素です。
<div id="pictures"></div>
ruby
pictures controllerにcreate actionのルーティングを追加します。
# coufig/routes.rb
resources :pictures, only: [:index, :create]
rails側でrenderして、HTMLをクライアントに返しても良いですが、
今回はディレクトリの構造が安易なのでRailsはidだけ渡して、残りはブラウザで処理してもらいます。
# app/controllers/pictures_controller.rb
def index
@response = ''.tap do |me|
Picture.all.reverse.each do |picture|
me << "#{picture.id},"
end
end
end
このエントリーに対するコメント
-
初めまして、だいぶ古い記事に対しての質問なのですがよろしいでしょうか?
このシステムの画像保存部分なのですが、画像のファイル番号はどうやって決定しているのでしょうか?
見たところデータベースで管理しているわけではないようなので、picture.idの振り方がわかりません。
よろしければ教えていただけませんか?2013年01月23日, 11:22 AM
-
コメントありがとうございます。
記事中にある$ rails generate model picture
というコマンドを実行したときに、idだけ持っているpicturesテーブルが自動で生成されます。
idは勝手にユニークでインクリメンタルな値になるので、特にきにせずにpicture.idを使っています。2013年01月23日, 8:23 PM
-
なるほど、そんな機能があったんですね。
すいません後1つあるんですが、
このシステムを完成させた時、画像の保存をかけると1つ前の画像が保存されてしまいます。
詳しく説明すると、先を2本描くと1本目を描いた状態のものが保存されます。その後連続して保存をかけると今度は2本描かれたものがちゃんと保存できるのですが、これはやはりcanvasのプログラムが悪いのでしょうか?
もし、原因をご存知でしたら教えていただけないでしょうか?2013年01月24日, 2:58 AM
-
うーん、なんでしょうね・・。
こちらでは発生していない現象なので、コードを書いた順番などが間違っているのかもしれません。
私の書いたコードはgithubにあるので見比べてみてください。https://github.com/asaumi/canvas_post
2013年01月25日, 9:28 AM
日本語が含まれない投稿は無視されますのでご注意ください。(スパム対策)
- トラックバック
-
- 編集後記:rails3 + html5 canvasでお絵かき投稿サイトを作ろう! | このコードわからん2011/11/17, 12:09 AM
[…] ついこの間、 rails3 + html5 canvasでお絵かき投稿サイトを作ろう! って記事を会社のブログに書いた。 […]
-
- あずみ.net » [Bookmark]rails3 + html5 canvasでお絵かき投稿サイトを作ろう! | KRAY Inc2013/02/19, 10:17 AM
[…] rails3 + html5 canvasでお絵かき投稿サイトを作ろう! | KRAY Inc […]
「いいね!」で応援よろしくお願いします!