タグ「JavaScript」が付けられているもの

famo.usは描画エンジンです。 先日紹介したときに言及したデモは下記にありました。

こちら

このfamo.usをAngularと統合した人たちがいます。

http://famo.us/integrations/angular/

これがなかなかいいのです。famo.usはJavaScriptですべて書くのですが、ちょっと手続き的な感じになります。それをAngularのdirectiveに書けるようにするのです。ちゃんと、bindingも動くので、更新系がなんとも簡単なのです。Angularの経験があれば普通なので、特に驚きもないと言えばそれまでなのですが、これに慣れると、生のfamo.usを書くのは面倒くさく感じるようになるかもしれません。

というわけで、サンプルを作ってみました。 1つのHTMLにすべて詰め込みたかったので、single fileのfamous-angularを使いました。通常は、bowerでインストールする方法が紹介されています。

single fileのものはcdnにないので、http://rawgit.com/を使いました。ところで、rawgitはcdn版(beta)もあってすばらしいです。これは続いて欲しいものです。

さて、出来上がったサンプルは

こちら

です。ソースコードはgistにありますが、下記にも貼り付けます。

<html ng-app="MyApp">
<head>
  <title>famo.us angular sample</title>
  <link type="text/css" href="//code.famo.us/famous/0.2/famous.css" rel="stylesheet" />
  <link type="text/css" href="//cdn.rawgit.com/Famous/famous-angular/fee2b717a53ad762c9e3157580ce255901f4ccad/dist/famous-angular.min.css" rel="stylesheet" />
  <script type="text/javascript" src="//code.famo.us/lib/require.js"></script>
  <script type="text/javascript" src="//code.famo.us/famous/0.2/famous.js"></script>
  <script type="text/javascript" src="//ajax.googleapis.com/ajax/libs/angularjs/1.2.18/angular.min.js"></script>
  <script type="text/javascript" src="//cdn.rawgit.com/Famous/famous-angular/fee2b717a53ad762c9e3157580ce255901f4ccad/dist/famous-angular.min.js"></script>
  <script type="text/javascript">
angular.module('MyApp', ['famous.angular'])
.controller('MyController', ['$scope', function($scope) {
  $scope.items = [];
  for (var y = 0; y < 1; y += 0.1) {
    for (var x = 0; x < 1; x += 0.1) {
      $scope.items.push({x: x, y: y, color: 'yellow'});
    }
  }
}]);
  </script>
</head>
<body ng-controller="MyController">
  <fa-app>
    <fa-modifier ng-repeat="item in items" fa-origin="[item.x, item.y]">
      <fa-surface fa-size="[30, 20]" fa-click="item.color = 'red'" fa-background-color="item.color" fa-properties="{textAlign: 'center', lineHeight: '20px'}">{{$index}}</fa-surface>
    </fa-modifier>
  </fa-app>
</body>
</html>

Famo.us/Angularはまだ発展途上ですべての機能がdirective化されていませんが、今後に期待しています。

Famo.us Physicsの簡単なサンプル

  • 投稿日:
  • by

最近注目しているライブラリfamo.us、なにがいいってURLがいいです。

http://famo.us/

今年になってオープンソース化されたのですが、以前プレビュー版のデモを見たときは3次元でぐりぐり動くものだったのですが、正式リリースではモバイルのリッチアプリのデモになっていて、ある意味おとなしくなっていました。実用的ではあります。

famo.usは技術的にもDOMの階層を使わずに自力でレイアウトして、fpsを保証するという面白いものになっています。

もっと感心したのは、Famo.us Universityです。ステップを追ってfamo.usの使い方を学習できます。ACEエディタでその場でいじって動作を確認できるのもすばらしい。

さて、3次元のぐりぐりですが、どうやらfamo.us Physicsというサブライブラリを使うといろいろできるようです。Famo.us Universityのコースではcoming soonとなっています。が、待ちきれずに試してみました。

とにかく簡単に面白いものができないかと思って、作ってみました。 まずはご覧ください。

こちら

どうですか?水色の玉をクリックしてみましたか?まだの人はもう一度。

なんと、これは1つのHTMLファイルに書いています。もちろん中身はほとんどJavaScriptのコードですが。63行なのでブログに貼り付けてもいいのですが、 gistにアップしてあるのでそちらをご参照ください。

ソースはこちら

他にもconstraintのモジュールがあるので、いろいろできそうです。 ただ、ドキュメントはまだ少ないので、現状ではソースを読まざるを得ないことがありました。

しばらく楽しめそうです。

オフラインファーストという言葉があるようです。言葉の存在こそ知りませんでしたが、以前からオフラインアプリを推進したいと思っていました。モバイルファーストなら、HTML5アプリといってもオフラインで使えるべきでしょう、と思います。これには賛否両論あるみたいですが。

http://blog.joelambert.co.uk/2012/11/26/offline-first-a-better-html5-user-experience/

2012年にこんな記事があったのですね。

