createjs(preloadjs)とCORSのところでハマったのでメモ
業務で、canvasに描画されているバイナリデータをサーバに送りたいということになった。
で、canvasのAPIにtoDataUrlというメソッドあるの知ってたので以下のように試してみるも、
var canvas = document.getElementById('testCanvas'); var data = canvas.toDataURL(); console.log(data);
Chromeでみると以下のようなエラーが。
>>Uncaught SecurityError: Failed to execute 'toDataURL' on 'HTMLCanvasElement': tainted canvases may not be exported <<
canvasまわりでのクロスドメインの話しはよく聞くので調べると
これで問題解決しそうだ。CORS(Cross-Origin Resource Sharing)っていうのか。
まずapache側で
<Location /xxxxx/> Header set Access-Control-Allow-Origin: "http://xxx.xx.xx.xxx" </Location>
と設定して、ブラウザで上記が反映されたレスポンスヘッダが正しく返ってくることを確認。
次にさきほどのサイトに書いてあった
img.crossOrigin = "Anonymous";
の部分なのですが、、、ここでハマってしまった。
まず、
var img = new Image(); img.crossOrigin = "Anonymous"; img.src = "http://xxx.yyyy.jp/image/test.png"; img.onload = function(){ var ctx = canvas.getContext("2d"); ctx.drawImage(img,0,0); var data = canvas.toDataURL(); console.log(data); }
はOKだけど
var img = new Image(); img.src = "http://xxx.yyyy.jp/image/test.png"; img.crossOrigin = "Anonymous"; img.onload = function(){ var ctx = canvas.getContext("2d"); ctx.drawImage(img,0,0); var data = canvas.toDataURL(); console.log(data); }
だと変わらずクロスドメインエラーがでる("Anonymous"を設定している箇所が違う)、、、
ってのがなんでかなのかよくわからない。
次に自分はcreatejs(preloadjs)を使ってるんだった。
どこで"Anonymous"の設定はやるんだろう?
現在使用しているバージョンのcreatejs(createjs-2013.05.14.min.jsです)の中を検索しても"Anonumous"は出てこないし、preloadjs内のタグローダで画像読み込んでいる箇所でもcrossOrigin属性を設定できそうな箇所もない。
かといってpreloadjsを使わずに上のような書き方に直すのはかなり手間だった。
createjsに直接手入れないとダメかなー。
要望もあったようですね。
でpreloadjsの最新のGithubみたら、奇遇にもこの記事書いてる9日前に"anonymous"の処理追加されてた 汗
Added better handling of local files / crossOrigin requests · a397f13 · CreateJS/PreloadJS · GitHub
これで問題なさそうですね。クロスドメインは厄介だ。。。
jsrender使ってviewとロジックをわける
恥を忍んで書きます。業務でアイテム一覧のようなものを表示する際に、コーダーから以下のような素材をもらって(実際のものとは違う。あくまでイメージ)
<div class="div_xxx"> <p class="p_xxx">アイテム名称</p> <p class="p_yyy">期間限定</p> ##期間限定のもののみ <img class="img_xxx" src="xxx.png"> </div>
で、エンジニアである自分が、ajax通信で取得したjsonの配列データをもとに、アイテム名称及び画像URLにそれぞれ配列のデータを割り当て、一覧表示するという処理を書いた。以下がそれ。
<!-- <div class="div_xxx"> <p class="p_xxx">アイテム名称</p> <p class="p_yyy">期間限定</p> ##期間限定のもののみ <img class="img_xxx" src="xxx.png"> </div> --> <script> var el = ""; for (var i = 0; i < list.length; i++) { if(list[i].limitFlg === true){ "<div class='div_xxx'><p class='p_xxx'>" + list[i].name + "</p><p class='p_yyy'>期間限定</p><img class='img_xxx' src='" + list[i].imgUrl + "'></div>" } else { "<div class='div_xxx'><p class='p_xxx'>" + list[i].name + "</p><img class='img_xxx' src='" + list[i].imgUrl + "'></div>" } } jQuery("#container").append(el); </script>
これはひどいw
なにがひどいって、コーダーが組んだ要素を文字列でjsで記述してるので、たとえばそこのデザイン少し変えたいので要素ごにょごにょいじりたいなんてときの保守性という点で最悪ですね(実際のものはもっと複雑な構成です)。
業務が忙しくしばらくこのまま放置していたのですが、最近ようやくjsrender使いました。
jquery-tmplというライブラリが有名だったようですが、deprecatedになったようで、でその同じ作者で今でも開発が続けられているのがjsrenderです。
使った結果が以下のものようになります。
<script id="tmpl_item" type="text/x-jsrender"> {{for list}} <div class="div_xxx"> <p class="p_xxx">{{:name}}</p> {{if limitFlg == true}} <p class="p_yyy">期間限定</p> {{/if}} <img class="img_xxx" src="{{:imgUrl}}"> </div> {{/for}} </script>
/* データの中身は例えば以下のようだとする var data = { list: [ { "name" :"aaaa", "imgUrl" :"aaaa.png", "limitFlg" :true }, { "name" :"bbbb", "imgUrl" :"bbbb.png", "limitFlg" :true }, { "name" :"cccc", "imgUrl" :"cccc.png", "limitFlg" :false } ] }; */ $(document).ready(function(){ var result = $("#tmpl_item").render(data); $("#container").append(result); });
いわゆる、viewの部分とロジックの部分が分離しています。forとかifあるのも便利ですね。
以上、2,3年前に話題になったであろうものを今にしてやってみるというw
HTML版スライドのライブラリ、Bespoke.jsをさわってみた
前回のエントリでHTML版スライドをつくるためのフレームワーク一覧のリンクを貼って試してみたいなんて書いたので、そのうちの1つ"Bespoke.js"というのをさわってみた。
markdalgleish/bespoke.js · GitHub
まあREADME.mdに書いてあることちょこっとやってみただけですが。
まずどんなものがつくれるのかをdemoでみてみる。完成度としては十分だと感じた。
https://github.com/markdalgleish/bespoke.js#demo
さて、さっそく使ってみよう。
https://github.com/markdalgleish/bespoke.js#getting-started
"The old fashioned way"というのはさすがに使いたくなかったので無視。
こちらにはさらっと書いてあるが、まず"Yeoman"というものを知らなかったので調べてみる。自分はこのサイトわかりやすかったので参考にした。
Yeoman入門(第一部、yoを使う) - from scratch
Yeomanは"Grunt"と"Bower"と"yo"の3つを引っくるめたもののことのようで、yoというのは一言でいうと"雛形を生成するツール"のようです。
なるほど、まず雛形を生成してから使ってくださいねということらしいので、Githubに書かれている通りにやってみる。
$ npm install -g yo generator-bespoke $ mkdir my-presentation && cd $_ $ yo bespoke
すると以下のような対話形式の質問が続くので、それに答える形で最適な雛形が生成されます。
Thanks for choosing Bespoke.js for your presentation! :) -@markdalgleish [?] What is the title of your presentation? test [?] Would you like bullet list support? Yes [?] Would you like responsive slide scaling? Yes [?] Would you like hash routing support? No [?] Would you like an animated progress bar? Yes [?] Would you like slide-specific deck styles? Yes [?] Would you like syntax highlighting? Yes
するとファイル一式が生成されていると思います。
srcディレクリ以下に"index.jade"だったり"main.styl"がありますが、"Gruntfile.js"があることからわかる通り、Gruntでビルドしてあげる必要があるようですね。
jadeとstylusについては以下
Jade - Template Engine
Stylus
$ grunt
これでpublicディレクトリが生成されたと思います。
public配下の"index.html"をブラウザで表示してみると、スライドの雛形が出来上がっている!
Bespoke.jsについてのエントリのつもりでしたが、どちらかというとYeomanとの出会いのほうが大きかったかもしれません。
とりあえず次の社内勉強会の発表はこれでいこう。
Node.jsでトータライザみたいなものをつくってみる
つい先日、社内勉強会でjsの話をしたのですが、そのときにjsついでということで"Node.jsでトータライザみたいなもの"をつくった。つくったけれどもあまりうまくいかなかったので(ウケもいまいち 汗)、戒めついでにメモとして残しておこう。
トータライザってテレビでよくみかける、「○○だと思う人はスイッチオン」みたいなやつのことで、今回つくろうとしたのは、発表内容の各項目に対して「すでに知っていた」「なんとなく知っていた」「全く知らなかった」みたいな3段階の評価をみんなにしてもらい、その結果をwebsocketでリアルタイムに表示するというもの。ダサいですがこんな画面。
jsに対する社内の認識がどれくらいあるかというのが結構謎だったのでやってみたかったんですよね。
まあNode.jsでwebsocketやりたかっただけですがw。
ソースについてはnodejsの付け焼き刃もいいところなので、こんな簡単なソースでしか書いていません。若干省略しているところも有り。
main.html
<button onclick="javascript:sendMessage(1);">わかった!</button> <p id="counter_1">0</p> <button onclick="javascript:sendMessage(2);">ビミョー</button> <p id="counter_2">0</p> <button onclick="javascript:sendMessage(3);">ダメだこりゃ</button> <p id="counter_3">0</p> <button onclick="javascript:resetMessage();">リセット</button> <script src="http://code.jquery.com/jquery-1.10.1.min.js"></script> <script src="/socket.io/socket.io.js"></script> <script> var s = io.connect('http://www.xxx.yyyy/'); /** * 接続時 */ s.on("connect", function () { console.log("connection"); }); /** * 切断時 */ s.on("disconnect", function (client) { console.log("disconnection"); }); /** * 更新受信時 */ s.on("S_to_C_message", function (data) { for(var i = 0; i < data.value.length; i++){ var sel = jQuery("#counter_" + (i + 1)); var count = parseInt(sel.text()); count++; jQuery("#counter_" + (i + 1)).text(data.value[i]); } }); /** * リセット受信時 */ s.on("S_to_C_reset", function (data) { for(var i = 0; i < data.value.length; i++){ var sel = jQuery("#counter_" + (i + 1)); var count = parseInt(sel.text()); count++; jQuery("#counter_" + (i + 1)).text(data.value[i]); } }); /** * 更新送信 */ function sendMessage(index) { s.emit("C_to_S_message", {value:index}); } /** * リセット送信 */ function resetMessage(){ s.emit("C_to_S_reset"); } </script>
server.js
var http = require("http"); var socketio = require("socket.io"); var fs = require("fs"); var server = http.createServer(function(req, res) { res.writeHead(200, {"Content-Type":"text/html"}); var output = fs.readFileSync("./main.html", "utf-8"); res.end(output); }).listen(8080); var io = socketio.listen(server); var userIndex = 0; var countList = [0, 0, 0]; io.sockets.on("connection", function (socket) { socket.on("C_to_S_message", function (data) { countList[data.value - 1]++; io.sockets.emit("S_to_C_message", {value:countList}); }); socket.on("C_to_S_reset", function (data) { countList = [0, 0, 0]; io.sockets.emit("S_to_C_reset", {value:countList}); }); });
で、なにがうまくいかなかったというと、
- なんか反応が遅いときがある(致命的、原因はこれから調べる)
- デザインがわかりづらい、やるならもっとわかりやすくインパクトあるものがよかった(たとえば更新するときにちょっとしたエフェクトいれるとか)
- キーノート+ブラウザってのがダメだったかも、ブラウザで発表資料つくろうかな(参考サイト)
トータライザつくろうと思ったというか簡単につくれるんだろうなと思わせてくれたのが、実は半年ほど前の勉強会で@Shumpeiさんがnodeでプレゼンの資料を表示しながら「いいね」の表示をwebsocketでリアルタイムに表示されるなんていうことをやっていたものです。
やっぱ勉強会に参加すると意外なところで刺激うけたりすることがあるからいいですよね。
次回発表するときはリベンジするぞー!
'Galaxy S4'のWebViewでのcanvasの表示不具合の対応
'Galaxy S4'のWebViewでのcanvasの表示不具合が結構有名ぽくて、自分もハマった。。。
以下のサイトなどに書かれています。
http://blog.happyelements.co.jp/2013_08_01_archive.html
Galaxy S4のWebviewで、非同期処理の中でのCanvasの描画がバグってる - 車輪を再発明 / koba04の日記
今回たまたまどこの記事にもなかった(はず)方法で解決できたのでメモ。
現象
WebViewでcanvasを表示するところが、他端末では正しく表示されているのに、'Galaxy S4'では何も表示されないという現象
解決方法
jQuery('#myCanvas').hide(); //処理 jQuery('#myCanvas').show();
流れとしては、まず一旦キャンバスを'hide()'してあげる。これをしないと自分の環境では正しく表示されませんでした。
次に'show()'をしてあげるのですが、これを実行するべきタイミングがはっきりとしていなくて、すべてのデータを設定し終えたあとにすべきはずだと思ったのですが、hide()の直後に書いても正しく表示さることもありました。でも基本的にはすべての描画処理を終えたあとだと思います。
と・・・理屈ではよくわからないことなのでなんかタメになった気がしないなぁ。
この方法が絶対的な解決方法ではないと思いますが、同じく困っている方いましたら試してみてください。
createjsの継承の実装について整理
javascriptで、オブジェクト指向の継承のようなものを実装する方法はいくつかあるようですが、今回のエントリーでは現在業務で使っているcreatejsのそれについて簡単に整理しましたので書いておこうと思います。
// ネームスペース // 名前空間の汚染を防ぐため this.namespace = this.namespace || {}; (function(){ // コンストラクタ関数 // new Cat() で呼び出される関数 /** * コンストラクタ */ var Cat = function() { // }; // static変数 // すべて大文字 // 外部から書き換えができてしまうが、すべて大文字であるため外部から書き換えをしないというルールで運用する /** 脚の数 */ Cat.LEGS_COUNT = 4; // 継承はプロトタイプに親のインスタンスをセットして行う // 継承するものがないときは // var p = Cat.prototype; /** プロトタイプ */ var p = Cat.prototype = new namespace.Animal(); // プライベート変数 // 先頭にアンダースコアをつける // 外部から書き換えができてしまうが、アンダースコアがついているため外部から書き換えをしないというルールで運用する /** 名前 */ p._name; // パブリック変数 /** 体重 */ p.weight = 0; // プライベートメソッド // 先頭にアンダースコアをつける // 外部から書き換えができてしまうが、アンダースコアがついているため外部から書き換えをしないというルールで運用する /** * うずくまる */ p._crouch = function(){ // }; // パブリックメソッド /** * 名前を取得する * @return 名前 */ p.getName = function() { return this._name; }; /** * 名前を設定する * @param name */ p.setName = function(name) { this._name = name; }; // オーバライド // オーバライドするときはそのまま定義する(この場合Animalのプロトタイプにwalkという関数が定義されているとする) /** * 歩く */ p.walk = function(){ // }; // オーバライド時にsuper.xxx()のように書きたいとき /** 眠る */ p.Animal_sleep = p.sleep; /** * 眠る */ p.sleep = function(){ this.Animal_sleep(); this._crouch(); }; // 外部からnamespace.Catでアクセスできるようにする namespace.Cat = Cat; })();
private扱いのものが外部からアクセスできないようにする実装することも可能なのでしょうが、javascriptの特性であるユルさがなくなってしまうような感じがするので自分はこのままでもいいのかなぁと思います。
ちなみに社内の叩き台で作成されたドキュメントに以下のような感じで書かれていたのですが、
/** 名前 */ var _name; /** * コンストラクタ */ var BadAnimal = function() { // }; var p = BadAnimal.prototype; p.getName = function() { return _name; }; p.setName = function(name) { _name = name; };
これだと"_name"がBadAnimalのプロトタイプ変数ではないので、
var animal1 = new namespace.BadAnimal(); var animal2 = new namespace.BadAnimal(); animal1.setName("iniesta"); console.log("animal1:" + animal1.getName()); console.log("animal2:" + animal2.getName()); animal2.setName("xavi"); console.log("animal1:" + animal1.getName()); console.log("animal2:" + animal2.getName());
としたときのログを見てみると
animal1:iniesta animal2:iniesta animal1:xavi animal2:xavi
となってしまいます。
これは完全にjavascriptでgetter/setterを実装してやろうとしたけれど失敗してしまったパターンですね。
以前どこかの勉強会で白石俊平さん(@Shumpei)が「"javascriptはつぎはぎだらけになってしまう"というよりも"つぎはぎだらけで書けてしまう"と考えるようにした」という話をされていたのですが、まさにそれと同様、無理をしてオブジェクト指向言語の実装を真似る必要はjavascriptにはないのだと個人的に思います。
エンジニアがデザイナーに知ってほしい4つのこと
柄にもなく仰々しいタイトルをつけてみました。
現在私はソーシャルゲームの主にクライアントエンジニアという立場で開発をしていますが、前職ではFLASHアニメーションを含めたデザインっぽい仕事もしてきましたので(Illustrator、Photoshop、3ds Max、あとFrameMakerというadobeのDTPソフトを使用していました)、最低限のデザインの知識とデザイナーの心情というのにも少しは理解があると思います。そういった経緯もあったため今回のエントリ書いてみました。
会社によってエンジニア・デザイナーの役割も違ければ仕事の進め方も当然異なるでしょうから、あくまで参考程度に読んでいただければ。他にもこういうのあるでしょだとか反論含め意見いただければ光栄です。
名前重要
こないだデザイナーさんからいただいたファイル名があまりにもおかしいもの(つまり中身がどんなファイルなのかがわからない!)だったのでこの名前で相応しいのかと伺うと、「まあファイル名なんてどうでもいいのですがね」と返されてしまいました。
エンジニアは変数名、パッケージ名、APIなどの名前をいかに「相応しい」ものにするかに苦心する人種です。その大きな理由としては、「後々別のメンバーがその名前をみたときに、それがどのようなものなのかをすぐに理解できるようにするため」です。「名前付けがうまくできないときは、設計を見直したほうがよいことが多い」と言う人もいます、つまりそれくらい重要なことなのです。
他にももっと理由はあると思います。ポイントは、デザイナーも名前付けを重要視する価値があるのではと思えることです。たとえば先に挙げた「設計力」。設計力があれば、「こういったパターンのデザインも発生しうるだろうから、それも考慮してこういったデザインにしておこう」「こことここは共通化できそうだらからそれを踏まえてデザインしよう」といったように、後々のデザインの修正だったり漏れの発生を少なくすることができると思います。そしてより正しい設計になっているかを確認する指針として名前を一つ一つ丁寧に考えてみる、これは案外大事なことだと思います。
ファイルサイズを意識する
開発環境でテストをしていて、どうも画像の読み込みに時間がかかるなぁなんてことがあります。よくよくみてみると、デザイナーがサーバにアップした画像のファイルサイズがめちゃくちゃ重いのが原因だったりします。
そんなことがあったので、昨年デザイナーが納品する画像についてはPNGGauntletなどのツールを用いた、ファイルサイズの圧縮の手順をまとめた仕様書を作成して仕組み化したのですが・・・中には忙しくなるとそれをサボるメンバーがいるようです(つまりその仕組み化が不十分であるということでありますが)。
ポイントはなぜファイルサイズを意識する、つまりファイルサイズを極力抑えることに必死ににならなければならないのか。これは私のような人間がプロファリングツールなどでページの総ファイル容量が十分小さなものであることを確認してニヤニヤするためではありません。
当然ユーザのストレスを極力少なくするためです。大雑把ですが、圧縮によってファイルサイズが50%になるとします。それによってユーザの待ち時間が半分(例えば300ms→150ms)になるとします。
・・・これって凄いことじゃないですか?我々が想像している以上に劣悪な通信環境でサービスを利用してくれているユーザがいるということを忘れてはいけないと思います。そういったユーザにとってはその効果・恩恵はより大きなものになっているはずです。
これでもまだファイルサイズを意識する必要はないというのであれば、個人的にはそういった方はユーザにサービスを提供する素養がないのではと思います。
共通化したがり
エンジニアには「DRY原則」というものが存在します。"Don't Repeat Yourself"の略で、つまり「同じことを繰り返すな」というものです。わかりやすり例でいうと、同じコードをあちこちに書くなということです。なぜならばそのあちこちに書いたコードに誤りがあった場合、そのすべてに修正が入るからです。これは手間でありまたバグを産む可能性も高くなります。これでエンジニアはとかく共通化したがる人種だということがわかっていただけたと思うのですが、そうなってくると、エンジニアはデザイナーがデザインするときにもこの共通化を意識してほしいと思ってしまいます。たとえば、ほとんど同じ機能なのに若干デザインだったりその見せ方が異なるというだけでコードの共通化が難しくなる・・・これはエンジニアにとってはかなりもどかしいことだと思います。
これについては反論するデザイナーが数多くいると思います。つまり共通化することを意識しすぎるあまりに、デザインの質・創造性が落ちるのではというものです。その点についてはもちろん理解しています。
ただしデザインにおいても共通化によるメリットはあると思います。たとえば同じUIによるユーザの認知向上などです。それを踏まえると、やはりまずは共通化を念頭に置くのは悪いことではないと思います。
仕様を知る
チームの規模は会社の方針なんかもあると思いますが、私はデザイナーやエンジニアといった実際にモノをつくっているメンバーは、手を動かすのと平行して、今携わっているサービスが本当に良いモノか、ユーザにとって喜びを感じられるものか(ストレスのたまるだけのモノになっていないか)についても考察し、早い段階でのフィードバックをするべきだと思っています。なぜなら、企画者が頭の中で考えたこと、またはそれを企画書に落としたものが完璧なものであるわけがない(これは悪口ではなくそういうものということ)、そしてその不完全さを誰が一番始めに気づけるかというと・・・実際にモノをつくっているメンバーだと思うからです。
ここまでいうからには、自分も当然先述したことを実践しているつもりです。仕様の不適切なところを指摘したり、このUIだと極めて操作しづらいのではと指摘したり。
ポイントは、エンジニアよりもデザイナーのほうが1つ前の工程にいるということです。当然ながら問題点は早い段階で気づくにこしたことはありません。より無駄な作業をしなくていいからです。というか要はエンジニアの不要な仕事が減るのですw
1つ前ということはそれだけ気づくことがより難しくなるということだと思いますので多くは望みません。ただ今以上にそういった姿勢で取り組むと、より良い効率の良い開発になるのではと思っています。
とか色々書いてみましたが、逆の話も聞いてみたいですねw
つまり「デザイナーがエンジニアに知ってほしいこと」も。これもたくさんあると思います。たとえば工数だったり状況次第で、エンジニアがある文字の色を指定すること(つまりちょっとしたデザインをすること)なんかもあったりするのですが、その色のセンスの無さに気を悪くしていたデザイナーもいたと思います。