ここ数年、チャット系のアプリが流行っています。代表的なものにLINE、Facebookメッセンジャー、WhatsAppなどが存在します。誰かが書き込んだ内容が即座に反映されてこそ、会話が盛り上がるというものです。
そんなチャットアプリの裏側を支える技術が、リアルタイム通信機能になります。

前回は、リアルタイム通信を実現するWebSocketサーバの概要と導入について解説しました。今回はWebSocketを使って簡易的なチャットアプリを作ってみたいと思います。

WebSocketサーバについて

WebSocketサーバですが、前回の記事でご紹介したgoofmint/ruby-websockets-chat-demoを使っています。[Deploy to Heroku]をクリックするだけで、すぐに自分でサーバが立てられるはずです。

アプリ構成

こちらが出来上がったアプリです。最初にユーザ名を決めます。

ユーザ名を決めるとチャット画面に入ります。今回はメッセージをデータベースに保存していませんので、最初は何もメッセージがない状態からはじまります。

メッセージが届くと、それが即座に画面に反映されます。他の人が書いたメッセージは青で、名前も一緒に表示されます。

自分が書いたメッセージも表示されます。この場合は色は緑、文字は右寄せとしています。

画面について

今回のアプリは2つの画面から構成されています。画面はOnsen UIで実装しています。

まず、画面遷移などの機能を提供する<ons-navigator> コンポーネントを定義しておきます。

<ons-navigator id="navigator" page="page1.html"></ons-navigator>

入室画面

起動して最初に表示されるのがユーザ名を決める入室画面です。

<template id="page1.html">
  <ons-page id="first-page">
    <ons-toolbar>
      <div class="center">入室</div>
    </ons-toolbar>

    <div class="content">
      <p>チャットへようこそ。<br />名前を決めてください。</p>
      <div class="loginform">
        <p>
          <ons-input id="username" modifier="underbar" placeholder="Username" float></ons-input>
        </p>
      </div>
      <ons-button id="push-button">入室</ons-button>
    </div>
  </ons-page>
</template>

チャット画面

次にチャット画面です。メッセージは動的に追加しますのでここでは定義しません。
画面下にテキストボックスとボタンを固定表示しています。

<template id="page2.html">
  <ons-page id="second-page">
    <ons-toolbar>
      <div class="left"><ons-back-button>入室</ons-back-button></div>
      <div class="center">チャット</div>
    </ons-toolbar>

    <div class="content">
      <ons-list id="chats">
      </ons-list>
    </div>

    <div class="send-area">
      <ons-input id="message" placeholder="メッセージ"></ons-input>
      <ons-button id="send" modifier="quiet">送信</ons-button>
    </div>
  </ons-page>
</template>

CSS定義

スタイルシートは以下のように定義しています。

.content {
  text-align: center;
  padding-bottom: 4em;
}

.loginform{
  margin-top: 30px;
}

.send-area {
  padding: 0 5px;
  box-sizing: border-box;
  line-height: 49px;
  position: fixed;
  bottom: 0px;
  width: 100%;
  border-top: solid 1px #ccc;
}

.send-area ons-input {
    display: inline-block;
    position: relative;
    width: calc(100% - 60px);
}

.send-area input {
    vertical-align: middle;
  position: relative;
  width: 98%;
}

JavaScriptについて

ではコードを書いていきます。まずはコメントだけで大まかな流れを紹介します。

// Onsen UIのスタイル定義

// WebSocketサーバの定義

// ページが切り替わる度に呼ばれます。
document.addEventListener('show', function(event) {
  var page = event.target;

  if (page.matches('#first-page')) {  // 入室画面の処理
    // 入室ボタンを押した時の処理

  } else if (page.matches('#second-page')) {  // チャット画面の処理
    // WebSocketでメッセージを受け取った時の処理

    // 送信ボタンを押した時の処理    

  }
}); 

// Onsen UIロード完了時の処理
ons.ready(function() {
  // 入室画面に戻るときの処理

});

Onsen UIのスタイル定義

Onsen UIでは、通常OSに合わせて自動でスタイルが変化します。
今回のチャットアプリはiOS、Androidともに同じスタイルに設定したいので、
はじめに以下のコードを実行してiOSスタイル(フラットデザイン)に統一します。

ons.forcePlatformStyling('ios');

WebSocketサーバの定義

WebSocketサーバはHerokuにデプロイしたものを使います。URLは自分の環境を設定してください。
Herokuにデプロイした場合、WebSockets over SSL/TLSに対応しているのでwssがプロトコルになります。

また、最初はWebSocketオブジェクトを格納する変数にnullを設定しておきます。

// WebSocketサーバの定義
var uri = "wss://your-chat.herokuapp.com/";

var ws = null;  // WebSocketオブジェクト

入室画面の処理

入室ボタンが押されたらWebSocketの接続を確立して、チャット画面に遷移します。
WebSocketオブジェクトの生成と同時に、サーバーへの接続が行われます。