さて、このオフラインファーストの文脈で、最近注目しているライブラリがあります。Breezeです。 まさにオフラインアプリを作るためのクライアントサイドデータ処理ライブラリです。

stackoverflowでBreezeの代替はないのかという質問がありましたが、

http://stackoverflow.com/questions/15938866/alternative-to-breeze-js

今のところ、ぱっとしたものはなさそうです。今後の登場にも期待しましょう。

Breezeはだいぶ強力なようですが、それを理解するのはなかなか骨が折れます。基本的なデータベースの概念が理解できていないとつらいのかしら。ドキュメントもあるのですが、どこから読んでいいのか分かりにくいです。stackoverflowで聞いてね、って感じです。それはそれで、いいアプローチだとは思います。 ちなみに、Angularのドキュメントは読みやすいと感じます。

今日の本題。MEANスタックって知っていますか? ちらほら記事があるので、知名度はそこそこでしょうか。 MongoDB, Express, AngularJS, Node.jsの頭文字をとっているのですが、個人的には順序が気になって仕方がないです。まあ、語呂合わせなんでしょうけど。

http://mean.io/

こんなサイトがあるのですね。

http://jp.blog.mongodb.org/post/49262866911/the-mean-stack-mongodb-expressjs-angularjs-and

なるほど、MongoDBの人たちが呼び始めたのでしょうか。

さて、本当の本題。BMEANスタックというのをご存知の人は少ないのではないでしょうか。まぁ、Breezeの人が呼んでいるだけだと思いますが、最近の一押しです。

BMEANのBはもちろんBreezeです。BreezeのバックエンドとしてMongoDBを使います。AngularJSも実はオフラインアプリと相性がいいと思っています。ExpressとNode.jsはバックエンドのフレームワークですね。

BMEANを見つけたのは、Breezeのサンプルアプリです。

http://www.breezejs.com/samples/zza

これもまたドキュメントが少なくて、ソースコード読めっていうスタンスなのですが、がんばって読んでみています。そこまで完全なオフラインファーストは意識されていないようで、基本的にはオンラインで動かすことを前提に、一時的にオフラインになっても大丈夫なようにできる(そのようにコーディングすれば)というところでしょうか。

しばらくはBMEANスタックを考えてみようと思います。 そのうち、オフラインファーストに求められるライブラリの形が見えてくるかもしれません。


2/8追記。

Breezeのチュートリアルは面白いです。

http://learn.breezejs.com/

インタラクティブに実行できるので、勉強になりますね。もっと内容が増えてくれたらいいのですが。

iPhoneのWebアプリというのは「ホーム画面に追加する」でインストール、というかブックマーク、するものです。HTML5でもそこそこアプリっぽく作れます。

さて、HTMLのタグで、ズームなどは無効にできます。例えば、次のようにします。

<meta name="viewport" content="width=device-width initial-scale=1, user-scalable=no" />
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />

ところが、これだけだと、縦にはスクロールできてバウンスしてしまいます。コンテンツの高さがちょうどぴったりだとしてもです。記憶違いでなければ、Androidではそのようなことはなかったような。

検索したところ、stackoverflowがわんさかヒットしました。比較的よくまとまっているかなと思ったのは次の3つ。

http://stackoverflow.com/questions/9651215/how-to-disable-bouncing-in-html5-fullscreen-iphone-app

http://stackoverflow.com/questions/10357844/how-to-disable-rubber-band-in-ios-web-apps

http://stackoverflow.com/questions/7768269/ipad-safari-disable-scrolling-and-bounce-effect

結局、JavaScriptを使わないとできないようですね。fullscreenのサイズで作っておくと、Safariで表示したときは表示エリアが狭くなるのでスクロールが必要です。そこでちょっとだけ工夫をしました。参考までにコードを載せておきます。

document.addEventListener('touchmove', function(e) {
  if (window.innerHeight >= document.body.scrollHeight) {
    e.preventDefault();
  }
}, false);

コンテンツの内部でスクロールやドラッグをしたい場合はさらに工夫が必要かと思いますが、それはまた機会に考えることにします。

mobile-bookmark-bubbleをangular.jsで使う

  • 投稿日:
  • by

mobile-bookmark-bubbleというのはiPhoneなどのMobile Safariで「ホーム画面に追加」を促すポップアップを出すライブラリです。

依存ライブラリもなく比較的ポータブルに実装されているのですが、 angular.jsで使おうとすると、デフォルトの#ハッシュで抑制する機構がうまくありません。

解決法は色々あると思いますが、おそらく最も手軽な$rootScopeを使う方法を実装してみました。$rootScopeなのでリロードするとリセットされますが、それはそれでいいケースもあるでしょう。

コードはこちら。

angular.module('MyModule', []).run(['$rootScope', '$timeout', function($rootScope, $timeout) {
  $timeout(function() {
    var bubble = new google.bookmarkbubble.Bubble();
    bubble.hasHashParameter = function() {
      return $rootScope.bookmarkbubble_shown;
    };
    bubble.setHashParameter = function() {
      $rootScope.bookmarkbubble_shown = true;
    };
    bubble.showIfAllowed();
  }, 1000);
}]);

