こんにちは!スマートフォン版Ameba(通称:デカグラフ)でフロントエンドの開発をしている2012年度新卒入社のオオシモと申します。

デカグラフでは、主にSNS機能(サークル・掲示板・メッセージ・写真など)のNode.jsからフロントエンドのJavaScriptを担当しています。ちなみにエディタはSublime Text 2を使用しています。とっても軽くて高機能でオススメです。

さて、皆さんはフロントエンドのJavaScriptのライブラリは何を使っていますか?多くの方がjQueryを使用しているのではないでしょうか。

デカグラフでは、chikuwa.jsと呼ばれるスマートフォンに特化した軽量ライブラリを使用しています。このライブラリは弊社の首席エンジニアが開発したものです。名前の由来は彼のペットのチワワのチクワちゃんらしいです!?

chikuwa.jsは以下のような特徴があります。

  • ファイルサイズが小さい
  • jQueryと比べて、スマートフォンに必要のないクロスブラウザ対策などの余計な記述がない
  • RouterやViewを使って簡単に画面遷移やコンテンツの出し分けができる
  • モジュール別の大規模開発を前提に作られている

今回はchikuwa.jsの使い方と、それを用いた大規模ウェブサイトのフロントエンドの開発手法を少しばかりお伝えしたいと思います。

DOM操作

デカグラフのほとんどのDOMは、JavaScriptによって生成・操作されています。

セレクタ

セレクタはjQueryとほぼ同様に書くことができます。

// containerというIDを持つdiv要素を選択
$('div#container')
// textというクラスを持つp要素を選択
$('p.text')

DOM生成

DOMの生成には主にtagメソッドとgatメソッドを使用します。

// <div class="container"></div>
$.tag('div.container')

// <div class="container">
//     <p class="text">もけけけ</p>
// </div>

$.tag('div.container')
    .tag('p.text').text('もけけけ').gat();

// また、jQueryと同様にappend/prependメソッドで上記と同様のDOMを生成することができます。
var container = $.tag('div.container');
container.append($.tag('p.text').text('もけけけ'));

イベント

イベントはonメソッドでDOMに登録することができ、第2引数のcallback関数は登録されたイベントの後に呼び出されます。これもjQueryとほぼ同様の使い方ですね。

// containerクラスを持つdiv要素にtouchstartイベントを登録。
$('div.container').on('touchstart', function (event) {
    alert('tapされたよ!');
});
// イベントの削除はoffメソッドを使用します。
$('div.container').off('touchstart');

プラグイン

デカグラフのページ遷移は、プラグインであるchikuwa-view.jsとchikuwa-dispatcher.jsを使っています。

chikuwa-view.js

chikuwa-view.jsではViewオブジェクトを定義することができます。Viewオブジェクトは、ページやページの中の細かいモジュールを作ることができます。


デカグラフのフロントエンドのJavascriptのファイル構成は機能単位で分けられています。

サークル(group.js)を例に取ると、サークルは、サークルTOP・サークル作成・サークル編集などの複数のページを持っています。それぞれのページに対してViewオブジェクトが定義されていて、さらに1つのページはモジュール単位のViewオブジェクトを組み合わせることによって作られています。


JavaScriptファイル構成とViewオブジェクトの構成

1 pixel|サイバーエージェント公式クリエイターズブログ-view

Vewオブジェクトview.group.topは複数のViewオブジェクトから成り立っています。

1 pixel|サイバーエージェント公式クリエイターズブログ-view_component

以下は、サークルのViewオブジェクトのサンプルコードです。

// ページ単位のViewオブジェクトを定義
$.views({
    'view.group.top': {
        init: function () {
            this.dom = $.tag('div').text('サークルTOP');
        },
        render: function () {
            // モジュールをappend
            $.view('view.group.top.myGroup').show(this.dom);
            $.view('view.group.top.pickup').show(this.dom);
            return this.dom;
        }
    },
    'view.group.edit': {
        init: function () {
            this.dom = $.tag('div').text('サークルの編集');
        },
        render: function () {
            return this.dom;
        }
    }
});
// モジュール単位のViewオブジェクトを定義
$.views({
    'view.group.top.myGroup': {
        init: function () {
            this.dom = $.tag('div').text('マイサークル');
        },
        render: function () {
            return this.dom;
        }
    },
    'view.group.top.pickup': {
        init: function () {
            this.dom = $.tag('div').text('ピックアップ');
        },
        render: function () {
            return this.dom;
        }
    }
});
// bodyにview.group.topをappend
// <body>
//     <div>サークルTOP
//         <div>マイサークル</div>
//         <div>ピックアップ</div>
//     </div>
// </body>
$.view('view.group.top').show($('body'));

