Edited at

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

More than 1 year has passed since last update.


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/

https://qiita.com/Suibari_cha/items/48da8519d6f363925b6a


最後に

記事書くのって結構労力必要ですね。

JS初めて4ヶ月そこらなので

どうしてこうなのとか、ここはこうした方がいい等

是非コメントしていただけると嬉しいです。