前言
websocket是 html5 新增的一项api,实现客户端与服务器之间的即时通信。今天用它来实现一个聊天室demo,这里选择原生js来实现,因为用惯了vue和react的舒适框架,是时候复习一下原生的api了。毕竟现在前端技术更新很快,掌握好底层的东西才能做到以不变应万变
demo在线预览:http://101.42.108.39:90/chat/
思路
后台使用node搭建一个websocket服务器,客户端连接此服务器完成握手,客户端每次发送消息,后台就向所有握手的客户端广播消息
关键api
前端
-
sorket = new WebSocket("ws://localhost:3000") 【初始化WebSocket对象】
-
this.sorket.onopen 【与服务端建立连接触发】
-
this.sorket.send 【向服务器发送消息】
-
this.sorket.onmessage 【收到服务端推送消息触发】
后台
-
ws.createServer=(conn=>{}).listen(3000) 【创建ws服务器】
-
conn.on("text", function (obj) {}) 【接收消息】
-
conn.sendText() 【向所有握手的客户端广播消息】
html部分
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>心念--云聊天室</title>
<link rel="stylesheet" href="./style.css" />
</head>
<body>
<div class="container">
<header>在线人数:0</header>
<main></main>
<footer>
<input
type="text"
placeholder="请输入要发送的内容..."
id="messageInput"
/>
<button id="send">发 送</button>
</footer>
<div id="modal">
<div class="modal-content">
<h2>请输入昵称</h2>
<input type="text" />
<div><button>开始聊天</button></div>
</div>
</div>
</div>
<script src="./chat.js"></script>
</body>
</html>
css样式
* {
margin: 0;
padding: 0;
}
body,
html {
height: 100%;
}
.container {
height: 100%;
display: flex;
flex-direction: column;
}
header {
height: 7vh;
border-bottom: 2px solid #555;
text-align: center;
line-height: 7vh;
font-size: 18px;
font-weight: bold;
}
main {
height: 85vh;
padding: 10px;
padding-bottom: 70px;
overflow: auto;
}
main .join-tip {
text-align: center;
color: #999;
margin: 5px;
}
main .mesItem {
position: relative;
display: flex;
padding: 25px 0;
color: #fff;
}
main .mesItem-me {
position: relative;
display: flex;
padding: 25px 0;
color: #fff;
justify-content: flex-end;
}
main .nickname {
width: 50px;
height: 50px;
border-radius: 50%;
background: #999;
text-align: center;
line-height: 50px;
}
main .content {
position: relative;
padding: 0 10px;
text-align: center;
line-height: 50px;
border-radius: 5px;
}
main p {
color: gray;
position: absolute;
bottom: -5px;
}
main .mesItem .content {
background-color: rgb(88, 179, 212);
margin-left: 15px;
}
main .mesItem-me .content {
background-color: rgb(21, 136, 21);
margin-right: 15px;
}
main .mesItem .content::before {
position: absolute;
left: -20px;
top: 50%;
transform: translateY(-50%);
height: 0;
width: 0;
content: "";
border: 10px solid rgba(255, 255, 255, 0);
border-top: 6px solid rgba(255, 255, 255, 0);
border-bottom: 6px solid rgba(255, 255, 255, 0);
border-right-color: rgb(88, 179, 212);
}
main .mesItem-me .content::before {
position: absolute;
right: -20px;
top: 50%;
transform: translateY(-50%);
height: 0;
width: 0;
content: "";
border: 10px solid rgba(255, 255, 255, 0);
border-top: 6px solid rgba(255, 255, 255, 0);
border-bottom: 6px solid rgba(255, 255, 255, 0);
border-left-color: rgb(21, 136, 21);
}
footer {
height: 8vh;
width: 100%;
display: flex;
border-top: 1px solid #999;
position: fixed;
bottom: 0;
}
#messageInput {
font-size: 18px;
border: none;
padding-left: 20px;
outline: none;
line-height: 8vh;
width: 100%;
}
#send {
width: 100px;
cursor: pointer;
background-color: aquamarine;
border-top: 1px solid #999;
font-size: 18px;
}
#modal {
height: 100%;
width: 100%;
background-color: rgba(0, 0, 0, 0.5);
position: fixed;
display: none;
}
#modal .modal-content {
border-radius: 5px;
padding: 10px;
background-color: #fff;
border: 1px solid #555;
height: 200px;
width: 300px;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
#modal .modal-content h2 {
text-align: center;
}
#modal .modal-content input {
text-align: center;
width: 100%;
height: 40px;
margin: 15px 0;
}
#modal .modal-content button {
margin: 0 auto;
display: block;
background-color: aquamarine;
border: 1px solid #eee;
padding: 10px;
font-size: 18px;
cursor: pointer;
}
js部分
class Chat {
header = document.querySelector("header");
modal = document.querySelector("#modal");
modalInput = document.querySelector("#modal input");
modalButton = document.querySelector("#modal button");
msgInput = document.querySelector("#messageInput");
msgSendBtn = document.querySelector("#send");
main = document.querySelector("main");
user = {};
msg = "";
sorket = new WebSocket("ws://localhost:3000");
// sorket = new WebSocket("ws://101.42.108.39:3000");
msgList = [];
constructor() {
// 如果localstorge存在用户信息,直接引用
// 不存在,就弹窗注册
const user = localStorage.getItem("user");
if (!user) {
this.modal.style.display = "block";
this.modalButton.onclick = () => {
if (!this.modalInput.value) return alert("昵称不能为空");
const name = this.modalInput.value;
const uid = "chat_user_" + Date.now();
const userInfo = { name, uid };
this.user = userInfo;
// localStorage存一下
localStorage.setItem("user", JSON.stringify(userInfo));
this.modal.style.display = "none";
// 广播入场通知
this.send({ ...this.user, type: 1 });
};
} else {
this.user = JSON.parse(user);
}
// 消息输入与发送事件+回车发送事件
this.msgInput.oninput = (e) => {
this.msg = e.target.value;
};
this.msgSendBtn.onclick = () => {
if (!this.msg) return alert("不能发送空的内容");
this.send({ ...this.user, msg: this.msg, type: 2 });
};
document.onkeydown = (event) => {
var e = event || window.event;
if (e && e.keyCode == 13) {
//回车键的键值为13
if (!this.msg) return alert("不能发送空的内容");
this.send({ ...this.user, msg: this.msg, type: 2 });
}
};
this.sorket.onopen = () => {
console.log("连接服务器成功");
// 如果是注册过的用户,发送入场广播
if (this.user.name) this.send({ ...this.user, type: 1 });
};
// 消息接收监听
this.sorket.onmessage = (e) => {
let message = JSON.parse(e.data);
this.msgList.push(message);
this.render();
};
}
// 发送消息
send(data) {
this.sorket.send(JSON.stringify(data));
this.msgInput.value = "";
this.msg = "";
}
render() {
let html = "";
this.msgList.forEach(({ type, uid, name, msg, time, userTotal }) => {
if (type === 1) {
html += `<div class="join-tip">${name} 加入了聊天</div>`;
}
if (type === 2 && uid !== this.user.uid) {
html += `<div class="mesItem">
<div class="nickname">${name}</div>
<div class="content">${msg}</div>
<p>${time}</p>
</div>`;
}
if (type === 2 && uid === this.user.uid) {
html += ` <div class="mesItem-me">
<div class="content">${msg}</div>
<div class="nickname">${name}</div>
<p>${time}</p>
</div>`;
}
this.header.innerText = `在线人数:${userTotal}`;
});
this.main.innerHTML = html;
// 保持滚动到最底部
this.main.scrollTop = this.main.scrollHeight;
}
}
new Chat();
node部分
const ws = require("nodejs-websocket");
const moment = require("moment");
var server = ws
.createServer(function (conn) {
conn.on("text", function (obj) {
obj = {
...JSON.parse(obj),
time: moment().format("YYYY-MM-DD HH:mm:ss"),
};
// 连接用户数
obj.userTotal = server.connections.length;
// 发送广播
server.connections.forEach((conn) => {
conn.sendText(JSON.stringify(obj));
});
});
conn.on("close", function (code, reason) {
console.log("关闭连接");
});
conn.on("error", function (code, reason) {
console.log("异常关闭");
});
})
.listen(3000);
console.log("WebSocket建立完毕");