(よく考えたら、$rootScopeに入れる必要はなくて、$timeoutの外でフラグ変数を持っていればいいだけか。$rootScopeに入れておくと、HTML側からフラグを操作できることがメリットになるのか、ならないのか。)

あまり参考にならないかもしれませんが、備忘メモでした。

JavaScriptの正規表現のパフォーマンスに興味があったので調べてみました。 現時点での考察であり、JavaScriptの処理系による違いもありますし、将来的に最適化が進んだ場合は結果が変わることがあると思いますので、ご注意を。

まず、初めに見つけた記事が、これ。

http://stackoverflow.com/questions/9750338/dynamic-vs-inline-regexp-performance-in-javascript

簡単に言うと、

/[a-z]/.exec(str);

と

new RegExp('[a-z]').exec(str);

で、パフォーマンスが違うけど何で?という質問でした。 結論から言うと、これは等価ではありません。 前者は正規表現を一度しか評価しないのに対し、後者は毎回RegExpオブジェクトを作ります。

どうやら、次の3つ関数はほぼ等価のようです。

function func1(str) {
  return /[a-z]/.exec(str);
}

var re2 = /[a-z]/;
function func2(str) {
  return re2.exec(str);
}

var re3 = new RegExp('[a-z]');
function func3(str) {
  return re3.exec(str);
}

「ほぼ」というのは、実際に中身を見たわけではないからです。ただ、処理時間から推測しただけです。

さて、ここまでは理解できていたのですが、実はこの状態だと正規表現のコンパイルは行われていないようなのです。

どういうことかと言うと、

var re4 = new RegExp('[a-z]');
re4.compile();
function func4(str) {
  return re4.exec(str);
}

とすると、処理時間が短くなるケースがあるのです。 これはちょっと意外でした。正規表現のコンパイルというのは最適化の過程で勝手に行われるのかと思っていました。もしかしたら、最適化が行われるのはもっと長い時間処理が回っていないからなのかもしれませんが。

まずは、nodeでテストをしてみました。

https://gist.github.com/dai-shi/7394062

このベンチマークを走らせたら、一つの書き方だけ明らかに速くなりました。それが上記の書き方です。nodeのバージョンをv0.8, v0.10, v0.11と試しましたが、傾向はどれも同じです。

次にjsperfでも試しました。

http://jsperf.com/classname-check-regex-vs-indexof

ちょっと目的が違うのでindexOfとの比較が入っていますが、RegExpの方は大体同じです。 Chromeでの実行結果は、compile()を実行したケースがわずかに速くなりました。試している正規表現が違うので、コンパイルが効きにくいのかもしれません。 Firefoxでの実行結果も、compile()のケースが速くなりました。こちらは10%以上。しかも、全体的にChromeの実行結果より速い。ちょっと意外です。

さて、別のベンチマークも見てみましょう。

http://jsperf.com/javascript-compiled-regex/15

これは、globalマッチのテストケースも入っているのでちょっと分かりにくいですが、それを除けばやっぱりcompile()しているケースが速いです。Chromeで20%ほどアップ。Firefoxで30%ほどアップ。

ちなみに、globalマッチはChromeの場合は最速で、Firefoxの場合はもっとも遅いようです。これは使い方を悩みますね。

これらのベンチマーク結果を踏まえてどういう方針にするかですが、 まず、クライアントサイドのコードの場合は、10%~30%程度速くなることにどれくらい意味があるかですが、ばらつきもあるので、よほどパフォーマンスにシビアでなければ、inline型の正規表現でよいように思います。読みやすいといのが最大のメリットです。つまり、

/[a-z]/.exec(str);

こういう感じです。

一方、サーバサイドのコード、つまり、nodeの場合は、2倍近く速くなるケースもあり、また通常サーバサイドのコードはパフォーマンスを重視することから、現時点ではcompile型の正規表現を使うことに意味があるかもしれません。つまり、

var re = new RegExp('[a-z]');
re.compile();
function func() {
  return re.exec(str);
}

こういう感じです。関数の外で定義しないといけないので、場合によっては気をつける必要があります。変数名の衝突や、意図しない書き換えなど。

var func = (function() {
  var re = new RegExp('[a-z]');
  re.compile();
  return function() {
    return re.exec(str);
  };
})();

のようにする手もありますが、ますます読みにくくなっていきますね。

本来なら、re.compile()がなくてもコンパイルしてくれたらいいように思うのですが、コンパイルそのものにコストがかかるケースもあるので一回しか使わないような場合はコンパイルすべきじゃないということですね。JITなどの最適化はあまり詳しくないのですが、もしかしたら、次第にコンパイルされていくのかもしれません。

と、ここまで書いたところで見つけたのですが、

https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Deprecated_and_obsolete_features

