目次


Famo.us を使用してハイパフォーマンスのモバイル UI を作成する

JavaScript アプリのユーザー・エクスペリエンスをネイティブ・コードのアプリ並みにレベルアップする

Comments

2014年春の Famo.us オープンソース UI レンダリング・フレームワークのベータ版公開リリースは、JavaScript 開発者コミュニティーで熱烈に歓迎されました。Famo.us には、JavaScript と Web 技術がモバイル開発シーンにおける主要技術となるのを最後まで妨げているボトルネックを、一部取り除くことが期待されているからです。具体的なボトルネックとしては、応答性の悪い UI、貧弱なユーザー・エクスペリエンス (UX) が挙げられます。

Famo.us は、最大限のフレーム・レンダリング・レートを実現するために、モバイル端末のハードウェア GPU (Graphics Processing Unit) をターゲットとしており、満足のいく UX を実現するための高度な物理エンジンを追加します。モバイル・アプリの UI を作成する場合、Objective-C、Swift、または Java 開発者に比べ、JavaScript 開発者は不利な状況に置かれていましたが、もはやそうではなくなっています。

この記事では、まず Famo.us の基本概念を紹介し、その設計を探った後、実際に動作するいくつかのサンプル・アプリケーションで実践します。そのなかには、Famo.us を使用して開発を行うためのアプリケーション・テンプレートとして使用できる、標準的なモバイル・アプリの UI も含まれています。(完全なサンプル・コードを入手するには、「ダウンロード」を参照してください)。

Famo.us の仕組み

アニメーションは、要素に変更を加えた連続するページ (フレーム) を素早く表示することによって作成されます。1 秒あたりに表示されるフレームの数が「フレーム・レート」です。人間の視覚には応答潜時があることから、フレーム・レートが高ければ、目の錯覚で動いているように見えるわけです (映画と同じ原理です)。Web ページでアニメーションを作成する場合は、フレームごとに要素のスタイル属性 (位置、色、不透明度) を変更します。これらの属性をどれだけ速く更新できるかが、最終的に UI の最大フレーム・レートを決定します。Web アプリケーションとやりとりする場合、ネイティブ・アプリケーションのようにスムーズな動きをする UX を提供するには、60 フレーム/秒 (fps) が最適レートであると見なされています。60 fps を常時維持することができない場合には、ジャーキネスやドロップアウト (総称してジャンク (Jank) と呼ばれます) など、UX に望ましくない影響が及びます。

Famo.us は基本的に、クロスブラウザーでハイパフォーマンスの UI レイアウト兼最適化ライブラリーであり、JavaScript で作成された独自のアニメーション・エンジン兼物理エンジンを備えています。Famo.us は、フレームごとの処理を可能な限り短い時間で実行するように最適化されています (最近の Famo.us ベンチマークでは、Famo.us は標準的なアニメーションについては、パフォーマンス・オーバーヘッドを伴わずに、16.7 ミリ秒のフレームあたりわずか 1 ~ 2 ミリ秒で処理できることが示されています)。したがって、最大限のフレーム・レートでターゲットの UI をレンダリングすることができます (通常は、最低 60 fps)。Famo.us プロジェクトでは、後続のすべてのリリースで、この fps パフォーマンスを維持または改善することに力を尽くしています。

Famo.us での処理に関して説明すると、Famo.us は一般的な DOM 中心のレイアウト処理と 2D/3D アニメーションを、独自のハイパフォーマンス処理で置き換えています。図 1 に、Famo.us の内部構造を示します。ブラウザーの requestAnimationFrame() (rAF) に指定されるコールバック関数によって、フレームごとに Famo.us エンジン (シングルトン・オブジェクト) が呼び出されます。1 秒あたり 60 回ということは、60 fps、つまり 1 フレームあたり 1/60 秒 (16.7 ミリ秒) ということになります。各コールバックは、Famo.us では「エンジン・ティック (engine tick)」と呼ばれています。

図 1. Famo.us ライブラリーでの処理
Famo.us の内部で処理がどのように行われるかを示す図
Famo.us の内部で処理がどのように行われるかを示す図

Famo.us API

Famo.us API によって開発者に提示されるのは、この API 固有の高度な構成可能オブジェクト (サーフェス、ビュー、ウィジェットなど) のセットです。後で作成する Famo.us コードでは、レンダリングするシーン・グラフ (現在、Famo.us では「レンダー・ツリー (render tree)」と呼ばれています) を作成し、イベント処理を関連付けて、アニメーションを指示またはスケジューリングします。

Famo.us シーン・グラフに含まれる各ノードは、「レンダー・ノード (render node)」です。シーン・グラフが処理されると、「レンダー・スペック (render spec)」と「ビルド・スペック (build spec)」という中間表現になります。Famo.us レンダリング・コンポーネントはターゲットの更新を効率化するために、内部でこれらの中間表現を使用します。

