17
24

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Node.js+Socket.ioを用いてチャットルームを実装しよう

Last updated at Posted at 2018-01-18

#Node.jsでチャットルームを作りたい
というわけで作っていきたいと思います。
方針としてはNode.jsでlocalhost:3000に鯖を立てて、
そこにHTML/CSS/JavaScriptを適用します。
##ファイル構成
今回の構成は以下の通りです。

- index.js

- index.html

- node_modules
   |
    - ..etc

- pic
   |
    - ..etc
- js
   |
    - window.js
    - socket.js
- css
   |
    - window.css
    
- package.json

##サーバ構築
下記コード内で require('module') をしている
nodeモジュールを npm install などでインストールしてください。

index.js
var http = require('http');
var fs   = require('fs');
var path = require('path');
var mime = {
   ".html": "text/html",
   ".css":  "text/css",
   ".js":  "text/javascript",
   // 読み取りたいMIMEタイプはここに追記
   // MIMEを宣言しないとクライアント側のHTMLに通した
   // JSやCSSが取得できない
};
//socket.ioモジュールの読み込み
var socketio = require("socket.io");

//HTTPオブジェクトを生成
var http_server = new http.createServer(function(req, res {
   if(req.url == '/'){
      filePath = '/index.html';
   }else{
      filePath = req.url;
   }
   //ここで絶対PATHの取得
   var fullPath = __dirname + filePath;
   res.writeHead(200, {"Content-Type": mime[path.extname(fullPath)] || "text/plain"});

      fs.readFile(fullPath, function(err, data){
         if(err){
            //エラー時の応答
         }else{
            res.end(data, 'UTF-8');
         }
    });
}).listen(3000);

console.log('Server running at http://localhost:3000/');

//イベントハンドラを設定
var io = socketio.listen(http_server);
io.sockets.on("connection", function (socket){

   //メッセージ送信(送信者にも送られる)
   socket.on("C_to_S_message", function (data){
      io.sockets.emit("S_to_C_message", {value:data.value});
   });
 
   //ブロードキャスト(送信者以外の全員に送信)
   socket.on("C_to_S_broadcast", function (data){
      socket.broadcast.emit("S_to_C_message", {value:data.value});
   });
 
   //切断したときに送信
   socket.on("disconnect", function (){
      io.sockets.emit("S_to_C_message", {value:"user disconnected"});
   });
});

##HTML/CSS/JavaScriptについて
今回、クライアントサイドとサーバサイドの結合に
少々手間をとりました。
その理由として、

HTMLに紐づけたjsにScoket.ioの
イベントハンドラ設定したらサーバサイドに認識されなくね?

まあこれですね、仕方なくHTMLに素書きしました...。
いい方法、教えていただきたいです(懇願)

###必要なもの
- socket.io.js(socket.js)
https://cdnjs.cloudflare.com/ajax/libs/socket.io/2.0.3/socket.io.js

以上のリンクを参照するか、落としてください。
今回はsocket.jsとしてjsファイル内に置いています。

とりあえず、コードは以下の通りです。
ボタンの部分はどんなインスタンスでもいいです。
今回は丸いボタン風画像をボタンにできるように実装しています。(ゴミ箱も同様)

index.html
<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <meta http-equiv="Access-Control-Allow-Origin" content="*">
        <meta name="viewport" content="width=device-width,initial-scale=1.0,minimum-scale=1.0,maximum-scale=1.0,user-scalable=no">
        <meta name="application-name" content="Olt_chat">
        <link rel="stylesheet" type="text/css" href="css/window.css">
    </head>
    <body>
        <div id = "window_panel" class = "window_panel">
            <div id="headder_panel"class="headder_panel">
                <a href="#" id="btn_clean" onclick = "clean_localStr()">
                    <div id = "btn_cln">
                         <img src="pic/clean_btn.png" style="width:100%; height:100%;">
                    </div>
                </a>
            </div>
            <div id="timeline_panel" >
                <div id="obj" class ="obj"></div>
            </div>
            <div id="input_panel" class = "input_panel" >
                <textarea id = "textInput_area" class ="textInput_area"></textarea>
               <a href="#" id="btn_sample" onclick = "setTimeout(btn_push(), 300);">
                    <div id = "btn_pic">
                         <img src="pic/button_post.png" style="width:100%; height:100%;">
                    </div>
                </a>
            </div>
        </div>
    </body>
    <script type="application/javascript" src="js/socket.js"></script>
    <script>
        var s = io.connect('http://localhost:3000');//ローカル
        //サーバから受け取るイベント
        s.on("connect", function () {});//接続時
        s.on("disconnect", function (client) {});//切断時
        s.on("S_to_C_message", function (data) {
            addMessage(data.value);
        });
        //クライアントからイベント送信時のイベントハンドラ
        function sendBroadcast(text) {
            let msg = text //取得
            s.emit("C_to_S_broadcast", {value:msg}); // サーバへ送信
            object_make(msg);
            console.log(msg);
        }
        function addMessage (value,color,size) {
            let msg = value.replace( /[!@$%<>'"&|]/g, ''); //タグ記号とかいくつか削除
            object_make(msg);//この関数を用いてチャットを表示
        }
    </script>
    <script type="application/javascript" src="js/window.js"></script>
</html>

描画処理というか全体的にネイティブJS使ってますが、
そこらへんは、レスポンシブデザインの処理の速さを求めた結果なのでご了承(自己満
....コメント打つのもだるくなってるレベルですが...。
画像のpathさえ入れてやればちゃんと動きます。

window.js
var count = 0;
var touch_position_h;
var touch_prevent_flag = 0;

document.getElementById("timeline_panel").ontouchstart = function(event){
    if (touch_prevent_flag === 0){  
    }else{
        touch_position_h = event.changedTouches[0];
     }
}
document.getElementById("timeline_panel").ontouchmove = function(event){
    if (touch_prevent_flag === 0){
            event.preventDefault();
    }else{
     }
}
window.onload = function(){
    window_getsize();   
}
window.onresize = function(){  
    window_getsize();  
}

function window_getsize(){  
    var size_h = window.innerHeight;
    var size_w = window.innerWidth;

       if(size_h > size_w){
        var element = document.getElementById("window_panel"); 
        element.style.height = size_h + 'px';
        element.style.width = size_w + 'px';

        var element = document.getElementById("headder_panel");
        let headder_size_h = size_h*0.085;
        element.style.height = headder_size_h + 'px';
        element.style.width = size_w + 'px';
        element.style.top = 0 + 'px';

        var element = document.getElementById("btn_clean"); 
        let btncln_size_h = headder_size_h;//0.2*0.5*0.2
        element.style.height = btncln_size_h*0.75 + 'px';
        element.style.width = btncln_size_h*0.75 + 'px';
        element.style.bottom = btncln_size_h*(0.1) + 'px';
        element.style.right = btncln_size_h*(0.4) + 'px';

        var element = document.getElementById("input_panel"); 
        let IPsize_h = size_h*0.14;
        element.style.height = IPsize_h + 'px';
        element.style.width = size_w + 'px';
        element.style.bottom = 0 + 'px';

        var element = document.getElementById("timeline_panel"); 
        let TLsize_h = size_h - IPsize_h - headder_size_h;
        element.style.height = TLsize_h + 'px';
        element.style.width = size_w + 'px';
        element.style.bottom = size_h - TLsize_h - headder_size_h + 'px';

        var element = document.getElementById("btn_sample"); 
        let btn02_size_h = size_h*0.12;
        element.style.height = btn02_size_h*0.85 + 'px';
        element.style.width = btn02_size_h*0.85+ 'px';
        element.style.bottom = btn02_size_h*(0.25/2.5) + 'px';
        element.style.right = btn02_size_h*(0.25) + 'px';

        var element = document.getElementById("textInput_area");
        let textarea_size_h = size_h*0.12;
        element.style.height = textarea_size_h*0.75 + 'px';
        element.style.width = size_w - btn02_size_h*0.85 - btn02_size_h*(0.25)*3  + 'px';
        element.style.bottom = textarea_size_h*(0.25/1.5) + 'px';
        element.style.right = btn02_size_h*0.85+btn02_size_h*(0.25)*2 + 'px';
        element.style.max_height = textarea_size_h*0.75 + 'px';
        element.style.max_width = size_w*0.675 + 'px';
    }
    else if(size_h < size_w){
        var element = document.getElementById("window_panel"); 
        element.style.height = size_h + 'px';
        element.style.width = size_w + 'px';

        var element = document.getElementById("headder_panel");
        let headder_size_h = 0;
        element.style.height = headder_size_h + 'px';
        element.style.width = size_w + 'px';
        element.style.top = 0 + 'px';

        var element = document.getElementById("input_panel"); 
        let IPsize_h = size_h*0.215;
        element.style.height = IPsize_h + 'px';
        element.style.width = size_w + 'px';
        element.style.bottom = 0 + 'px';

        var element = document.getElementById("timeline_panel");    
        let TLsize_h = size_h - IPsize_h - headder_size_h;
        element.style.height = TLsize_h + 'px';
        element.style.width = size_w + 'px';
        element.style.bottom = size_h - TLsize_h - headder_size_h + 'px';
            
        var element = document.getElementById("btn_sample"); 
        let btn01_size_h = size_h*0.2;
        element.style.height = btn01_size_h*0.85 + 'px';
        element.style.width = btn01_size_h*0.85 + 'px';
        element.style.bottom = btn01_size_h*(0.25/2.75) + 'px';
        element.style.right = btn01_size_h*(0.25) + 'px';

        var element = document.getElementById("btn_clean"); 
        let btncln_size_h = size_h*0.085;
        element.style.height = btncln_size_h*0 + 'px';
        element.style.width = size_w*0 + 'px';
        element.style.bottom = btncln_size_h*(0.075) + 'px';
        element.style.right = btncln_size_h*(0.4) + 'px';

        var element = document.getElementById("textInput_area");
        let textarea_size_h = size_h*0.2;
        element.style.height = textarea_size_h*0.75 + 'px';
        element.style.width = size_w - btn01_size_h*(0.25)*3 - btn01_size_h*0.85 + 'px';
        element.style.bottom = textarea_size_h*(0.25/2) + 'px';
        element.style.right = btn01_size_h*(0.25)*2+btn01_size_h*0.85 + 'px';
        element.style.max_height = textarea_size_h*0.75 + 'px';
        element.style.max_width = size_w*0.675 + 'px';
    }
}
function btn_push(){
    var element = document.getElementById("textInput_area");
    let send_text = element.value;
    if(element.value!==null && element.value!=false){
        element.value=null;
    }else{
        return;
    }
    setTimeout(sendBroadcast(send_text), 1000);
}
var count_onclick_btnSample = 0;
var messageLast_containerSizeHeight = [];
function object_make(text){  

    let TLsize_h = document.getElementById("timeline_panel").height; 
    let TLsize_w = document.getElementById("timeline_panel").width;
    var str_tmp = 0 + '';
    if(count_onclick_btnSample > 0){
        str_tmp = TLsize_h + messageLast_containerSizeHeight[count_onclick_btnSample];
    }else {
        var str_tmp_s;
        str_tmp_s = str_tmp;
    }
    let object = document.createElement('div');  
    let object_s = document.getElementById("obj");

    object_s.innerHTML = ['<div id="message_container">'
        + '</div>'
            + '<div id = "message_0" >'
            + '<p>'+ text +'</p>'
            + '</div>' 
         + '</div>'
        + '</div>'].join("");
        
    object_s.append(object);

    var element = document.getElementById("message_container");
    var element1 = document.getElementById("message_0");

    messageLast_containerSizeHeight.splice(count_onclick_btnSample+1, 0, str_tmp);
    count_onclick_btnSample = count_onclick_btnSample + 1;  
}
function clean_localStr(){
    localStorage.clear(); 
}

ここまでくればあと少し。

window.css
html,body{
        margin:0px;
        flex: 0;
        overflow:hidden;
 }
 #window_panel{
        position: absolute;
        flex: 0;
        background:#999;    
}
#input_panel{
        position: absolute;
        flex: 0;
        background:rgb(73, 72, 72);
 }
#headder_panel{
        position:fixed;
        background:rgb(73, 72, 72);
}
#timeline_panel{
        position: absolute;
        background:#EEE;
}
#btn_clean{
        position:absolute;
        display: inline-block;
        background: rgb(73, 72, 72);
        color: rgb(73, 72, 72);
        text-align: center;
        vertical-align: middle;
        overflow: hidden;
}
#btn_clean:active {
        border-bottom: solid 1px rgb(73, 72, 75);;
        box-shadow: 0 0 0px rgba(0, 0, 0, 0.40);
        border-radius: 10%;
}
#btn_clean:btn_cln {
       position: absolute;
}
#btn_sample{
        position:absolute;
        display: inline-block;
        background: rgb(73, 72, 72);
        color: rgb(73, 72, 72);
        border-radius: 50%;
        text-align: center;
        vertical-align: middle;
        overflow: hidden;
        box-shadow: 0px 2px 2px rgba(0, 0, 0, 0.29);
        text-shadow: -0.2px -0.2px rgba(255, 255, 255, 0.43), 1px 1px rgba(0, 0, 0, 0.49);
}
#btn_sample:active {
        border-bottom: solid 1px #6d6c6d;
        box-shadow: 0 0 1px rgba(0, 0, 0, 0.40);
        border-radius: 50%;
}
#btn_sanmple:btn_pic{
        position: absolute;
}
#textInput_area{
        position: absolute;
        font-size: 100%;
        background:#EEE;
        overflow:hidden;
        border:none;
        resize: none;
}
#message_container{
        position:absolute;
        background:#000;
}
#message_0{
         position:absolute;
         background:#CCC;
         font-size: 100%;
}

ようやくできました。こんな感じです!
スクリーンショット 2018-01-18 16.13.39.png

左がSafari右がChrome ブラウザです。
是非やって見てください。
※ 何度もいいますが丸ボタン画像とゴミ箱画像用意してくださいね。
操作方法はHTMLあたりのコードを見てください。

参考にした記事はこちら
http://www.tettori.net/post/852/

##最後に
記事書くのって結構労力必要ですね。
JS初めて4ヶ月そこらなので
どうしてこうなのとか、ここはこうした方がいい等
是非コメントしていただけると嬉しいです。

17
24
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
17
24

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?