あれ、compile()は非推奨なのですね。ということは、やっぱり処理系が最適化の過程でコンパイルしていくのが筋ということですね。 非推奨のcompile()を使うとそれを強制的に動かせるのでベンチマークコードの場合は有利といったところでしょうか。

というわけで結論は、よほどの理由がなければ、inline型を使っておけばよいでしょう。new RegExp()を使ってもパフォーマンスが悪くなるわけではないので、場合によっては使ってもよいでしょう。

ぐるっと回って振り出しに戻った感じですが、個人的にはすっきりしました。

JSONの仕様では、Date型は定義されていません。しかし、今回どうしてもsocial-cms-backendでJSONにDate型を埋め込みたくなりました。

stackoverflowで面白い投稿を見つけました。

http://stackoverflow.com/questions/4511705/how-to-parse-json-to-receive-a-date-object-in-javascript

どうやらMicrosoftが.NET Frameworkで独自に拡張しているようです。

http://msdn.microsoft.com/en-us/library/bb412170.aspx

これは、"\/Date(1234567)\/という文字列をDate型とみなすというものです。正確にはタイムゾーンも指定できるようですが、使わないので省略します。

さて、JSON.parseには第二引数にreceiverを指定して独自のdeserializeができるようです。

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/parse

stackoverflowの投稿はこれを使ったサンプルです。ただし、receiverに届くときにはバックスラッシュのエスケープは消えているので、"/Date(1234567)/"もDate型に変換されます。分かって使っている分にはよいですが、JSONの仕様からは外れ(いや、Microsoftのやり方もハックですが)、独自のコンベンションという感じですね。

参考までに、コードを載せておきます。

function parseJSON(str) {
  function receiver(key, value) {
    if (typeof value === 'string') {
      var match = /\/Date\((\d+)\)\//.exec(value);
      if (match) {
        return new Date(parseInt(match[1], 10));
      }
    }
    return value;
  }

  return JSON.parse(str, receiver);
}

そうそう、例で示した1234567の数値のところは、Date.now()と同じようにミリ秒です。

動画は便利ですね。身体的な技を習得する場合もそうですが、最近ではプログラミングも動画で習得するようですね。自分は使っていませんが、ドットインストールは便利そうです。

さて、本題です。スクリーンキャストをテキストベースで行いたいと思いました。ターミナルだけで手軽にやろうと。

ttyrecというツールを使いました。Ubuntuではaptでインストールできます。

$ apt-get install ttyrec

実行すると新しいシェルが始まるのでそこで一通り作業してexitすると、全て記録されています。とても簡単です。

ttyplayで実行できます。再生中に速度も変えられるので便利。2倍速くらいでもついていけます。

ttyrecで録画したスクリーンキャストをwebにアップする方法を調べました。2つ見つけました。

  • playterm.orgというサイトにアップする
  • jsttyplayというライブラリを使う

初めはお手軽なplayterm.orgを使うつもりでしたが、一つだけ難点が。試してないから確かではないのですが、一度アップすると削除ができない様子なのです。もうちょっと自分で制御したくて、jsttyplayを使うことにしました。しかし、jsttyplayはライブラリなのでいろいろ手を入れないと使えません。まずは、Webサイトが必要です。

今回は、GitHub pagesを使いました。gh-pagesブランチにjsttyplayをコピーして、test.htmlを参考にHTMLを書きました。不具合もいくつかあって、

  • Rewindボタンが動かない(バグ修正した)
  • PlayPauseボタンの動作が不明(非表示にした)
  • fontが相対パスなので、ディレクトリが異なると読み込めず
  • ttyrecordファイルのURLがハードコードされている

などが気になりました。

あと、注意点がいくつかあります。

  • ターミナルのサイズを記録時と再生時で同じにする必要がある。これは、ttyrecの制限。playterm.orgでは80x24を標準としている。
  • TERM=xtermでは途中で固まった。TERM=vt100だと大丈夫。ttyplayは問題ないので、これはjsttyplayの制限。

これらをクリアして見事ttyrecで記録したスクリーンキャストをWebに載せることができました。HTML5 Canvasで動いているようです。ターミナルをエミュレートしている感じでしょうか。かっこいいですね。

jsttyplay_screenshot.png

このシンプルさがいいです。 再生速度変更ボタンはありませんが、簡単に作れそうです。 そのうち暇ができたらちょっと改造してみようかと思いました。

興味がある方はお試しください。

jsdomへのpull request: XHRサポート

  • 投稿日:
  • by

connect-prerendererをちゃんと動かすためにやったjsdomの修正その2をpull requestにしました。

https://github.com/tmpvar/jsdom/pull/654

実はXHR自体は、 node-XMLHttpRequest をそのまま使うだけでほとんど苦労はありませんでした。

大変だったのは、クッキーを引き継ぐところでした。 そもそもXHRを使わない場合も、jsdomはクッキーの引継ぎをサポートしていませんでした。XHRを使わない場合というのは、JavaScriptやCSSや画像ファイルなどを読み込む場合です。

