LoginSignup
72
73

More than 5 years have passed since last update.

vueとfirebaseでブラウザチャット

Last updated at Posted at 2017-08-05

ブラウザのチャットを作ります。

成果物

デモ
https://anywaychat.netlify.com

コンセプト

夏休みのプログラミング教室を想定して

  • コマンド打たない
  • 開発環境の構築をしない
  • 多少「こんなん作れんだ!」感があるもの

使うもの

実装

index.html
<html lang="ja">
<head>
    <meta charset="utf8" />
    <!-- cssライブラリの読み込み -->
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css">
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bulma/0.5.0/css/bulma.min.css">
    <!-- 自分で書いたcssの読み込み -->
    <link rel="stylesheet" href="style.css">
</head>
<body>
<div id='app'>

    <!-- ▼画面上部の緑の部分 -->
    <header>
        <h1 class="title is-5">AnywayChat</h1>
        <p class="chaturl subtitle"><a v-bind:href="developerSite" target="_blank">created by {{developerName}}</a></p>
    </header>
    <!-- ▲画面上部の緑の部分 -->

    <div id="message-contents">
        <!-- ▼メッセージ一個ぶんの表示 -->
        <div v-for="message in messageList" class="message-wrapper is-clearfix">
            <div class="box" v-bind:class="{'mymessage' : isMyMessage(message) }">
                <div class="content">
                    <p>
                    <strong>{{message.userName}}</strong> <small>{{displayTime(message)}}</small>
                    <br />
                    {{message.message}}
                    </p>
                </div>
            </div>
        </div>
        <!-- ▲メッセージ一個ぶんの表示 -->
    </div>

    <!-- ▼メッセージ入力部分 -->
    <footer>
        <div class="field is-grouped">
            <div class="control is-expanded">
                <input v-model="message" v-on:keydown.enter="send" class="input is-medium" type="text" placeholder="Message" />
            </div>
            <div class="control control-submit">
                <button v-bind:disabled="message.length==0" v-on:click="send" class="button is-primary button-submit">送信</button>
            </div>
        </div>
    </footer>
    <!-- ▲メッセージ入力部分 -->
</div>

<!-- javascriptライブラリの読み込み -->
<script src="https://unpkg.com/vue"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/babel-standalone/6.24.0/babel.js"></script> 
<script src="https://cdnjs.cloudflare.com/ajax/libs/animejs/2.0.2/anime.js"></script>
<script src="https://www.gstatic.com/firebasejs/4.2.0/firebase.js"></script>
<script src="https://www.gstatic.com/firebasejs/3.8.0/firebase-app.js"></script>
<script src="https://www.gstatic.com/firebasejs/3.8.0/firebase-database.js"></script>