// 入室ボタンを押した時の処理
$('#push-button').on('click', function() {
  // ユーザ名を設定
  username = $('#username').val();

  // WebSocket接続
  ws = new WebSocket(uri);

  // チャット画面に遷移
  document.querySelector('#navigator').pushPage('page2.html');
});

チャット画面の処理 - WebSocketでメッセージを受け取った時

WebSocket経由でメッセージを受け取った時には onmessage イベントが呼ばれます。この時、データが文字列で送られてくるので、受信した際には JSON.parse で復元します。

そして、自分が送り主だった場合はボタンを緑にして右寄せ、他のユーザからのメッセージはデフォルト色のボタンを左寄せ表示します。メッセージの下にユーザ名も表示します。

// WebSocketでメッセージを受け取った時の処理
ws.onmessage = function(message) {
  var data = JSON.parse(message.data);

  // 送信元が自分か他人かで画面のデザインを変更
  if (data.handle == username) {
    $('#chats').append(`
      <ons-list-item modifier="nodivider">
        <div class="right">
          <ons-button style="background-color: green">${data.text}</ons-button>
        </div>
      </ons-list-item>`);
  }else{
    $('#chats').append(`
      <ons-list-item modifier="nodivider">
        <ons-button>${data.text}</ons-button>
        <span class="list-item__subtitle">${data.handle}</span>
      </ons-list-item>`);
  }
};

チャット画面の処理 - 送信ボタンを押した時

メッセージを送信する時は、WebSocketの send メソッドを使います。この時は JSON.stringify を使ってJSONを文字列に変換した上で送信します。

// 送信ボタンを押した時の処理
$('#send').on('click', function(e) {
  // WebSocketで送信
  ws.send(JSON.stringify({ handle: username, text: $('#message').val() }));

  // 元の入力内容は削除
  $('#message').val('')
});

入室画面に戻るときの処理

最後に入室画面に戻るときの処理です。<ons-back-button> の機能によって前の画面に戻りますが、そのタイミングでWebSocketのコネクションを切断します。
prepop は、ページがpopされる直前に発生するOnsen UIのイベントです。

// 入室画面に戻るときの処理
$('#navigator').on('prepop', function() {
  // WebSocket切断
  ws.close();
  ws = null;
});

コード全文は以下のようになります。

// Onsen UIのスタイル定義
ons.forcePlatformStyling('ios');

// WebSocketサーバの定義
var uri = "wss://your-chat.herokuapp.com/";
var ws = null;  // WebSocketオブジェクト

var username;   // ユーザー名

// ページが切り替わる度に呼ばれます。
document.addEventListener('show', function(event) {
  var page = event.target;

  if (page.matches('#first-page')) {  // 入室画面の処理
    // 入室ボタンを押した時の処理
    $('#push-button').on('click', function() {
      // ユーザ名を設定
      username = $('#username').val();

      // WebSocket接続
      ws = new WebSocket(uri);

      // チャット画面に遷移
      document.querySelector('#navigator').pushPage('page2.html');
    });
  } else if (page.matches('#second-page')) {  // チャット画面の処理
    // WebSocketでメッセージを受け取った時の処理
    ws.onmessage = function(message) {
      var data = JSON.parse(message.data);

      // 送信元が自分か他人かで画面のデザインを変更
      if (data.handle == username) {
        $('#chats').append(`
          <ons-list-item modifier="nodivider">
            <div class="right">
              <ons-button style="background-color: green">${data.text}</ons-button>
            </div>
          </ons-list-item>`);
      }else{
        $('#chats').append(`
          <ons-list-item modifier="nodivider">
            <ons-button>${data.text}</ons-button>
            <span class="list-item__subtitle">${data.handle}</span>
          </ons-list-item>`);
      }
    };

    // 送信ボタンを押した時の処理
    $('#send').on('click', function(e) {
      // WebSocketで送信
      ws.send(JSON.stringify({ handle: username, text: $('#message').val() }));

      // 元の入力内容は削除
      $('#message').val('')
    });

  }
}); 

// Onsen UIロード完了時の処理
ons.ready(function() {
  // 入室画面に戻るときの処理
  $('#navigator').on('prepop', function() {
    // WebSocket切断
    ws.close();
    ws = null;
  });
});

以上でチャットアプリの完成となります。
今回作成したのは簡易的なサンプルアプリなので、データベースは利用していません。過去のメッセージは消えてしまいます。本格的なチャットアプリを作る際にはメッセージを保存する仕組みが必要です。
他に必要な機能としては、以下のような機能が挙げられるでしょう。

  • 認証
  • チャットルーム選択
  • 画像アップロード
  • 参加者一覧
  • メッセージ検索

ぜひ今回のコードをベースにしてカスタマイズしてみてください。


チャットは単独のアプリだけでなく、既存のアプリをさらに魅力的にするために追加しても良い機能になります。WebSocketを使うことで、そのための実装はすぐに実現できるでしょう。

今回のコードはmoongift/monaca-with-websocketにアップロードしてあります。実装時の参考にしてください。