まず、JavaScriptのロード時にもクッキーを引き継ぐようにするコーディングをしてから、XHRでもクッキーを引き継ぐようにしました。XHRはnode-XMLHttpRequestの内部のコードまで理解しなくてはならず、苦労しました。ちょっと強引に実装したため、もしかしたら将来のバージョンのnode-XMLHttpRequestでは動かないかもしれません。

pull requestは無事マージされて、jsdom v0.8.1がリリースされました。興味がある方はお試しください。

https://npmjs.org/package/jsdom

connect-prerenderer もこのバージョンを使うように修正しました。

https://npmjs.org/package/connect-prerenderer

connect-prerendererをちゃんと動かすためにやった 修正をjsdomのpull requestにして欲しいと依頼されました。

unit testを書いて欲しいということなので、書いてみたのですが動かなくて苦労しました。 __defineGetter__と__defineSetter__を初めて使いました。 ECMAScriptではこれらは入っていない(definePropertyを使う)そうですが、nodeでは__defineGetter__と__defineSetter__は使えるようです。

結局、当初やっていた修正では全くダメ(なぜconnect-prerendererが動いたのか不明)で、window.locationまわりのコードを全部書き直しました。 おかげで、全体的により仕様に合った動きをするようになり、立派なpull requestができあがりました。

ちゃんとマージされたようで、めでたしめでたし。

https://github.com/tmpvar/jsdom/pull/650

ちょっと新しいことに挑戦しようと思って、MongoDBを勉強中です。 node.jsから使うモジュールとして、 native driver と、 mongoose をみました。

Railsに慣れている人はODMのmongooseのが受け入れやすいかもしれませんが、 自分は生のJSONをいじりたかったので、native driverを使うことにしました。

http://christkv.github.com/node-mongodb-native/

を読み始めましたが、初めはとってもとっつきにくかったです。 どこから読んでいいか分からなかったです。

チュートリアルの一つ目から読むのがおすすめです。

全体像を理解してからは、Manualが読みやすかったです。 http://mongodb.github.io/node-mongodb-native/api-generated/collection.html が一例ですが、URLにapi-generatedと有るので、ソースコードから生成しているのでしょうか。ちなみに、ページの最初にあるUsageも読めなくはないですが、内容も古く中途半端な感じがしました。

MongoDBのAPIには満足です。JSONをそのまま保存でき、クエリで検索できます。Collectionを事前に生成しておく必要もありません、オンディマンドで作ってくれます。このあたりを便利に使うと、ODMには戻れないかも。まあ、自由度が高すぎるのでそれを嫌うケースもあるとは思います。

ところで、自分がやろうとしたことでできないことがこれまでに一つありました。

クエリもJSONで書くのですが、例えば、

{"product_name":"apple", "color":"red"}

のように書くと、product_nameがappleで、かつ、colorがredのデータを検索することになります。「または」の指定も可能で、

{"product_name":"apple", "$or":{"color":"red", "name":"ringo"}}

のように書けます。しかし、この書き方だと、 「(AまたはB)かつ(CまたはD)」のようなクエリが書けないのです。 うーん、惜しい。

Issueになっているのかと調べようと思いましたが、Issue Trackerの使い方がよく分かりません。nodeのドライバーの問題ではなく、本体の問題だろうとは思うので本体のIssueを探そうとは試みました。

https://jira.mongodb.org/secure/IssueNavigator.jspa

これで合ってますよね。"or"という検索キーワードが論理式と認識されているのですかね。とりあえず、あきらめることにします。

そのうち実装されるといいですね。

RSS Pipesの補完機能としてのRSSリーダーを、HTML5 Canvasのみを使ってどこまでスマホアプリが作れるのか挑戦中です。

Googleリーダがなくなるから、それの代替として作ろうと始めましたが、Googleリーダの期限までには完成しそうにありません。

今日は、RSSの記事のリンクを開く機能を作りました。 CanvasではHTMLは表示できません。そこで、Canvasの外側に独立して iframeを使うことで表示しました。 iframeの表示はjQueryを使いました。KineticJSのようなpure JavaScriptを使っていると、DOM操作はちょっと古びたものに感じます。

さて、RSSのリンクを開くためのボタンも作ってみました。 右上の方に矢印のアイコンを置いて、それを押すとiframeが開きます。 その矢印アイコンですが、Kineticのshapeで作成するのは面倒だったので、inkscapeで作成してSVG DataをKineticのpathで読み込みました。

コードはこんな感じです。

var path = new Kinetic.Path({
  x: 0,
  y: -6,
  data: 'M 23.618434,50.171286 41.144607,36.346769 23.23155,22.400304 l 0.0092,8.18132 c 0,0 -7.445838,-1.03921 -11.864782,3.095364 -4.4188829,4.134559 -4.3930939,13.742678 -4.3930939,13.742678 0,0 2.442808,-4.269676 6.9240349,-5.841822 4.481182,-1.572147 9.583341,-0.746586 9.583341,-0.746586 l 0.127874,9.340028 z',
  fill: '#f0f0f0',
  scale: 0.43
});

