「GitHub」と一致するもの

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/

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

最近、ページ内リンクというのはあまり使われないと思いますが、使いたいケースがあったので、調べました。備忘メモです。

ページ内リンクというのは、リンク先に

<a name="id001">

とか

<div id="id001">

とか書いておいて、リンク元を

<a href="#id001">

とすることで、このリンクをクリックするとリンク先にスクロールしてくれるものです。

ところが、AngularJSではこれはルートとみなされ、ご丁寧に#/id001に変換して、$routeProviderの処理がされてしまいます。当然、そのようなルートは用意していないので、予期せぬ動作になります。例えば、otherwiseにredirectToを設定していたりすると、トップページに戻ります。

これは困ったということで、いろいろ調べました。はじめに見つけたのがこれ。

Angular JS - Scrolling To An Element By Id

AngularJSには$anchorScroll()というのが用意されていてその使い方が説明されています。具体的には、$rootScopeの$routeChangeSuccessイベントをフックするのですが、詳しくは、上記サイトをご覧ください。

さて、これを試しましたが、うまくありませんでした。 新しいページを表示してスクロールする分にはいいのですが、同一ページでもリフレッシュされてしまいます。つまり、$routeChangeSccessイベントはルートが新しくなってから呼ばれるものなのです。AngularJSのドキュメントを読むと、$routeChangeStartというイベントもあるのですが、結局、そのイベント処理のあとに、ルートを書き換えてしまいました。

さらに調べると、次のIssueを見つけました。

https://github.com/angular/angular.js/issues/1699

$locationのhashやpathを変更しても、ルートを書き換えない方法を用意してほしいという要望です。この中に書いてある方法では解決しませんでしたが、最後にPRを見つけました。

https://github.com/angular/angular.js/pull/2555

蛇足ですが、このPRは分かりやすく書かれてますね、Basic Usageとか。 でも、結局このPRもリジェクトされてました。 これを許すと、URLの状態とルートの状態が整合しないことができてしまうので避けたいそうです。

悩んだあげく、次の方法で解決しました。

まず、$routeProviderを次のようにします。

$routeProvider.when('/home', {
  templateUrl: 'partials/home.html',
  controller: HomeCtrl,
  reloadOnSearch: false
}).
otherwise({
  redirectTo: '/home'
});

ポイントは、reloadOnSearchです。OnSearchとありますが、hashの変更にも有効のようでした。

その上で、

<a href="#id001">

の代わりに、

<a href="#/home#id001">

とします。まあなんとも普通の方法ですが、これで目的は達成できました。 /homeがハードコードされているのが気になる方は、$locationが使える状況なら、次のようにしましょう。

'<a href="#' + $location.path() + '#id001">'

いかがでしょうか。AngularJSを使いつつ、レガシーなことをやろうとすると大変だ、という例でした。

ちなみに、hrefを使わなくて良いのなら、

Angular JS - Scrolling To An Element By Id

の2つ目の方法がよさそうです。試していませんが。

connect-prerendererのgooglebot対応

  • 投稿日:
  • by

connect-prerendererのStart数が20に到達。すごいのかすごくないのかよく分かりませんが。

今回、Issueが報告されたので、対応しました。

https://github.com/dai-shi/connect-prerenderer/issues/6

AJAXページをクロールするための、Googleの仕様があるらしいです。

https://developers.google.com/webmasters/ajax-crawling/docs/getting-started

仕様は簡単で、hashのすぐあとにエクスクラメーションマークをつけておくと、_escaped_fragment_というクエリパラメータをつけてクロールしてくれるということです。

つまり、

http://aaa.bbb/ccc#!ddd

が

http://aaa.bbb/ccc?_escaped_fragment_=ddd

に変換されて、googlebotからアクセスされることになります。

これは、connect-prerendererの出番です。この変換スキームに対応するように修正しました。詳しくはソースコードを参照することになりますが、使い方は下記のようになります。

var prerenderer = require('connect-prerenderer');
app.use(prerenderer({
  targetGenerator: 'googlebot'
});

この使い方は、まだドキュメントに書いていません。テストができていないからです。

興味ある人いませんか~?

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

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

だいぶ間が空いてしまいましたが、HTML5を使ってどこまでスマホアプリが作れるのか、挑戦しようと進めています。しかも、フルキャンバスで。

題材はRSSリーダーです。RSS Pipesの補完機能として考えています。

前回はHellow Worldを表示するところまででした。今回は、RSSを取得してテキスト表示してみました。ただし、まだスクロールできません。当然クリックもできません。

RSSはjQueryを使って読み込むことにしました。コードはこんな感じです。

$.ajax({
  type: 'GET',
  url: rssurl,
  dataType: 'xml',
  success: function(xml) {
    var $xml = $(xml);
    var items = [];
    $xml.find("item").each(function() {
      var $this = $(this);
      var item = {
        title: $this.find("title").text(),
        link: $this.find("link").text(),
        description: $this.find("description").text(),
        pubDate: $this.find("pubDate").text()
      };
      items.push(item);
    });
    updateRssContent(items);
  },
  error: function() {
    alert('failed to get the rss: ' + rssurl);
  }
});

updateRssContent()でキャンバスにかきますが、座標も自分で計算しないといけません。

function updateRssContent(items) {
  $.each(items, function(index, item) {
    var text = new Kinetic.Text({
      x: 5,
      y: 5,
      text: item.title,
      fontSize: 12,
      fontFamily: 'Arial',
      fill: '#aaaaff'
    });
    text.setOffset({
      y: -index * (text.getHeight() + 5)
    });
    layer.add(text);
  });
  layer.draw();
}

setOffsetを使うよりいい方法があるのではないかと想像しますが、まだ探していません。

実行結果はこんな感じです。

実行結果20130519

最新版はherokuでも実行可能です。

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

今回は、GitHubにコードをアップしました。

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

そう、プロジェクト名もcanvas-rss-readerにしました。

しかし、キャンバスを使うとあたりまえですが文字列選択ができないですね。JavaScriptでクリップボードって制御できるのでしょうか。

http://www.w3.org/TR/clipboard-apis/

このあたりをウオッチすることになるのでしょうか。