レンダリング・コンポーネントは、サポートされるフレーム・レート (現時点では 60 fps) でシーン・グラフを評価して、必要な更新をブラウザーの DOM、Canvas 要素、または WebGL に出力します (Famo.us のベータ版実装でターゲットとしているのは、ブラウザーの DOM のみです)。Famo.us は、同じ画面上にある、サポート対象のターゲットのどれにでも出力するように ― しかも (「マルチモード・オペレーション (multimode operations)」をサポートしているため) 同時に出力するように ― 設計されています。

サーフェスとレンダリング可能要素

Famo.us では、「レンダリング可能要素 (renderable)」を構成してアニメーション化します。開発者が扱う中で最も基本となる共通のレンダリング可能要素は、Surface です。Surface は、ブラウザーの DOM では <div> 要素として表示されます (Famo.us の Surface を調べると、<div> があることがわかります)。Famo.us のその他の特殊なタイプのサーフェスは、以下のように他の HTML5 要素で表現されます。

  • Surface<div> で表現されます。
  • VideoSurface<video> で表現されます。
  • ImageSurface<img> で表現されます。
  • InputSurface<input> で表現されます。
  • CanvasSurface<canvas> で表現されます。

Surface クラスには content フィールドがあります。このフィールドに、Surface 上でレンダリングする HTML を追加します (<div> の中に追加します)。

Famo.us は、content (HTML としてレンダリングされるストリング) を取得する方法や場所には干渉しません。したがって当然、このストリングがテンプレート、データ・ソース、またはデータ・バインディング・テクノロジーの統合ポイントとなります。さらに、レンダリングされる Surface 上で HTML によって形成されるミニ DOM を操作することもできます。ただし、レイアウト、内包、アニメーション・ワークについては、Famo.us で処理する必要があります。

ページの DOM を直接モードで操作する代わりに Famo.us でプログラミングすると、パフォーマンスもレンダリングの柔軟性も向上します。

3D レンダリング・ライブラリーとの比較

こうした Famo.us のアーキテクチャーは、Three.js や SceneJS をはじめとする WebGL の 3D レンダリング JavaScript ライブラリーのアーキテクチャーと似ています。両ライブラリーの類似点と相違点 (以下に記載) を把握しておくと、Famo.us を短時間でマスターするのに役立つはずです。

  • どちらのアーキテクチャーでも、フレームのレンダリングをトリガーするのは、ブラウザーの rAF です。
  • 3D レンダリング・ライブラリーでは、三角形、2D オブジェクト (例えば、壁や床など)、そしてさらに高度な 3D 形状 (錐体、球体、立方体、または多角形のメッシュなど) をシーン・グラフ内に位置決めして配置します。Famo.us ではシーン・グラフ内に、2D サーフェスまたはそれよりも高度なオブジェクト (ビューやウィジェットなど) を位置決めして配置します。
  • 3D レンダリング・ライブラリーでオブジェクトを変換するには、そのオブジェクトがリーフとなっているシーン・グラフに変換ノードを追加して、ノード間を連結します。Famo.us の場合は、サーフェスやその他のレンダリング可能要素がリーフとなっているシーン・グラフに、モディファイアー (modifier) ノードを追加して連結します。
  • SceneJS では、JSON を使用して複合シーン・グラフを指定することや、グラフ・ノードの ID 属性を指定してその複合シーン・グラフを変更することができます。Famo.us には、JSON を使用して複合シーン・グラフを作成できるように、Scene コンポーネントが用意されています。このコンポーネントを使用して作成した複合シーン・グラフは、レンダー・ノードの ID 属性を指定して変更することができます。
  • 3D ライブラリーでは、rAF によるコールバックの時点で、オブジェクトの属性がアニメーション化されてトゥイーニングされます。Famo.us では、ブラウザーの rAF によるコールバックによって駆動される Famo.us エンジン (engine) のティックごとに、サーフェス (一般的に言うと、レンダリング可能要素) のプロパティーが更新されてトゥイーニングされます。
  • 3D ライブラリーには、より写実的なアニメーションを作成するために物理エンジンが組み込まれているのが一般的です。Famo.us には、ユーザーにとってより直感的なアニメーションとなるように、完全な物理エンジンが組み込まれています。
  • 3D シーンのすべてのオブジェクトは三角形で構成されていますが、開発者が個々の三角形を扱わなければならいことはほとんどありません。それは、3D レンダリング・ライブラリーには一般に、球体や錐体などのよく使われる形状のメッシュが含まれているためです。Famo.us の基本的なレンダリング可能要素はサーフェスですが、このライブラリーにはそれよりも高度な既製の UI ビューおよびウィジェットが多数含まれているため、開発者が個々のサーフェスを扱う必要があるのはまれなことです。
  • 3D ビューポートのレンダリング場所である Canvas 要素を除けば、HTML が 3D ライブラリーで果たす役割はありません。Famo.us では、HTML がサーフェスのレンダリング場所となりますが、UI ページの構造は、これまでのように HTML で定義されるのではなく、JavaScript コードに指定されます。したがって、レイアウトを制御するために大量の <div> タグや <table> タグを管理する必要はありません。CSS は要素のスタイルに変更を加えるためだけに使用され、レイアウトには使用されません (ただし、DOM がターゲットとなっている場合、すべての可視要素は HTML の要素であるため、HTML はやはり重要です)。
  • 一般に、3D ライブラリーのレンダリング・コンポーネントがターゲットとするのは、Canvas、WebGL、および SVG 出力です。なかでも、3D レンダリング GPU ハードウェアにほぼ直接アクセスできる、WebGL が好まれています。Famo.us のレンダリング・コンポーネントがターゲットとするのは、Canvas、DOM、WebGL です。Famo.us の DOM アップデーターは、主要なブラウザーがそのパフォーマンス目標を達成するために提供している既知の GPU 最適化を使用します。