全コードはこちらです。

https://github.com/dai-shi/canvas-rss-reader/

実行サンプルはこちらです。

http://canvas-rss-reader.herokuapp.com/

未読既読の機能までできたら、とりあえずは使えるようになるかと思うのですが、どうでしょう。

RSS Pipesの補完機能としてのRSSリーダーを、HTML5 Canvasのみを使ってどこまでスマホアプリが作れるのか挑戦中です。

今回は、フリックで横スライドに挑戦しました。 しばらく時間が空いてしまったのは、どうやって実装するかイメージがわかなかったからです。 試行錯誤した結果、比較的満足いくものになりました。

実装するときに気にしているのは、 普通のスマホアプリと同じくらいの操作性を目指しつつ、KineticJSの機能をフル活用して少ないコーディングで実現することです。 横スライドも、縦スクロールと同様にドラッグ&ドロップの機能を活用しました。 なので、正確にはフリックを判定しているわけではなく、ドロップ後の位置でスライドするかどうか判定しています。 ちょっと使ってみると、直感と異なることもあるのですが、それは今後の課題ということで。

今回、Canvasでアプリを作る時に意識をしているのは、軽快さです。 普通に作ったアプリより軽く感じるWebアプリを作れるのかがポイントです。 現状はまだ機能が少ない(そもそも、まだRSSのURLが開けない)ですが、 自分のスマホではかなり軽快にスライド動作ができるようになっています。

他のスマホではどうなのか気になります。 ぜひ、お持ちのスマホで試してみてください。 下記の実行サンプルをスマホのブラウザで開くだけです。

http://canvas-rss-reader.herokuapp.com/

全コードはこちらです。

https://github.com/dai-shi/canvas-rss-reader/

この時点のコードを参照するには、本日のコミットを参照してください。 上記実行サンプルは、今後最新版に変わっていってしまいますので、ご注意を。


6/23追記。

大切な発見を書くのを忘れていました。TweenのonFinishは呼ばれないことがあってはまりました。Tweenでノードを動かしている時に、ドラッグ&ドロップをすると、終了時にonFinishが呼ばれませんでした。setTimeoutを使うことで解決しました。KineticJSのバージョンは、4.5.2です。この仕様が将来のバージョンでどうなるかは不明です。

JavaScriptでランダムに色を生成したいと思いました。

乱数はMath.random()で生成できます。 これを使って、RGBの値を生成することはできますが、 安直に生成すると灰色っぽい色になってしまいます。

ここはやはり、明るさや彩度は固定して色相だけどランダムにしたいところでしょう。

JavaScriptで色を操作するライブラリを探しました。

http://stackoverflow.com/questions/8022885/rgb-to-hsv-color-in-javascript

を参照して、見つけたよさそうなライブラリ2つ。

TinyColorは現在もアクティブに開発されているようで、安心できます。 一方、color.jsは3年前のコミットが最後です。正確には、README.mdは更新されているので、放置されているわけではなく、コードが枯れているのだと思います。

今回は、color.jsを使いました。理由は、今回やりたことについては十分機能があり、コードもきれいで短いからです。

多機能を求める場合は、TinyColorを使うと思います。こちらは、node.jsでも使えるように書かれています。

ランダムに色を生成するには、次のようにしました。

function genRandomColor() {
  var hue = Math.random();
  var saturation = 1.0;
  var lightness = 0.5;
  return Color.hsl(hue, saturation, lightness).hexTriplet();
}

色操作のライブラリはもっと探せばいろいろあるかもしれません。また、機会があれば。

RSS Pipesの補完機能としてのRSSリーダーを、HTML5 Canvasのみを使ってどこまでスマホアプリが作れるのか挑戦中です。

以前、RSS取得を実装したので、今回はそれっぽく表示してみることにしました。

まずは、RSSのタイトルと概要をリスト表示するだけです。 一つのアイテムあたりに80pxの高さを使いました。

KineticJSなのかCanvasなのか分かりませんが、フォントサイズをpxで指定するため、手軽にレイアウトできました。

スクロール処理もちゃんと動き、上々です。

しかし、改行の処理が甘いですね。甘いというレベルではなくおかしいですね。このあたり、Canvasでアプリを作るときのつらさかもしれません。はまりそうなので、今回は手を出さないことにしました。

今回は実行結果のスクリーンショットをとりました。

canvas-scn.png

全コードはこちらです。

https://github.com/dai-shi/canvas-rss-reader/

実行サンプルはこちらから。

http://canvas-rss-reader.herokuapp.com/

どのRSSフィードを表示するかもクエリパラメータでURL指定できるのですが、詳しい話はまた今度にします。

AngularJSにはフィルタという便利な機能があります。

チュートリアルの3で解説されています。

