こんにちは。きんくまです。
先日の土曜日にF-siteで話す予定でした。
が、金曜日に父が亡くなりまして、急遽行けなくなってしまいました。
当日、会場にこられた方や、スタッフの皆様にはご迷惑をおかけしました。
すみませんでした。
ただ、せっかく作った発表用のものがありますので、アップします。
今回お題はjsflを使って何かをするというものでした。
それで、仕事の効率化みたいなものは、他の方々がすばらしいものを作ってくるだろうなと思ったので、
私はネタ担当と勝手に思い、「jsfl使ってこんなこともできたよ!」というものを作ろうと思いました。
なので、ブラウザからflashのステージ上や同時接続している他のブラウザにも描き込む
お絵描きチャットを作ってみました。
flashとサーバーの通信は、WindowSWF(拡張パネル)からSocket通信をしています。
ブラウザとサーバーはWebosocketを使って通信します。
サーバーはnode.jsを使って立てます。
それで、各フレームに描いたら、swiffyでパブリッシュをします。
ブラウザから描き込んでいるので、iPhoneからも描き込めて、swiffyでパブリッシュなので、変換されたアニメーションも表示することができました。
発表の資料はこちらです。
PDF版
node_canvas.pdf
ソースはnode.jsとsocket.ioを使っているので、かなり短くすみました。
server.js
node.jsのサーバーたちです。
var app = require('http').createServer(handler), io = require('socket.io').listen(app), fs = require('fs'), net = require('net'), url = require('url'), canvasdata = require('./canvasdata'), tlframes = [], flashSock; for(var i = 0, len = 6; i < len; i++){ tlframes[i] = new canvasdata.TLFrame(); } var responseFile = function(path, res){ fs.readFile(__dirname + path, function (err, data) { if (err) { res.writeHead(500); return res.end('Error loading index.html'); } res.writeHead(200); res.end(data); }); }; function handler(req, res) { var path = url.parse(req.url); if(path.href === '/'){ responseFile('/index.html', res); }else if(path.href === '/canvasdata.js'){ responseFile('/canvasdata.js', res); }else if(path.href === '/ui_image.png'){ responseFile('/ui_image.png', res); }else if(path.href === '/canvas.swf.html'){ responseFile('/../flash/flash/canvas.swf.html', res); }else{ res.writeHead(404); res.end('not found'); } }; app.listen(4000); io.set('log level', 2); io.sockets.on('connection', function (socket) { //clone var frame, stroke, flashJSON; socket.emit('clone', { type:'start' }); for(var i = 0, len = tlframes.length; i < len; i += 1){ frame = tlframes[i]; for(var j = 0, len2 = frame.getStrokeLength(); j < len2; j += 1){ stroke = frame.getStroke(j); socket.emit('clone', { type:'add', frame:i, stroke:j, width:stroke.width, color:stroke.color, points:stroke.points }); } } socket.emit('clone', { type:'end' }); //touch socket.on('touch', function(data){ if(data.type === 'start'){ frame = tlframes[data.frame]; data.stroke = frame.getStrokeLength(); stroke = new canvasdata.Stroke(data.width, data.color); stroke.points.push(data.x, data.y); frame.addStroke(stroke); io.sockets.emit('touch', data); }else if(data.type === 'move'){ frame = tlframes[data.frame]; stroke = frame.getStroke(data.stroke); stroke.points.push(data.x, data.y); io.sockets.emit('touch', data); }else if(data.type === 'end'){ if(flashSock){ frame = tlframes[data.frame]; stroke = frame.getStroke(data.stroke); flashJSON = JSON.stringify({ "frame":data.frame, "stroke":stroke }); flashSock.write(flashJSON); //JSON.parse(text); } } }); socket.on('publish', function(data){ flashJSON = JSON.stringify({"type":"publish"}); flashSock.write(flashJSON); }); }); var sockServer = net.createServer(function(socket){ console.log('client connected'); flashSock = socket; socket.on('end', function(){ console.log('client disconnected'); }); socket.on('data', function(buf){ var msg = buf.toString(); if(msg.indexOf('swiffy complete') !== -1){ io.sockets.emit('location', {href:'canvas.swf.html'}); //console.log('complete'); } }); }); sockServer.listen(3000, function(){ console.log('server listen'); });
canvasdata.js
クライアント、サーバー共通で使います。
var TLFrame = function(){ this.strokes = []; }; TLFrame.prototype.addStroke = function(stroke){ this.strokes.push(stroke); }; TLFrame.prototype.getStroke = function(index){ return this.strokes[index]; }; TLFrame.prototype.getStrokeLength = function(){ return this.strokes.length; }; var Stroke = function(width, color){ this.width = width; this.color = color; this.points = []; }; if(exports){ exports.TLFrame = TLFrame; exports.Stroke = Stroke; }
index.html
クライアント側です。
<html> <head> <meta name="apple-mobile-web-app-capable" content="no"> <meta name="apple-mobile-web-app-capable" content="yes" /> <meta name="viewport" content="width=device-width,initial-scale=1,user-scalable=no" /> <script src="/socket.io/socket.io.js"></script> <script src="canvasdata.js"></script> <script> var Painter = function(){ this.canvas = document.getElementById('mycanvas'); this.ctx = this.canvas.getContext('2d'); this.CW = this.canvas.width; this.CH = this.canvas.height; this.tlframes; this.currentFrameIdx = 0; this.currentStrokeIdx = 0; this.initFrames(); this.clear(); }; Painter.prototype = { initFrames:function(){ this.tlframes = []; for(var i = 0, len = 6; i < len; i++){ this.tlframes[i] = new TLFrame(); } this.currentFrameIdx = 0; this.currentStrokeIdx = 0; }, clear:function(){ this.ctx.fillStyle = '#ffffff'; this.ctx.fillRect(0,0,this.CW, this.CH); }, draw:function(frame){ var stroke, points; this.clear(); for(var i = 0, len = frame.getStrokeLength(); i < len; i += 1){ stroke = frame.getStroke(i); points = stroke.points; this.ctx.strokeStyle = stroke.color; this.ctx.lineWidth = stroke.width; this.ctx.beginPath(); this.ctx.moveTo(points[0], points[1]); for(var j = 2, len2 = points.length; j < len2; j += 2){ this.ctx.lineTo(points[j], points[j+1]); } this.ctx.stroke(); } }, onSocketTouchData:function(data){ var frame, stroke; if(data.type === 'start'){ this.currentStrokeIdx = data.stroke; frame = this.tlframes[data.frame]; stroke = new Stroke(data.width, data.color); stroke.points.push(data.x, data.y); frame.addStroke(stroke); }else if(data.type === 'move'){ frame = this.tlframes[data.frame]; stroke = frame.getStroke(data.stroke); stroke.points.push(data.x, data.y); if(data.frame === this.currentFrameIdx){ this.draw(frame); } }else if(data.type === 'end'){ /* frame = this.tlframes[data.frame]; stroke = frame.getStroke(data.stroke); stroke.points.push(data.x, data.y); this.draw(frame); */ } }, onSocketCloneData:function(data){ var frame, stroke; if(data.type === 'start'){ this.initFrames(); }else if(data.type === 'add'){ frame = this.tlframes[data.frame]; stroke = new Stroke(data.width, data.color); stroke.points = data.points; frame.addStroke(stroke); }else if(data.type === 'end'){ frame = this.tlframes[this.currentFrameIdx]; this.draw(frame); } }, setCurrentFrameIdx:function(idx){ this.currentFrameIdx = idx; this.draw(this.tlframes[idx]); } }; var Socketio = function(){ this.socket; this.main; this.delegate; }; Socketio.prototype = { connect:function(){ this.socket = io.connect('http://localhost'); this.registerEvents(); }, registerEvents:function(){ var self = this; this.socket.on('touch', function(data){ if(self.delegate){ self.delegate.onSocketTouchData(data); } if(data.type === 'start'){ self.main.setTimelineBg(data.frame, 'keyframe_element'); } }); this.socket.on('clone',function(data){ if(self.delegate){ self.delegate.onSocketCloneData(data); } if(data.type === 'add'){ self.main.setTimelineBg(data.frame, 'keyframe_element'); } }); this.socket.on('location',function(data){ location.href = data.href; }); }, emitTouchStart:function(frameIdx, strokeWidth, strokeColor, point){ this.socket.emit('touch', { type:'start', frame:frameIdx, x:point.x, y:point.y, width:strokeWidth, color:strokeColor }); }, emitTouchMove:function(frameIdx, strokeIdx, point){ this.socket.emit('touch', { type:'move', frame:frameIdx, stroke:strokeIdx, x:point.x, y:point.y }); }, emitTouchEnd:function(frameIdx, strokeIdx, point){ this.socket.emit('touch', { type:'end', frame:frameIdx, stroke:strokeIdx //x:point.x, //y:point.y }); }, emitPublish:function(){ this.socket.emit('publish', {type:'publish'}); } }; var TouchUtil = function(){ }; TouchUtil.addListener = function(element, type, listener){ if('ontouchstart' in window){ switch(type){ case 'start': element.addEventListener('touchstart', listener); break; case 'move': element.addEventListener('touchmove', listener); break; case 'end': element.addEventListener('touchend', listener); break; defalt: break; } }else{ switch(type){ case 'start': element.addEventListener('mousedown', listener); break; case 'move': element.addEventListener('mousemove', listener); break; case 'end': element.addEventListener('mouseup', listener); break; defalt: break; } } }; TouchUtil.removeListener = function(element, type, listener){ if('ontouchstart' in window){ switch(type){ case 'start': element.removeEventListener('touchstart', listener); break; case 'move': element.removeEventListener('touchmove', listener); break; case 'end': element.removeEventListener('touchend', listener); break; defalt: break; } }else{ switch(type){ case 'start': element.removeEventListener('mousedown', listener); break; case 'move': element.removeEventListener('mousemove', listener); break; case 'end': element.removeEventListener('mouseup', listener); break; defalt: break; } } }; TouchUtil.getPositionByEvent = function(e){ if(e.touches && e.touches[0]){ return {x:e.touches[0].clientX, y:e.touches[0].clientY}; }else{ return {x:e.clientX, y:e.clientY}; } }; var Main = function(){ var self = this, anoBodyTouchMove = function(e){ e.preventDefault(); self.onBodyTouchMove(e); }, anoBodyTouchEnd = function(e){ e.preventDefault(); self.onBodyTouchEnd(e); TouchUtil.removeListener(self.body, 'move', anoBodyTouchMove); TouchUtil.removeListener(self.body, 'end', anoBodyTouchEnd); }, anoMenuTouchMove = function(e){ self.onMenuTouch(e); }, anoMenuTouchEnd = function(e){ TouchUtil.removeListener(self.body, 'move', anoMenuTouchMove); TouchUtil.removeListener(self.body, 'end', anoMenuTouchEnd); }; this.painter = new Painter(); this.socket = new Socketio(); this.socket.main = this; this.socket.delegate = this.painter; this.socket.connect(); this.body = document.getElementsByTagName('body')[0]; TouchUtil.addListener(this.painter.canvas, 'start', function(e){ e.preventDefault(); self.onCanvasTouchStart(e); TouchUtil.addListener(self.body, 'move', anoBodyTouchMove); TouchUtil.addListener(self.body, 'end', anoBodyTouchEnd); }); var timeline = document.getElementById('menu'); TouchUtil.addListener(timeline, 'start', function(e){ e.preventDefault(); TouchUtil.addListener(self.body, 'move', anoMenuTouchMove); TouchUtil.addListener(self.body, 'end', anoMenuTouchEnd); self.onMenuTouch(e); }); var publishBtn = document.getElementById('publish_link'); publishBtn.addEventListener('click', function(e){ e.preventDefault(); self.socket.emitPublish(); }); }; Main.prototype = { onBodyTouchMove:function(e){ var point = TouchUtil.getPositionByEvent(e); point.y -= 55; this.socket.emitTouchMove(this.painter.currentFrameIdx, this.painter.currentStrokeIdx, point); }, onBodyTouchEnd:function(e){ var point = TouchUtil.getPositionByEvent(e); point.y -= 55; this.socket.emitTouchEnd(this.painter.currentFrameIdx, this.painter.currentStrokeIdx, point); }, onCanvasTouchStart:function(e){ var point = TouchUtil.getPositionByEvent(e); point.y -= 55; this.socket.emitTouchStart(this.painter.currentFrameIdx, 1, '#000000', point); }, setTimelineBg:function(index, type){ var timeline = document.getElementById('tl_frame'+index); if(type === 'empty'){ }else if(type === 'keyframe'){ timeline.style['background-position'] = '0px -25px'; }else if(type === 'keyframe_element'){ timeline.style['background-position'] = '0px -55px'; } }, setTimelineCurrentPosition:function(frameInex){ var tlcurrent = document.getElementById('timeline_current'); tlcurrent.style['left'] = (frameInex * 50 + 1) + 'px'; }, onMenuTouch:function(e){ var point = TouchUtil.getPositionByEvent(e); var tlframeIdx = Math.floor(point.x / 50); if(tlframeIdx > 5){ return; } if(tlframeIdx !== this.painter.currentFrameIdx){ this.painter.setCurrentFrameIdx(tlframeIdx); this.setTimelineCurrentPosition(tlframeIdx); } } }; window.onload = function(){ var main = new Main(); }; </script> <style> body{ margin: 0px; padding: 0px; background-color:#000; } ul,li{ margin:0px; padding:0px; } #wrapper{ width:320px; height:460px; text-align:left; background-color:#D4D4D4; } #menu{ height:55px; position: relative; overflow: hidden; } #timeline{ width:350px; padding-top:25px; margin-left:0px; } #timeline li{ width:50px; height:30px; display:block; float:left; background-image:url(ui_image.png); background-repeat:no-repeat; background-position:0px -25px; } #timeline li.empty{ background:url(ui_image.png) no-repeat -200px -25px; } #timeline_ruler{ width:320px; height:25px; background:url(ui_image.png) no-repeat 0px 0px; position: absolute; top:0px; left:0px; } #timeline_current{ width:48px; height:53px; background:url(ui_image.png) no-repeat -260px -100px; position: absolute; top:2px; left:1px; } /* canvas{ border:1px solid #999; } */ footer{ width:320px; height:45px; background:url(ui_image.png) no-repeat 0px -160px; } #publish{ width:64px; height:20px; padding-top:8px; margin-left:6px; } #publish a{ width:64px; height:30px; display:block; } </style> </head> <body> <div id="wrapper"> <div id="menu"> <ul id="timeline"> <li id="tl_frame0"></li> <li id="tl_frame1"></li> <li id="tl_frame2"></li> <li id="tl_frame3"></li> <li id="tl_frame4"></li> <li id="tl_frame5"></li> <li id="tl_frame6" class="empty"></li> </ul> <div id="timeline_current"></div> <div id="timeline_ruler"></div> </div> <canvas width="320" height="360" id="mycanvas"></canvas> <footer> <div id="publish"><a href="#" id="publish_link"></a></div> </footer> </div> </body> </html>
NodePainter.as
flash側の拡張パネルです。
package { import adobe.utils.MMExecute; import com.adobe.serialization.json.JSON; import com.bit101.components.PushButton; import flash.display.Graphics; import flash.display.Sprite; import flash.display.StageAlign; import flash.display.StageScaleMode; import flash.events.Event; import flash.events.MouseEvent; import flash.events.ProgressEvent; import flash.net.Socket; [SWF(width="250",height="250",frameRate="24",backgroundColor="#aaaaaa")] public class NodePainter extends Sprite { public static const CONNECT_COLOR_RED:int = 0; public static const CONNECT_COLOR_GREEN:int = 1; private var _socket:Socket; private var _connectButton:PushButton; private var _isconnected:Boolean = false; private var _connectCircle:Sprite; public const HOST:String = "localhost"; public const PORT:int = 3000; public function NodePainter() { stage.scaleMode = StageScaleMode.NO_SCALE; stage.align = StageAlign.TOP_LEFT; createConnectCircle(); _connectButton = new PushButton(this, 10, 10, "CONNECT", onConnectClick); } private function onConnectClick(e:MouseEvent):void { if(_isconnected === false){ _socket = new Socket(); _socket.addEventListener(Event.CONNECT, onSocketConnect); _socket.addEventListener(ProgressEvent.SOCKET_DATA, onSocketData); _socket.connect(HOST, PORT); _connectButton.label = "DISCONNECT"; }else{ _isconnected = false; drawConnectCircle(CONNECT_COLOR_RED); _socket.removeEventListener(Event.CONNECT, onSocketConnect); _socket.removeEventListener(ProgressEvent.SOCKET_DATA, onSocketData); _socket.close(); _socket = null; _connectButton.label = "CONNECT"; } } protected function onSocketData(event:ProgressEvent):void { var chars:Array = [], char:String, msg:String; while(_socket.bytesAvailable){ char = _socket.readUTFBytes(1); chars.push(char); } msg = chars.join(''); var obj:Object = JSON.decode(msg); if(obj.type === 'publish'){ this.publishSwiffy(); _socket.writeUTF('swiffy complete'); _socket.flush(); }else{ this.changeFrame(obj.frame); this.changeStroke(obj.stroke.width, obj.stroke.color); this.drawStagePath(obj.stroke); } //trace(obj.width, obj.color, obj.points.length); } private function publishSwiffy():void { var commands:Array = []; commands.push('var path = fl.configURI + "Commands/" + "Export as HTML5 (Swiffy).jsfl";'); commands.push('fl.runScript(path);'); MMExecute(commands.join('')); } private function changeStroke(strokeWidth:int, strokeColor:String):void { var commands:Array = []; commands.push('var myStroke = fl.getDocumentDOM().getCustomStroke("toolbar");'); commands.push('myStroke.color = "'+strokeColor+'";'); commands.push('fl.getDocumentDOM().setCustomStroke(myStroke);'); MMExecute(commands.join('')); } private function changeFrame(frameIndex:int):void { var jsfl:String = "fl.getDocumentDOM().getTimeline().currentFrame = "+frameIndex; MMExecute(jsfl); } private function drawStagePath(obj:Object):void { var commands:Array = [], i:int = 0, len:int = obj.points.length; commands.push('var fill = fl.getDocumentDOM().getCustomFill();'); commands.push('fill.style= "noFill";'); commands.push('fl.getDocumentDOM().setCustomFill( fill );'); commands.push('var myPath = fl.drawingLayer.newPath();'); for(i = 0; i < len; i += 2){ commands.push('myPath.addPoint('+obj.points[i]+', '+obj.points[i+1]+');'); } commands.push('myPath.makeShape();'); MMExecute(commands.join('')); } protected function onSocketConnect(event:Event):void { _isconnected = true; drawConnectCircle(CONNECT_COLOR_GREEN); } private function createConnectCircle():void { _connectCircle = new Sprite(); _connectCircle.x = 120; _connectCircle.y = 15; addChild(_connectCircle); drawConnectCircle(CONNECT_COLOR_RED); } private function drawConnectCircle(colorType:int):void { var color:int, g:Graphics = _connectCircle.graphics; if(colorType === CONNECT_COLOR_RED){ color = 0xFF0022; }else if(colorType === CONNECT_COLOR_GREEN){ color = 0x74D062; } g.clear(); g.lineStyle(1,0x888888,0.5); g.beginFill(color,1); g.drawCircle(5, 5, 5); g.endFill(); } } }
■ 自作iPhoneアプリ 好評発売中!
・フォルメモ - シンプルなフォルダつきメモ帳
・ジッピー電卓 - 消費税や割引もサクサク計算!
■ LINEスタンプ作りました!
毎日使える。とぼけたウサギ