Famo.us を扱う

実際の Famo.us アプリケーションを見ると、Famo.us の概念を理解するのに役立ちます。図 2 のサンプル・アプリケーションは、Famo.us スターター・キットに付属しているデフォルト・アプリケーションであり、generator-famous パッケージ (「Famo.us を実行する 3 つの方法」を参照) からもデフォルトで生成されます。このアプリケーションは、Famo.us ロゴをレンダリングしたものを y 軸を中心に回転させるだけのアプリケーションです。

図 2. generator-famous によって生成されるデフォルト・アプリケーション
ロゴを回転させるデフォルト Famo.us アプリケーションのスクリーン・キャプチャー
ロゴを回転させるデフォルト Famo.us アプリケーションのスクリーン・キャプチャー

main.js では、リスト 1 に示すコードによって単独の ImageSurface が作成されます。

リスト 1. ImageSurface (DOM の <img> にレンダリング) の作成
var logo = new ImageSurface({
    size: [200, 200],
    content: '/content/images/famous_logo.png',
    classes: ['backfaceVisibility']
});

リスト 1 の content オプションは、<img> タグの src 属性に相当します。classes オプションは、CSS クラスを HTML 要素に追加します。backfaceVisibility は、ユーザーに回転するロゴの裏側を表示するための CSS クラスであり、app.css に以下のように定義されています。

.backfaceVisibility {
  -webkit-backface-visibility: visible;
  backface-visibility: visible;
}

「コンテキスト (Context)」(DOM のノードに関連付けられているミニ DOM マネージャー) は、ノードに含まれるすべての DOM によって表現される 1 つのシーン・グラフを管理します。通常 (複数のパースペクティブやヘッドアップ・ディスプレイなどの特殊なプロジェクトに取り組んでいるのでなければ)、使用するコンテキスト・インスタンスは 1 つだけです。

「モディファイアー (Modifier)」は、Famo.us シーン・グラフのレンダー・ノードであり、下位にあるレンダー・ノードの一部の属性 (通常は、変換を適用する属性) を変更します。複数のモディファイアーを連結することもできます。その場合、変換行列が結合されて、シーン・グラフのリーフが必ずレンダリング可能要素になります。

この例では、centerSpinModifierorigin プロパティーと transform プロパティーが含まれています。origin プロパティーは、その要素の親に相対して指定されます。この場合、親に該当するのは Famo.us コンテキストです。図 3 に、Famo.us の origin を指定する際の規約を示します。

図 3. origin プロパティーの設定規約
origin プロパティーの設定規約を示す、各位置にラベルが付けられた 3x3 のグリッ
origin プロパティーの設定規約を示す、各位置にラベルが付けられた 3x3 のグリッ

図 3 に示されている 3x3 のグリッドには、9 つのポイントが配列されています。[0.0] は左上に位置し、[1,1] は右下に位置します。中心は [0.5, 0.5] です。この 3 つの規約に、他のポイントが従います。例えば、[1,0] は右上に配置され、[0,1] は左下に配置されるなどです。

centerSpinModifier に含まれる transform 属性は、Transform.rotateY() を介して、y 軸を中心に回転させる関数を返します。

  var initialTime = Date.now();
  var centerSpinModifier = new Modifier({
      origin: [0.5, 0.5],
      transform : function() {
          return Transform.rotateY(.002 * (Date.now() - initialTime));
      }
  });
mainContext.add(centerSpinModifier).add(logo);

上記のコードにより、図 4 に示す単純なシーン・グラフになります。

図 4. デフォルト・アプリケーションのシーン・グラフ
デフォルト・アプリケーションのシーン・グラフを示す図。上から下の順に、「コンテキスト (Context)」ノード、「回転モディファイアー (Rotation modifier)」ノード、「ロゴ ImageSurface (logo ImageSurface)」ノードが示されています。

これで Famo.us エンジンは、インメモリー・シーン・グラフを評価するとともに、rAF に設定された 1 秒あたり約 60 回という頻度でのフレームごとの DOM の更新を効率的に行うようになります。更新のたびに、centerSpinModiferinitialTime からの経過時間をチェックして、y 軸を中心にロゴを少しずつ回転させます。回転速度は、定数 0.002 を調整して簡単に変えることができます。