http://docs.angularjs.org/tutorial/step_03

動作するデモもあります。

http://angular.github.io/angular-phonecat/step-3/app

試してみたことない人はぜひどうぞ。

さて、このフィルタですが、およそ次のように使います。

<input ng-model="query">
<ul>
  <li ng-repeat="item in items | filter:query">
    {{item.title}}
  </li>
</ul>

この場合、queryが空の場合は全件が出力され、queryを入力し始めると件数が絞られていきます。 これを、queryが空の場合は全件ではなく例えば、10件にしたいと思いました。

AngularJSにはlimitToというフィルタもありますので、それを使うことでできました。

<li ng-repeat="item in items | filter:query | limitTo:limit">

のように変更します。 さて、このlimit変数はどうしましょう。 あまりうまい方法は思いつかなかったのですが、JSで次のようにしました。

$scope.updateLimit = function() {
  if(query.length) {
    limit = 99999;
  } else {
    limit = 10;
  }
};
$scope.updateLimit();

さらに、HTML側を次のようにします。

<input ng-model="query" ng-change="updateLimit()">

これでqueryが空の場合はlimitが10になります。そうでない場合は99999にすることで全件表示しようとしてますが、当然この数字より大きい配列がきたら切れてしまいます。nullとかNaNとか指定できないのかと思いましたが、angular.jsのソースを読んでもそんな例外扱いはありませんでした。 フィルタを条件付にできないのかとも思いましたが、ドキュメントを読む限りでは分かりません。

とりあえず、これでよしとします。トップ10件にする場合は、limitを呼び出す前に並び替えればよいでしょう。

<li ng-repeat="item in items | filter:query | orderBy:orderFunc | limitTo:limit">

それで、同じようにorderFuncを条件に応じて変更するということです。まあ、ここまでするくらいならカスタムフィルタを書いたほうが早いかもしれませんね。

おまけ。

filterやorderByやlimitToなどのことを総称して「フィルタ」と呼ぶのですが、filterも「フィルタ」です。正確には「フィルタのフィルタ」でしょうか。ややこしい。

RSS Pipesの補完機能としてのRSSリーダーを、HTML5 Canvasのみを使ってどこまでスマホアプリが作れるのか挑戦中です。

まだ、スクロールの処理を実装しています。KineticJSでできるだけ簡単にやろうと考えて、ドラッグ&ドロップのイベントをつかってやりました。

スマホで使うには、やはり慣性スクロールが重要です。正しい呼び方は知りませんが、あの指を離してもしばらくスクロールしているあれです。

まじめに物理シミュレーションをしようかとも思ったのですが、使いやすければいいだろうということで感覚的にパラメータ調整しました。KineticJSではここらあたりが限界でしょうか。

ちなみに、iPhoneの複数のアプリでスクロール操作を試してみましたが、アプリによって挙動が違うことに気づきました。例えば、Safariではスクロールが止まるのが早いですが、Twitterではしばらくスクロールしています。今まであまり意識していなかったので気づかなかったです。

あと、スクロール途中にタッチして止めることが相当重要だとうことを再認識しました。

コードは少し長くなってしまったので載せません。あまり特記するポイントはないです。バウンスするところと組み合わせるのがすこしやっかいでした。

全コードはこちらです。

https://github.com/dai-shi/canvas-rss-reader/

実行サンプルはこちらから。

http://canvas-rss-reader.herokuapp.com/

うまくいったらライブラリ化したいと思いつつも、今は決めうちのコードになっています。

RSS Pipesの補完機能としてのRSSリーダーを、HTML5 Canvasのみを使ってどこまでスマホアプリが作れるのか挑戦中です。

今回は、スクロール時のバウンスに挑戦です。スクロールの端で戻るやつです。

米特許商標局、アップルのバウンススクロール特許は無効と予備的判断。対サムスンでも争点

こんなニュースになっているやつです。

さて、KineticJSにはTweenという機能があって、スムーズな動きが簡単に表現できます。ちなみに、Tweenというのは、

http://en.wikipedia.org/wiki/Inbetweening

のことのようですね。 例によって、 KineticJSのチュートリアル を見ながらやったのですが、一つはまったことがあります。

最初にどのドキュメントをみたのか定かではありませんが、今日までKineticJSのv4.4.2を使ってました。しかし、このバージョンではチュートリアル通りのコードを実行するとエラーになります。最新のv4.5.2を使ったら解決しました。

コードはこんな簡単に書けてしまいました。すごいお手軽。

layer.on('dragend', function() {
  var pos = layer.getPosition();
  var newY;
  if (pos.y > 0) {
    newY = 0;
  } else if (pos.y < -layerHeight + stage.getHeight()) {
    newY = -layerHeight + stage.getHeight();
  } else {
    return;
  }
  var tween = new Kinetic.Tween({
    node: layer,
    easing: Kinetic.Easings.StrongEaseOut,
    duration: 0.3,
    y: newY
  });
  tween.play();
});