chikuwa-dispatcher.js

プロフィールのアルバムのページ(https://s.amebame.com/#profile/photos/1234(ユーザーID)?tab=album)へ遷移するとIDが1234のユーザーのアルバムが表示されます。


1 pixel|サイバーエージェント公式クリエイターズブログ-view

ページはハッシュをもとに出し分けられて、ユーザー情報はハッシュの"1234"から、そして写真・アルバムのどちらのタブを表示するかは"?tab=album"をもとに表示されています。ハッシュに"?tab=photo"が含まれている場合は写真のタブを表示します。

このように、どのコンテンツを出すのかはハッシュによって決められていて、chikuwa-dispatcher.jsのDispatcherオブジェクトによって実現されています。


下記のコードはプロフィールのアルバムへ遷移する時のサンプルコードです。

まず、createHashメソッドでhashを作成して、excuteメソッドでhashと一致するRouterのaction関数を呼び出します。Routerはroutesメソッドによって定義されています。第3引数のparamsと第4引数のqueryはaction関数の引数(下記の例ではvars)にセットされています。paramsとqueriesの値からページを出し分けします。

// ハッシュを生成
// controller: profile
// action: photo
// params: {id: '1234'}
// query: {tab: 'album'}
var hash = $.dispatcher.createHash('profile', 'photo', {id: '1234'}, {tab: 'album'});// #profile/photo/1234?tab='album'
// ページ遷移
$.dispatcher.execute(hash);
// 下記のようにRouterが定義されていると、controller名(profile)とaction名(photo)が一致するaction関数が呼び出されます。
// Routerを定義
$.routes('profile', {
    '/photo/:id': {
        name: 'photo',
        action: function (vars) {
            var id = vars.params.id;// 1234:idによってユーザを出し分け
            var tab = vars.queries.tab;// album:どちらのタブを開くかを出し分け
            if (tab === 'photo') {
                // 写真タブを出す処理
            } else if (tab === 'album') {
                // アルバムタブを出す処理
            }
        }
    }
});

基本的には、各JavaScriptファイルにRouterを1つ定義し、ページ毎にactionが用意されています。そして、action関数内でそのページに当たるViewオブジェクトを呼び出します。

ABテスト

デカグラフでは、ユーザーに合わせてより良いコンテンツを見ていただくためにA/Bテストを行なっています。A/Bテストの説明とデザインの手法はこちらの記事の中で説明されています。

そこで、今回はchikuwa.jsを用いた、フロントエンドでのA/Bテストの出し分け方法について説明したいと思います。


下記がA/Bテストのサンプルコードです。基本的には前述したViewオブジェクトとRouterを使って出し分けをしています。まず、A用とB用の複数のViewオブジェクトからなる配列を用意します。ハッシュの中にAが含まれているのならばA用の配列を、Bが含まれているならばB用の配列をforEachでループさせてViewオブジェクトをbodyにappendしています。

$.routes('test', {
    '/:pattern': {
        name: 'top',
        action: function(vars) {
            var pattern = vars.params.pattern;// A or B
            var viewArray = [];// 複数のViewオブジェクトを入れる配列を用意
            if (pattern === 'A') {// Aパターンの場合
                // Aパターンに必要なViewオブジェクトを配列にセット
                viewArray = [
                    'view.a.header',
                    'view.a.body',
                    'view.a.footer'
                ];
            } else if (pattern === 'B') {// Bパターンの場合
                // Bパターンに必要なViewオブジェクトを配列にセット
                viewArray = [
                    'view.b.header',
                    'view.b.body',
                    'view.b.footer'
                ];
            }
            viewArray.forEach(function(item) {
                // それぞれのViewオブジェクトをbodyにappend
                $.view(item).show($('body'));
            });
        }
    }
});

上記のコードのようにRouterとViewオブジェクトを用いることにより、簡単にA/Bテストの出し分けを実現することができます。

最後に

最後までお付き合いいただきありがとうございました。

chikuwa.jsはMITライセンスの規約を守れば誰でもフリーで利用することができます。スマートフォンに特化したWebサイトを開発する際には、みなさんの使用するライブラリの選択肢の1つにいれていただければと思います。