以上の説明をまとめると、作業の流れは以下のようになります。

  1. logoImageSurface を作成します。
  2. 以下の処理を行う Famo.us の Modifier を作成します。
    • コンテキストの中央 (origin = [0.5 0.5]) で自転するロゴを設定します。
    • y 軸を中心に徐々に回転させる transform を使用します。
  3. Modifier をシーン・グラフに追加した後、Modifier の直下に logo ImageSurface を追加します。
  4. Famo.us エンジンによってシーン・グラフが処理され、rAF に設定された頻度で DOM が更新されて、ロゴがノンストップで回転します。

サンプル・アプリケーションの拡張

デフォルトのサンプル・アプリケーションを拡張するために、次のサンプル・アプリケーションではロゴの 100 個のインスタンスを 10x10 の正方形の形になるように並べ、x 軸中心で回転するロゴと y 軸中心で回転するロゴを交互に配置します。図 5 に、完成版のアプリケーションが動作している様子を示します。

図 5. 100 個のロゴ・インスタンスを回転させる拡張アプリケーション
100 個のロゴ・インスタンスを回転させる拡張アプリケーションのスクリーン・キャプチャー
100 個のロゴ・インスタンスを回転させる拡張アプリケーションのスクリーン・キャプチャー

図 6 に、このサンプル・アプリケーション用に作成しなければならないシーン・グラフを示します。

図 6. 拡張アプリケーションのシーン・グラフ
複数のロゴを回転させるアプリケーションのシーン・グラフを示す図
複数のロゴを回転させるアプリケーションのシーン・グラフを示す図

このバージョンは、前のサンプル・アプリケーションを以下のように拡張していることがわかるはずです。

  • シーン・グラフの分岐は 1 つだけではなく、100 個に増えています。
  • 各分岐のモディファイアーが 2 つに増えています。ロゴを最初に必要な位置に移動するための変換モディファイアーが作成されて、回転モディファイアーの前に追加されています。

モディファイアーを作成して、各ロゴのコンテキストに追加するコード (リスト 2 を参照) は、最初のサンプル・コードと同様です。このバージョンには、各フレームをアニメーション化して更新するための計算処理が含まれています。

リスト 2. 100 個のロゴを回転させるように拡張されたサンプル・コード
var mainContext = Engine.createContext();
var initialTime = Date.now();
function rotY() {
        return Transform.rotateY(.002 * (Date.now() - initialTime));
    }
function rotX() {
        return Transform.rotateX(.002 * (Date.now() - initialTime));
    }