今回も、実行結果はスクリーンショットでは見せられないので、下記からどうぞ。アニメーション系はスクリーンショットは使えませんね。動画にするのは手間がかかりすぎる気がします。

http://canvas-rss-reader.herokuapp.com/

全コードはこちらです。

https://github.com/dai-shi/canvas-rss-reader/tree/adfe56808e98da9936c690b85bd169aabca4bb13

コミットの日付を見れば分かるでしょうということで、次回からはコミット指定のURLの記載は省略します。

RSS Pipesの補完機能としてのRSSリーダーを、HTML5 Canvasのみを使ってどこまでスマホアプリが作れるのか挑戦中です。

前回の結果、スクロール動作が遅いことが気になりました。

KineticJSを使うとノードの操作やイベント処理などは格段に使いやすくなるものの、描画という点ではオーバーヘッドになっているようです。おそらく、ノード数が増えれば増えるほど遅くなるのでしょう。

http://www.html5canvastutorials.com/kineticjs/html5-canvas-shape-caching-with-kineticjs/

に画像としてキャッシュすることで、高速化する方法が記述されていたので試してみました。その結果、だいぶ改善されました。これなら実用に耐えそうです。しかし、逆にノード毎のイベントハンドラーが使えないことになります。せっかくのKineticJSですが、活用できていないことになってしまうのかもしれません。このあたりは引き続き考えてみましょう。

コードはこんな感じになりました。

function updateRssContent(items) {
  var tmp = new Kinetic.Layer();
  var rect = new Kinetic.Rect({
    x: 0,
    y: 0,
    width: stage.getWidth(),
    height: stage.getHeight(),
    fill: '#000000'
  });
  tmp.add(rect);
  var y = 5;
  $.each(items, function(index, item) {
    var text = new Kinetic.Text({
      x: 5,
      y: y,
      text: item.title,
      fontSize: 12,
      fontFamily: 'Arial',
      fill: '#aaaaff'
    });
    y = y + text.getHeight() + 5;
    tmp.add(text);
  });
  if (y > rect.getHeight()) {
    rect.setHeight(y);
  }
  tmp.toImage({
    width: rect.getWidth(),
    height: rect.getHeight(),
    callback: function(img) {
      var image = new Kinetic.Image({
        image: img,
        x: 0,
        y: 0
      });
      layer.add(image);
      layerHeight = rect.getHeight();
      layer.draw();
    }
  });
}

実行結果はスクリーンショットでは見せられないので、 下記からどうぞ。ただし、数日間以内には新しいバージョンになってしまいます。昨日のバージョンはたった1日の公開でした。

http://canvas-rss-reader.herokuapp.com/

全コードはこちらです。

https://github.com/dai-shi/canvas-rss-reader/tree/9061aeab3ab034beca7fdaa9a472d3f3225671d5

必要であれば、このコミットをチェックアウトして自力で走らせることもできます。

HTML5 Canvasのみを使ってどこまでスマホアプリが作れるのか挑戦中です。 題材はRSSリーダーです。RSS Pipesの補完機能として考えています。

前回に続き、スクロールを実装しました。

その前に、テキストの配置について補足します。前回は、setOffsetを使いました。ちょっと分かりにくかったので、ループの中でy座標をインクリメントするように変更し、背景の高さもあわせて大きくするようにしました。詳しくはコードを参照してください。

さて、本題のスクロール処理についてです。スマホを想定しているので、スクロールバーではなく、タッチ(ドラッグ)スクロールです。はじめは、touchstart/mousedownイベントを使って自力でスクロール処理を書いてみましたが、スクロールするたびにdraw()で再描画をしたところ、とても遅くなってしまいました。

そこで、KineticJSが用意しているDrag&Dropの仕組みを使う方法に変更しました。この方がすっきり書けました。どこまで細かい制御ができるかはこれからのお楽しみです。

コードはこんな感じです。

var layer = new Kinetic.Layer({
  draggable: true,
  dragBoundFunc: function(pos) {
    var newY = pos.y;
    if (newY > 50) {
      newY = 50;
    }
    var minY = -50 - rect.getHeight() + stage.getHeight();
    if (newY < minY) {
      newY = minY;
    }
    return {
      x: this.getAbsolutePosition().x,
      y: newY
    };
  }
});

想像していたより、スクロールがスムーズではありません。Canvasを直接操作した場合と比較していませんが、KineticJSのオーバーヘッドがそれなりにあるのでしょうか。

実行結果はスクリーンショットでは見せられないので、 下記からどうぞ。ただし、数日間以内には新しいバージョンになってしまいます。

http://canvas-rss-reader.herokuapp.com/

全コードはこちらです。

https://github.com/dai-shi/canvas-rss-reader/tree/3d370b3386b7f46116b892ac4cf37d5282d3910a

ところで、スマホで試したら、最後までスクロールできないみたいです。また、スクロールも遅いです。早速、課題浮上かもしれません。続く。