<!-- 自分で書いたjavascriptの読み込み -->
<script type='text/babel' src="script.js"></script>
</body>
</html>
script.js
let app = new Vue({
  el: '#app',

  // 初期化
  created: function () {

    // ユーザ名の設定
    let inputedUserName = window.prompt("ユーザ名を入力してください", "");
    this.userName = inputedUserName

    if(inputedUserName.length == 0){
        // 入力がなかった場合
        let dummyNameList = ['太宰治','三島由紀夫','カフカ','田中角栄', '大塩平八郎', '土方巽', 'アルベルト・アインシュタイン', 'バラモス', 'メタルスライム'];
        this.userName =  dummyNameList[Math.floor(Math.random() * dummyNameList.length)];
    }

    // firebaseの設定
    this.setupFirebase()

    // chatの設定
    this.setupChat()

    // メッセージを読み込む
    this.loadMessage()
  },

  // これ大事
  data: {
    message: ""
    , userName: ""
    , userId: Math.random().toString(36).slice(-8)
    , messageList: []
    , developerName: "nakadoriBooks"
    , developerSite: "https://twitter.com/nakadoribooks"
  },

  // 処理
  methods: {

    // firebaeの設定
    setupFirebase: function(){
        var config = {
            apiKey: "AIzaSyBWasVTMVGAc1c8IlrXIdKYuIN5i8yMk-I",
            authDomain: "nakadorichat.firebaseapp.com",
            databaseURL: "https://nakadorichat.firebaseio.com",
            projectId: "nakadorichat",
            storageBucket: "",
            messagingSenderId: "46816258733"
        };
        firebase.initializeApp(config);
    },

    // チャット読み込み
    setupChat: function(){
        let ref = firebase.database().ref('chats')
        let hash = location.hash

        // chatId があったとき
        if(hash != null && hash.length > 0){
            let chatId = hash.slice( 1 ) ;
            this.chatRef = ref.child(chatId)
            console.log("read chat", chatId)
        }
        // なかったとき
        else{
            let createdAt = this.timestamp()

            // 新しいチャットを作って
            this.chatRef = ref.push()

            // 保存する
            this.chatRef.set({
                createdAt: createdAt
                , createdAtReverse: -createdAt
            })

            location.href = location.origin + location.pathname + "#" + this.chatRef.key
            console.log("create chat", this.chatRef.key)
        }
    },

    // メッセージを送る
    send: function(event){
        if(this.message.length == 0){
            return;
        }

        // 新しいメッセージを作って
        let messageRef = firebase.database().ref('messages').push()
        let createdAt = this.timestamp()

        // 保存する
        let chat = this.chatRef.key
        messageRef.set({
            chat: chat
            , message:this.message
            , userName: this.userName
            , userId: this.userId
            , createdAt: createdAt
            , createdAtReverse: -createdAt
        })

        // 入力エリアリセット
        this.message = ""
    }

    // メッセージを読み込む
    , loadMessage:function(){
        let chatKey = this.chatRef.key
        let loadRef = firebase.database().ref("messages").orderByChild("chat").equalTo(chatKey)

        // 最初に全部取ってくる
        loadRef.once('value').then((snapshot) => {

            var messageList = []            
            snapshot.forEach(function(childSnapshot) {
                var data = childSnapshot.val()
                data.key = childSnapshot.key
                messageList.push(data)
            })

            // 日付でソート
            messageList.sort(function(a,b){
                if( a.createdAt < b.createdAt ) return -1;
                if( a.createdAt > b.createdAt ) return 1;
                return 0;
            });

            // 表示に反映
            this.messageList = messageList

            // 追加の監視
            this.observeMessage()

            // 下までスクロール
            this.scrollToBottom()
        });
    },

    // メッセージの監視
    observeMessage: function(){
        let chatKey = this.chatRef.key
        let chatRef = firebase.database().ref("messages").orderByChild("chat").equalTo(chatKey)
        chatRef.on("child_added", (snapshot) => {
            let newMessage = snapshot.val()
            newMessage.key = snapshot.key
            for(var i=0,max=this.messageList.length;i<max;i++) {
                let message = this.messageList[i]
                if(message.key == newMessage.key){
                    return
                }
            }

            // 表示に反映
            this.messageList.push(newMessage)
            this.scrollToBottom()
        })
    },

    // 下までスクロール
    scrollToBottom :function() {
        setTimeout(()=>{
            let height = Math.max(0, document.body.scrollHeight - document.body.clientHeight)
            anime({
                targets: "body",
                scrollTop: height,
                duration: 200,
                easing: "easeInQuad"
            });
        }, 100)
    },

    // おれのメッセージ?
    isMyMessage: function(message){
        return this.userId == message.userId
    },

    displayTime: function(message) {
        let timestamp = message.createdAt * 1000
        var date = new Date(timestamp)
        var diff = new Date().getTime() - date.getTime()
        var d = new Date(diff);

        if (d.getUTCFullYear() - 1970) {
            return d.getUTCFullYear() - 1970 + '年前'
        } else if (d.getUTCMonth()) {
            return d.getUTCMonth() + 'ヶ月前'
        } else if (d.getUTCDate() - 1) {
            return d.getUTCDate() - 1 + '日前'
        } else if (d.getUTCHours()) {
            return d.getUTCHours() + '時間前'
        } else if (d.getUTCMinutes()) {
            return d.getUTCMinutes() + '分前'
        } else {
            return d.getUTCSeconds() + '秒前'
        }
    },
    timestamp: function(){
        let date = new Date()
        let timestamp = date.getTime()
        return Math.floor( timestamp / 1000 )
    }
  }
})

その他

style.css
/**
 primaryColor: #00d1b2
**/

header{
    position:fixed;
    width:100%;
    background:#00d1b2;
    z-index:2;
    height:55px;
}

#app header h1{
    padding-left:10px;
    padding-top:8px;
    color:#ffffff;
}

.chaturl{
    padding-left:10px;
}

.chaturl a{
    color:#ffffff;
    font-size:14px;
}

.chaturl a:hover{
    text-decoration: underline;
}

footer{
    position:fixed;
    bottom:0px;
    width:100%;
    height:66px;
    border-top:1px solid #aaaaaa;
    background:#ffffff;
}

footer .field{
    padding:10px;
}

.button-submit{
    height:45px;
    line-height:1.0;
    width:100%;
}

.control-submit{
    width:20%;
}

#message-contents{
    z-index:1;
    padding: 55px 10px 76px 10px;
    background:#ffffff;
}

.message-wrapper{
    margin:20px 10px;
}

.message-wrapper .box{
    float:left;
}
.message-wrapper .box.mymessage{
    float:right;
}

.mymessage p{
    text-align: right;
}

時間割

1回2時間を 3回くらいかな
- css/html/jsの雰囲気
- vue の感じ
- アプリケーション実装

72
73
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
72
73