明けましておめでとうございます。 🙂
昨年のご愛顧も込めまして、「WordPress デザインワークブック」共著のコモモさんこと高橋朋代さんと Web 仕掛け絵本のサイトをつくりました。とてもかわいい感じになりましたので、良ければ見てください!
PC/Mac はモダンブラウザ(IE8 以下はごめんなさいです)に対応しています。
モバイル系は iOS は 6 以降の Mobile Safari、Android は 4 以降の Chrome が良い感じで見れます。Android 2 の方は Firefox 17 を使うとゆっくりですが動作します。3G 回線の場合は、ちょっと読み込みに時間かかりますので、白くなったりしたらリロードしてみてください。キャッシュするときれいに動きます。
iPhone/iPad は 4S 以降(Apple A5)でフルスペックで動くはずです。すごい性能。iPhone の場合は Retina ででます。絵本的になりますので iPad3 もおすすめです。ベンチマークにもどうぞ。
自分の本職はバックエンド側の処理、業務系アプリの処理やインフラなどなのですが、WordPress を含めこういった Web サイトをつくる場合には、芸術性のあるデザインやブラウザフロントエンド処理が不可欠です。
“WordPress デザインワークブック” の執筆ではこの部分にコモモさんの全面支援をもらっていますが、”バッタになりたい魔法使い”では絵をコモモさんに、ひろましゃはお話と、なんと初 JavaScript をやっております。 動きも面白いですので良ければその辺もご覧ください。 🙂
さて、去年はこのブログで「WordPress 徹底解析」シリーズを週1でやろうと始めておりましたが、少し忙しくなってしまい止まってしまってごめんなさい。2月くらいからまた時間が出来ると思いますので再開したいと思います。
今年もどうぞよろしくお願いいたします!
WordPress ではないですが、せっかく Web 系のネタですのでこの仕掛け絵本の仕組みを解説したいと思います。 まずは動作見て頂ければ。スクロールを上下するとぴょこぴょこ動くのが分かると思います。 🙂
Web 仕掛け絵本で実現したかったことは2つでした。
- 60FPS のフルフレームで動作させる
- スマートフォン・タブレットでも動かす
絵本という前提があるので、コンピュータ的なフレーム落ちしたガタガタした動作は物語の世界を壊すと考え、ブラウザに 1/60 秒フレーム描画(canvas & requestAnimationFrame)をさせています。また、スマートフォン、タブレットでみれたら絵本っぽいなというのも考えました。
このため、canvas と requestAnimationFrame(もしくはベンダープレフィックス)が使えないブラウザでは動作しません。IE で言えば IE9 以降に対応します。ちなみに IE9 は結構速かったです。スマートフォン系では iOS6 以降、Android 4(標準ブラウザもしくは Chrome、Firefox17)が動作します。iOS5 の方、ごめんなさい…
この仕組みにパララ絵本という名称をつけていて、いわゆるパララックス効果も入っているのですが、通常のパララックスとは異なります。というのも、普通のパララックスはブラウザの縦座標(scrollTop)に対して、描画オブジェクトの位置を決めますが、パララ絵本ではスクロール位置を動かすタイミングとしてだけ使って、あとはタイマー処理しています。これは 60 フレーム描画をするためと、スマートフォン(Mobile Safari)がスクロール中動作にイベントが発生しないことに対する仕様です。
実際の実装ですが、絵本のコマに対して canvas タグを対応させ、CSS では canvas を display: block; してブラウザの描画領域を全て埋め、横幅、余白を含め全ての描画制御を JavaScript から行っています。たとえば、背景画像横幅が足りなければ CSS で真ん中にもっていくのではなく、”白”を画像の左右に描画しているイメージです。
ブラウザにやらせるよりも自前で制御したほうが、必ず見せたい領域を中央にもってくる処理や、 devicePixelRate(retina)の対応など、描いてもらった絵を忠実に再現させるのが楽だったというのが理由です。(このためちょっとブラウザリサイズの処理は遅いです)
<body id="parara"> <canvas></canvas> <canvas></canvas> ... </body>
body { margin: 0; padding: 0; } canvas { display: block; }
JavaScript のソースはオーソドックスな、いわゆるゲーム描きです。requestAnimationFrame(render) と通常タイマー(update)の二つをつかった 1/60 処理です。
/** * Event initialize. */ Scene.prototype.start = function() { this.hack(); window.addEventListener('load', this.init.bind(this), false); window.addEventListener('resize', this.init.bind(this), false); window.addEventListener('orientationchange', this.init.bind(this), false); window.addEventListener('scroll', this.update.bind(this), false); window.setInterval(this.update.bind(this), 1000 / ANIMATION_FPS); this.render(); }; /** * render. */ Scene.prototype.render = function() { requestAnimFrame(this.render.bind(this)); // viewport range render. ... ... ... /** * requestAnimationFrame wrapper. * context invalid?: Illegal operation on WrappedNative prototype object */ window.requestAnimFrame = (function() { return window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || window.oRequestAnimationFrame || window.msRequestAnimationFrame || function(/* function */callback, /* DOMElement */element) { window.setTimeout(callback, 1000 / ANIMATION_FPS); }; })();
オブジェクトの設計は、複数あるコマ(Scene)、コマ(Page)、コマ内の描画オブジェクト(Actor)と分割し、Scene ではブラウザのリサイズやスクロール位置処理と update render タイミングをとる処理、Page では Actor の実座標や動作タイマー(tick)制御、Actor は描画処理(canvas.drawImage)という感じになっています。
描画オブジェクトも全てビットマップ(Image)ですので、これらに対する DOM の操作はありません。全て canvas コンテキストに描いています。
Actor の座標はそれぞれに設定された動作開始スクロール位置からタイマーで +1 増加を始める tick 値によって決まり、逆スクロールでは時間(tick)を巻き戻すことで元の配置に戻るようにしています。つまり x += 1; とやるわけではなく x = tick とするイメージです。
隠しコマンドで「d」キーを押すとデバッグ表示になるようにしています。たぶん見るとすぐ分かると思います。
また、Actor の座標に関しては PararaEhon.js 初期化時にコンストラクタで定義できるようになっており、update や efect(drawImage する直前)をメソッドを処理時に差し込み、仕掛け絵本ライブラリとして使えるようにしています。
/** * バッタになりたい魔法使い. * * @author komomo and hiromasa * @version 1.0 */ new PararaEhon.Scene({ id : 'parara', width: 960, height: 600, book : [ /** * ページ01 */ { bg: { image: 'images/page01/01_bg.png', fixed: true }, wizard: { image: 'images/page01/01_m.png', x: 50, y: 25 }, ..... }, /** * ページ02 */ { ..... hitsuji01: { image: 'images/page02/02_hitsuji01.png', x: 15, //y: 320, start: 200, update: function() { this.y = 600 + (1 - Math.exp(-6 * (this.tick / 60))) * -280; if(this.tick > 60) this.complete = true; } }, .....
/** * Constructor. */ Actor = function(context, object) { ....... this.update = this.update ? object.update.bind(this) : null; this.effect = this.effect ? object.effect.bind(this) : null; ....... };
さて、キモとなる canvas.drawImage ですがブラウザによっていろいろ面白い動作がありました。Gecko や Webkit のソースまで見ていないので本当に見た目だけの判断です。間違っている可能性大きいです。
- Firefox17 と IE9 は スクロールで見えなくなっている canvas にもまじめに drawImage している?(見えているコマだけ render したら速くなりました)
- Firefox17 の Mac と Windows 版は描画が少し遅い。Linux 版(しかも性能の劣る機械)だとフルルフレームで描画される。(本当にこれはなんとなくなのですが Linux 以外は Image が GPU じゃなくてメインメモリから転送されているような。うまく VRAM に乗らなかった?気のせい?)
- Firefox では drawImage の引数で画像範囲外を誤って転送させても動くが、他のブラウザだと動作しない。(動かない動作は OpenGL で同様の間違いをしたときの動作に似ている)
- Mobile Safari に過激に複数の canvas をレンダリングさせると落ちる時がある
- Android Firefox 17 が結構がんばって canvas 描ける。(遅いですが)
- 速度の印象は Safari < Chrome < Firefox17 = IE9 という感じでした。がんばれ Firefox!(もうすぐでるであろう Firefox18 / IonMonkey が楽しみです)
なにぶんにもモダンな JavaScript をかくこともブラウザもほとんど初めてで、押し迫る期限の中座標処理でうひょ〜となってしまった部分もあり、まだまだ不出来ですがもし興味ありましたらソースを見てみてください。 🙂
というわけで。
[tegaki]みんなバッタになっちゃえ![/tegaki]