for (var i=0; i< 10; i ++)
  for (var j=0; j<10; j++) {
    var image =
        new ImageSurface({
            size: [50, 50],
            content: '/content/images/famous_logo.png'
        });
    var transMod =
       new Modifier({
              size: image.getSize.bind(image),
              transform: Transform.translate(j * 50, i * 50, 0)
            }
        );

    var rotMod =
        new Modifier({
            origin: [0.5, 0.5],
            // xor
            transform : (((i % 2) !== (j % 2)) ?  rotY  : rotX)
        });

    mainContext.add(transMod).add(rotMod).add(image);

このアプリケーションでは、y 軸を中心に回転させる rotY と、x 軸を中心に回転させる rotX という 2 つの変換関数を使用しています。シーン・グラフの分岐は、ネストされた i-j ループで作成します。各分岐に追加した 2 つのモディファイアーの名前は、transMod (ロゴ画像を配置する変換) と rotMod (ロゴを自転させる回転) です。

x 軸中心の回転と y 軸中心の回転を交互に配置するために、rotModtransform 属性は、以下のように変更されています。

transform : (((i % 2) !== (j % 2)) ?  rotY  : rotX)

最初のサンプルと同じように、シーン・グラフをインメモリーでセットアップすると、Famo.us エンジンがそれを処理して rAF に設定された頻度で DOM を更新します。

アニメーションのトランジションとトゥイーン

UI を作成する際には、有限時間のアニメーションを扱うのが一般的です。有限時間のアニメーションの一例としては、ユーザーがスクロール・リストの末尾に達すると目にする「バウンス」が挙げられます。また、裏返されたトランプのカードをめくって、表を見せるという例もあります。

Famo.us でトランジションをサポートするために使用しているのは、Transitionable クラスです。このクラスが表す属性によって、一定期間にわたるトランジションを実現することができます。次のサンプル・アプリケーションに示すのは、トゥイーンによるトランジション (トゥイーン・トランジション) を 2 つ使用する例です。

このアプリケーションを実行すると、ページ中央の Famo.us スクロール・ビュー内に developerWorks の記事のリストが表示されます。最初の 10 秒間、このリストは y 軸を中心に一定の速度で回転します。その後、急に元に戻った勢いで 10 秒間 y 軸を中心にバウンスします。この有限時間のアニメーションが実行されている間は、いつでも (デスクトップ・ブラウザーを使用している場合は、マウスのホイールを使って) リストをスクロールすることができます。図 7 に、実行中のアプリケーションを示します。

図 7. トゥイーン・トランジションによってアニメーション化されていて y 軸を中心に回転する、記事のスクロール・リスト
回転する記事のリストのスクリーン・キャプチャー
回転する記事のリストのスクリーン・キャプチャー

Famo.us には、記事のスクロール・リストのようなビューが、高度な Famo.us コンポーネントとしてあらかじめ用意されています。これらのコンポーネントを構成することで、簡単に UI を作成することができます (このサンプルでは、ScrollContainer を使用しました)。Famo.us のビューとウィジェットについては、次のサンプル・アプリケーションで詳しく探ることとして、現時点では、スクロール・リストが一連の順序付けられた Famo.us の Surface で構成されていることを理解しておけば十分です。リスト 3 に、このリストを作成するコードを記載します。

リスト 3. 記事のスクロール・リストの作成
function createArticlesList() {
        artListSVC = new ScrollContainer({
            scrollview: {direction: Utility.Direction.Y}
        });
        var lines = [];
        artListSVC.sequenceFrom(lines);

        for (var i in articles)  {
            var surf = new Surface({
                content: '<div class"a-title">' + articles[i].title + '</div>',
                size: [undefined, 50],
                properties: {
                    textAlign: 'left',
                    color: 'black'
                }
            });
            surf.addClass('article-cell');
            surf.addClass('backfaceVisibility');
            surf.artIdx = i;
            surf.pipe(eh);
            lines.push(surf);
        }
        artListSVC.container.addClass('backfaceVisibility');
        artListSVC.container.addClass('borderbox');

    }

リスト 3 では、Famo.us の Surface を作成して、lines という名前を付けています。作成する Surface ごとに、1 つの developerWorks 記事の名前を表示します。artListSVC という名前の Famo.us ScrollContainer を作成し、その sequenceFrom() メソッドを使用して、lines 配列からなるスクロール・リストを構成します。

トゥイーン・トランジションのプログラミング

artListSVC のようなビューも、同じくレンダリング可能要素です (レンダリング可能要素をリーフとして持つ自身の内部シーン・グラフを管理します)。1 つ以上のモディファイアーを使用してビューを変換し、これまでのサンプル・コードと同じようにして、コンテキストのシーン・グラフに追加することができます。artListSVC をコンテキストに追加するコードは、以下のとおりです。

var sm = new StateModifier({align:[0.5, 0.5], 
         origin: [0.5, 0.5]});
mainContext.add(sm).add(artListSVC);

StateModifier は、一定期間にわたる状態を維持するモディファイアーです (内部で Transitionable を使用)。トゥイーン・トランジションによってアニメーション化する場合は、最初と最後の状態 (キー・フレームとも呼ばれます) だけを指定します。トゥイーン・トランジションが中間状態の値を補間して、ティックごとにレンダリング・エンジンに渡します。したがって、独自のコードで中間状態を計算したり、維持したりする必要はありません。

リスト 4 に、トゥイーン・トランジションをプログラミングするコードを記載します。

リスト 4. トゥイーン・トランジションによるアニメーション化
Transitionable.registerMethod('tween', TweenTransition);
・ス

sm.setTransform(Transform.rotateY(Math.PI), {method: 'tween', 
        curve:'linear', duration:10000},
        function() {
              sm.setTransform(Transform.rotateY(2 * Math.PI), 
               {method: 'tween', duration: 10000,
                curve: 'spring'});
           });

リスト 4 のコードはまず、TweenTransitiontween メソッドとして Transitionable に登録します。次に、StateModifiersetTransform() メソッドを使用して、トゥイーニングされた rotateY 変換を追加します。set Transform() メソッドは、1 番目の引数として変換方法を取り、2 番目の引数として Transitionable を、3 番目の引数として完了コールバック関数を取ります。

リスト 4 では、最初にアニメーション化されるトランジションは 10 秒間続き、スクロール・リストが一定の速度で y 軸を中心に回転します。この最初のトゥイーンが完了した時点でコールバックが呼び出され、2 番目のトゥイーンが開始されます。このトゥイーンは spring 曲線によって突如逆方向に回転し、次の 10 秒間 y 軸を中心にバウンスします。

TweenTransition を明示的に登録する必要はありません。Famo.us は、Transitionablemethod 属性が指定されていない場合、デフォルトで TweenTransition を使用するからです。しかし、リスト 4 には、別のトランジションメソッド (Famo.us 物理エンジンによるトランジションなど) を登録する場合に備えて、登録方法を示しています (Famo.us 物理エンジンについての説明は、この記事では行いません)。

レンダリング・パースペクティブ

ユーザーがピクセル単位で指定するレンダリング・パースペクティブは、ビューアーの「カメラ」とレンダリングされるシーンとの間の距離に関連します。この値を設定するために使用するのは、Context.setPerspective() です。小さい値は、同じ視野を維持しながら、レンダリング対象のオブジェクトにビューアーを近づけるため、カメラの広角レンズのような効果をもたらします。パースペクティブをいろいろと変えることによって、さまざまなアニメーションの見た目をより良いものにすることができます。このサンプル・アプリケーションでは、より印象的な効果にするために、パースペクティブを 500 に設定しています。

標準的なモバイル・アプリの UI への Famo.us の適用

これまでのサンプル・アプリケーションは、純粋なオブジェクトのアニメーションであり、2D サーフェスを使用するという点を除き、Famo.us を 3D レンダリング・ライブラリーのように扱ってきました。次のサンプル・アプリケーションでは、このスタイルの構成とアニメーションが、モバイル UI の作成にも通用することを確認します。

図 8 に示すように、一般的なモバイル・アプリのレイアウトでは、一番上にナビゲーション・バーがあり、UI の状態に応じて「Back (戻る)」ボタンや「More (詳細)」ボタン (図 8 には示されていません) がアクティブになります。一番下にあるタブ・バーは、一連のトグル・ボタンで構成されています (各ボタンは、CSS を使用してスタイルを設定することができます)。これらのボタンは、コンテンツ領域に表示するアイテムを選択できるようになっています。コンテンツ領域は中央にあり、端末のサイズに応じて柔軟にサイズが変更されます。

図 8. Famo.us でのモバイル・アプリの UI の構成
標準的なモバイル UI レイアウトを注釈付きで示すスクリーン・キャプチャー
標準的なモバイル UI レイアウトを注釈付きで示すスクリーン・キャプチャー

一般的なアプリで使用されている、指でスクロールできるアイテム・リストでは、ユーザーがさらに詳細を調べることができます。このサンプル・アプリケーションでは、developerWorks の記事のリストを表示するか、オープンソースの動画のリストを表示するかを選択できるようになっています。

ユーザーがリストのアイテムのいずれかを選択すると、コンテンツ領域が変更されて、選択されたアイテムが表示されます。アイテムは、コンテンツ領域の範囲内に表示されることもあれば (つまり、ヘッダーとフッターは表示されたままになります)、全画面に表示されることもあります (この場合、ヘッダーとフッターが見えなくなります)。

アプリを実際に試してみる

モバイル・ブラウザーでアプリを実行してみてください。

アプリを起動すると、記事のリストが表示されます (図 9 を参照)。

図 9. アプリでの developerWorks の記事リストの表示
記事のリストを表示するサンプル・アプリケーションの画面のスクリーン・キャプチャー
記事のリストを表示するサンプル・アプリケーションの画面のスクリーン・キャプチャー

タブ・バーの「Videos (動画)」ボタンにタッチすると、オープンソースの動画リストが表示されます (図 10 を参照)。

図 10. アプリによるオープンソース動画リストの表示
動画のリストを表示するサンプル・アプリケーションの画面のスクリーン・キャプチャー
動画のリストを表示するサンプル・アプリケーションの画面のスクリーン・キャプチャー

リストに示されているいずれかの動画にタッチすると、動画がロードされてコンテンツ領域で再生されます (図 11 を参照)。

図 11. アプリによる動画の再生
選択された動画を再生しているアプリのスクリーン・キャプチャー
選択された動画を再生しているアプリのスクリーン・キャプチャー

図 11 で、ナビゲーション・バーに「Back (戻る)」ボタンが表示されていることに注意してください。このボタンにタッチすると、動画のリストが再表示されます。「Articles (記事)」にもう一度タッチすると、記事のリストが再表示されます。今度は、いずれかの記事の名前にタッチしてください。すると、選択された記事がアプリによってロードされ、表示されます (図 12 を参照)。

図 12. 選択された記事を表示するアプリ
選択された記事を表示しているアプリのスクリーン・キャプチャー
選択された記事を表示しているアプリのスクリーン・キャプチャー

Famo.us のビューとウィジェットの使用方法

Famo.us のビューとウィジェットを組み合わせると、簡単にモバイル・アプリの UI を作成することができます。

サーフェス (およびレンダリング可能要素全般) とビューは、ウィジェットに組み込むことができます。ビューには、管理対象レンダリング可能要素の間での複雑なオーケストレーションおよび相互作用ロジックを含めることができます。ビューは Famo.us のイベント処理サポートを使用することで、イベントを受信、処理、送信することができます。ウィジェットは、それ自体がレンダー・ノードであるため、コンテキストのシーン・グラフのリーフとして追加することができます。Famo.us には、すぐに使用できる以下のビューとウィジェットの選択肢が用意されています。

  • Scrollview は、レンダリング可能要素の (x 方向または y 方向の) シーケンシャル・リストを制御します。このビューにより、(通常は一連のサーフェスを使用して) タッチ操作またはマウス操作でリストをスクロールできるようになります。
  • HeaderFooterLayout が管理するレンダリング可能要素は、指定されたサイズのヘッダーとフッター、そして可変サイズのコンテンツ領域の 3 つです。サンプル・モバイル UI のレイアウトには、このビューが使用されています。
  • EdgeSwapper は、複数のレンダリング可能要素の表示を管理するコンテナーで、親要素の端から子要素をスライドインさせます。サンプル・モバイル UI では、このビューを使用して 2 つのスクロール可能リストを表示しています。
  • ScrollContainerScrollview を収容するビューであり、さらには ScrollView に表示されるコンテンツのクリッピングに使用される管理対象 Surface も収容します。サンプル・モバイル UI では、HeaderFooterLayout のコンテンツ領域で ScrollContainer を使用して、記事または動画のリストを表示しています。

サンプル・モバイル UI では、以下の 3 つのウィジェットを使用しています。

  • NavigationBar は、タイトル・サーフェスと、ナビゲーション・バーの「Back (戻る)」ボタンと「More (詳細)」ボタンを表す 2 つのクリック可能サーフェスの表示を管理するミニ・アプリ・ビューです。このウィジェットは、back イベントと more イベントを送信します。
  • TabBar は、ウィジェットの横方向または縦方向に配置されるバーを管理します (デフォルトは、トグル・ボタンです)。管理対象のウィジェットが選択されると、select イベントでそのウィジェットに対応する ID が送信されます。
  • ToggleButton は、2 つの管理対象サーフェスを表示するボタンで、オンまたはオフの状態になります。

これらの使用可能なビューとウィジェットを使用したモバイル UI は、図 13 に示すようなシーン・グラフになります。

図 13. モバイル・アプリの UI のシーン・グラフ
サンプル・アプリの UI のシーン・グラフを示す図。
サンプル・アプリの UI のシーン・グラフを示す図。

ツリーのルートにあるのは「コンテキスト (Context)」であることに変わりはありませんが、ツリーのモディファイアーとリーフは識別しにくくなっています。作成された Famo.us ビューごとに、コンポーネントの管理の詳細がカプセル化されて、必要とされるユーザー・インタラクション部分および動作が提供されます。つまり、これらのコーディングをする必要はありません。

このモバイル UI を作成するには、以下のようにシーン・グラフをコーディングします。すると、Famo.us エンジンがそのシーン・グラフを処理して rAF に設定された頻度で DOM を更新します。

  1. 記事のリストを作成します。それには、Famo.us の ScrollContainer に、各記事につき 1 つの Surface (リスト内のセル) のリストを管理する Scrollview を含めます。
    function createArticlesList() {
            artListSVC = new ScrollContainer({
                scrollview: {direction: Utility.Direction.Y}
            });
            var lines = [];
            artListSVC.sequenceFrom(lines);
    
            for (var i in articles)  {
                var surf = new Surface({
                    content: '<div class="a-title">' + articles[i].title + 
                                    '</div><div class="a-desc">' + articles[i].desc + '</div>',
                    size: [undefined, 100],
                    properties: {
                        itemType: 'article',
                        listIndex: i,
                        textAlign: 'left',
                        color: 'black'
                    }
                });
                surf.artIdx = i;
                surf.pipe(eh);
                lines.push(surf);
            }
        }
    function createWebSurface() {
            wb = new Surface(
                );
       
        }

    上記リストで強調表示されている content プロパティーに注目してください。このプロパティーには、リスト内のセルをレンダリングするために使用する HTML と、関連する CSS クラスが設定されます。

    itemTypelistIndex の 2 つは、click イベント・ハンドラーで選択された実際のデータ・アイテムを識別するカスタム・プロパティーです。

  2. 動画のリストを作成します (このコードは、ステップ 1 のコードと同様なので記載しません)。
  3. 選択された記事を表示するための Surface を作成します。
    function createWebSurface() {
            wb = new Surface(
                );
       
        }
  4. 選択された動画を表示するための Surface を作成します。
    function createVideoSurface() {
         vs = new VideoSurface(
             {
                 size: [undefined,undefined],
                 autoplay: true
             }
             );
    
    }
  5. NavigationBar ウィジェットを作成してヘッダーに追加します。
    function addHeader() {
         nb = new NavigationBar({
             size: [undefined, 75],
             content: 'dW Famo.us',
             moreContent: '',
             backContent: '',
             properties: {
                 lineHeight: '75px'
             }
         });
         layout.header.add(nb);
         eh.subscribe(nb);
         eh.on('back', function() {
             rc.setOptions({
                 inTransition: false,
                 outTransition: true
             });
             if (backTarget !== undefined)
                 rc.show(backTarget);
             setNavbarBack(false, undefined);
         });
    }
  6. EdgeSwapper ビューを作成してコンテンツ領域に追加します。このコントローラーが、記事のリスト、動画のリスト、1 つの記事の表示、または 1 つの動画の表示を切り替えます。
    function addContent() {
       rc = new EdgeSwapper({
           overlap: false,
           outTransition: false,
           size:[undefined, undefined]
           });
       layout.content.add(rc);
    
    }
  7. タブ・バーを作成してフッターに追加します。
    function addFooter() {
        var tb = new TabBar({
        });
        layout.footer.add(tb);
        tb.defineSection(0,{content: 'Articles', onClasses: ['tabbuton'], offClasses: ['tabbutoff']});
        tb.defineSection(1,{content: 'Videos', onClasses: ['tabbuton'], offClasses:['tabbutoff']});
        tb.select(0);
        eh.subscribe(tb);
        eh.on('select', function(but) {
           rc.setOptions({
                inTransition: false,
                outTransition: false
            });
    
          switch (but.id) {
          case 0:
            rc.show(artListSVC);
            break;
          case 1:
            rc.show(vidListSVC);
            break;
          }
          setNavbarBack(false, undefined);
        });
    
    }

    CSS の tabbuton クラスがボタンのオン状態のスタイルを設定し、tubbutoff クラスがオフ状態のスタイルを設定します。select イベントのイベント・ハンドラーは、ボタン「0」にタッチされた場合は記事のリストを表示し、ボタン「1」にタッチされた場合は動画のリストを表示します。
  8. 記事のリストをコンテンツ領域に表示します。Scrollview 内での選択によって送信される click イベントに対応するイベント・ハンドラーを追加します。
    function init() {
        rc.show(artListSVC);
        eh.on('click', function(obj) {
                rc.setOptions(
                {
                    inTransition: true,
                    outTransition: false
                });
                var surfaceProps = obj.origin.getProperties();
                if (surfaceProps.itemType === 'article') {
                    wb.setContent('<iframe width="100%" height="100%" src="' + 
                            articles[surfaceProps.listIndex].url + '"></iframe>');
                    rc.show(wb);
                    setNavbarBack(true, artListSVC);
                }
                else
                {   // video
                    vs.setContent(videos[surfaceProps.listIndex].url);
                    rc.show(vs);
                    setNavbarBack(true, vidListSVC);
                }
    
        });
    
    }

インメモリー・シーン・グラフを指定して、必要なイベント・ハンドラーをすべて関連付けた今、Famo.us エンジンでシーン・グラフを処理できる状態になりました。

このサンプル・コードで作成した Famo.us シーン・グラフ (図 13 を参照) をよく調べてください。Famo.us シーン・グラフに修正を加えて、他の情報を選択して表示できるようにするのは簡単で、単にデータ・ソースを変更してスタイルに修正を加えるだけで済みます。さらに、このような修正をパラメーター化して自動化することも可能です。基本的に Famo.us では、「リストを参照して、表示するアイテムを選択する」モバイル・アプリの汎用クラス用に、UI アプリケーション・テンプレートを作成することができます。時間とともに、このようなアプリケーション・テンプレートの包括的なコレクションを作成できるようになり、多種多様なアプリケーション領域を網羅できるようになります。

まとめ

ネイティブ・コードのモバイル・アプリを作成するのは簡単なことではありません。複数のモバイル・オペレーティング・システム (さらにリビジョン間の差異) だけでなく、プラットフォームごとに異なる複数のプログラミング言語と数百ものシステム API を学ぶ険しい学習曲線を辿らなければなりません。独自仕様のツールセットや、カスタマイズされたツールセットに加え、さまざまなビルドおよびデプロイメントのパイプラインと市場に手を広げると、急速に進化を続ける一連のテクノロジーの最新情報を把握してサポートしなければならなくなります。アプリケーションにすぐに使えるボイラープレート・コードがあるとしても、モバイル・プラットフォーム全体でそのコードを新しい問題に適用するには、コーディングとデバッグに費やす時間が、数週間、あるいは数ヶ月にもなりかねません。

その一方、モバイル・アプリ開発を対象とした Web テクノロジーは、クロスプラットフォームであることを約束しているにも関わらず、ネイティブ・コードの UX を実現するまでには程遠い状況となっています。しかし、それはこれまでの話です。Famo.us フレームワークは、ブラウザー最適化における最近のブレークスルーと、3D レンダリング・ライブラリーでの完成度の高い概念を結合することで、モバイル Web アプリケーション用にハイパフォーマンスで使いやすく、かなりの自動化が可能な UI 作成プラットフォームを実現します。今や JavaScript 開発者は、ネイティブ・コードによる実装に匹敵するようなユーザー・エクスペリエンスを提供するモバイル・アプリを簡単に作成することができます。

謝辞

この記事のレビューに協力してくださった Famo.us の Andrew De Andrade 氏と Larry Robinson 氏、そしてインタビューに時間を割いてくださった Jeanne Feldkamp 氏と Steve Newcomb 氏に感謝いたします。


ダウンロード可能なリソース


関連トピック


コメント

コメントを登録するにはサインインあるいは登録してください。

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=60
Zone=Web development, Mobile development
ArticleID=982556
ArticleTitle=Famo.us を使用してハイパフォーマンスのモバイル UI を作成する
publish-date=09112014