前言
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部分
代码语言:javascript复制1<!DOCTYPE html>
2<html lang="en">
3 <head>
4 <meta charset="UTF-8" />
5 <meta http-equiv="X-UA-Compatible" content="IE=edge" />
6 <meta name="viewport" content="width=device-width, initial-scale=1.0" />
7 <title>心念--云聊天室</title>
8 <link rel="stylesheet" href="./style.css" />
9 </head>
10 <body>
11 <div class="container">
12 <header>在线人数:0</header>
13 <main></main>
14 <footer>
15 <input
16 type="text"
17 placeholder="请输入要发送的内容..."
18 id="messageInput"
19 />
20 <button id="send">发 送</button>
21 </footer>
22
23 <div id="modal">
24 <div class="modal-content">
25 <h2>请输入昵称</h2>
26 <input type="text" />
27 <div><button>开始聊天</button></div>
28 </div>
29 </div>
30 </div>
31 <script src="./chat.js"></script>
32 </body>
33</html>
34
css样式
代码语言:javascript复制1* {
2 margin: 0;
3 padding: 0;
4}
5
6body,
7html {
8 height: 100%;
9}
10
11.container {
12 height: 100%;
13 display: flex;
14 flex-direction: column;
15}
16
17header {
18 height: 7vh;
19 border-bottom: 2px solid #555;
20 text-align: center;
21 line-height: 7vh;
22 font-size: 18px;
23 font-weight: bold;
24}
25
26main {
27 height: 85vh;
28 padding: 10px;
29 padding-bottom: 70px;
30 overflow: auto;
31}
32
33main .join-tip {
34 text-align: center;
35 color: #999;
36 margin: 5px;
37}
38
39main .mesItem {
40 position: relative;
41 display: flex;
42 padding: 25px 0;
43 color: #fff;
44}
45
46main .mesItem-me {
47 position: relative;
48 display: flex;
49 padding: 25px 0;
50 color: #fff;
51 justify-content: flex-end;
52}
53
54main .nickname {
55 width: 50px;
56 height: 50px;
57 border-radius: 50%;
58 background: #999;
59 text-align: center;
60 line-height: 50px;
61}
62main .content {
63 position: relative;
64 padding: 0 10px;
65 text-align: center;
66 line-height: 50px;
67 border-radius: 5px;
68}
69
70main p {
71 color: gray;
72 position: absolute;
73 bottom: -5px;
74}
75
76main .mesItem .content {
77 background-color: rgb(88, 179, 212);
78 margin-left: 15px;
79}
80
81main .mesItem-me .content {
82 background-color: rgb(21, 136, 21);
83 margin-right: 15px;
84}
85
86main .mesItem .content::before {
87 position: absolute;
88 left: -20px;
89 top: 50%;
90 transform: translateY(-50%);
91 height: 0;
92 width: 0;
93 content: "";
94 border: 10px solid rgba(255, 255, 255, 0);
95 border-top: 6px solid rgba(255, 255, 255, 0);
96 border-bottom: 6px solid rgba(255, 255, 255, 0);
97 border-right-color: rgb(88, 179, 212);
98}
99
100main .mesItem-me .content::before {
101 position: absolute;
102 right: -20px;
103 top: 50%;
104 transform: translateY(-50%);
105 height: 0;
106 width: 0;
107 content: "";
108 border: 10px solid rgba(255, 255, 255, 0);
109 border-top: 6px solid rgba(255, 255, 255, 0);
110 border-bottom: 6px solid rgba(255, 255, 255, 0);
111 border-left-color: rgb(21, 136, 21);
112}
113
114footer {
115 height: 8vh;
116 width: 100%;
117 display: flex;
118 border-top: 1px solid #999;
119 position: fixed;
120 bottom: 0;
121}
122#messageInput {
123 font-size: 18px;
124 border: none;
125 padding-left: 20px;
126 outline: none;
127 line-height: 8vh;
128 width: 100%;
129}
130#send {
131 width: 100px;
132 cursor: pointer;
133 background-color: aquamarine;
134 border-top: 1px solid #999;
135 font-size: 18px;
136}
137
138#modal {
139 height: 100%;
140 width: 100%;
141 background-color: rgba(0, 0, 0, 0.5);
142 position: fixed;
143 display: none;
144}
145
146#modal .modal-content {
147 border-radius: 5px;
148 padding: 10px;
149 background-color: #fff;
150 border: 1px solid #555;
151 height: 200px;
152 width: 300px;
153 position: absolute;
154 top: 50%;
155 left: 50%;
156 transform: translate(-50%, -50%);
157}
158
159#modal .modal-content h2 {
160 text-align: center;
161}
162
163#modal .modal-content input {
164 text-align: center;
165 width: 100%;
166 height: 40px;
167 margin: 15px 0;
168}
169
170#modal .modal-content button {
171 margin: 0 auto;
172 display: block;
173 background-color: aquamarine;
174 border: 1px solid #eee;
175 padding: 10px;
176 font-size: 18px;
177 cursor: pointer;
178}
179
js部分
代码语言:javascript复制1class Chat {
2 header = document.querySelector("header");
3 modal = document.querySelector("#modal");
4 modalInput = document.querySelector("#modal input");
5 modalButton = document.querySelector("#modal button");
6 msgInput = document.querySelector("#messageInput");
7 msgSendBtn = document.querySelector("#send");
8 main = document.querySelector("main");
9 user = {};
10 msg = "";
11 sorket = new WebSocket("ws://localhost:3000");
12 // sorket = new WebSocket("ws://101.42.108.39:3000");
13 msgList = [];
14
15 constructor() {
16 // 如果localstorge存在用户信息,直接引用
17 // 不存在,就弹窗注册
18 const user = localStorage.getItem("user");
19 if (!user) {
20 this.modal.style.display = "block";
21 this.modalButton.onclick = () => {
22 if (!this.modalInput.value) return alert("昵称不能为空");
23 const name = this.modalInput.value;
24 const uid = "chat_user_" Date.now();
25 const userInfo = { name, uid };
26 this.user = userInfo;
27 // localStorage存一下
28 localStorage.setItem("user", JSON.stringify(userInfo));
29 this.modal.style.display = "none";
30
31 // 广播入场通知
32 this.send({ ...this.user, type: 1 });
33 };
34 } else {
35 this.user = JSON.parse(user);
36 }
37
38 // 消息输入与发送事件 回车发送事件
39 this.msgInput.oninput = (e) => {
40 this.msg = e.target.value;
41 };
42 this.msgSendBtn.onclick = () => {
43 if (!this.msg) return alert("不能发送空的内容");
44 this.send({ ...this.user, msg: this.msg, type: 2 });
45 };
46 document.onkeydown = (event) => {
47 var e = event || window.event;
48 if (e && e.keyCode == 13) {
49 //回车键的键值为13
50 if (!this.msg) return alert("不能发送空的内容");
51 this.send({ ...this.user, msg: this.msg, type: 2 });
52 }
53 };
54
55 this.sorket.onopen = () => {
56 console.log("连接服务器成功");
57
58 // 如果是注册过的用户,发送入场广播
59 if (this.user.name) this.send({ ...this.user, type: 1 });
60 };
61
62 // 消息接收监听
63 this.sorket.onmessage = (e) => {
64 let message = JSON.parse(e.data);
65 this.msgList.push(message);
66 this.render();
67 };
68 }
69
70 // 发送消息
71 send(data) {
72 this.sorket.send(JSON.stringify(data));
73 this.msgInput.value = "";
74 this.msg = "";
75 }
76
77 render() {
78 let html = "";
79 this.msgList.forEach(({ type, uid, name, msg, time, userTotal }) => {
80 if (type === 1) {
81 html = `<div class="join-tip">${name} 加入了聊天</div>`;
82 }
83
84 if (type === 2 && uid !== this.user.uid) {
85 html = `<div class="mesItem">
86 <div class="nickname">${name}</div>
87 <div class="content">${msg}</div>
88 <p>${time}</p>
89 </div>`;
90 }
91
92 if (type === 2 && uid === this.user.uid) {
93 html = ` <div class="mesItem-me">
94 <div class="content">${msg}</div>
95 <div class="nickname">${name}</div>
96 <p>${time}</p>
97 </div>`;
98 }
99
100 this.header.innerText = `在线人数:${userTotal}`;
101 });
102 this.main.innerHTML = html;
103
104 // 保持滚动到最底部
105 this.main.scrollTop = this.main.scrollHeight;
106 }
107}
108
109new Chat();
110
node部分
代码语言:javascript复制1const ws = require("nodejs-websocket");
2const moment = require("moment");
3
4var server = ws
5 .createServer(function (conn) {
6 conn.on("text", function (obj) {
7 obj = {
8 ...JSON.parse(obj),
9 time: moment().format("YYYY-MM-DD HH:mm:ss"),
10 };
11
12 // 连接用户数
13 obj.userTotal = server.connections.length;
14
15 // 发送广播
16 server.connections.forEach((conn) => {
17 conn.sendText(JSON.stringify(obj));
18 });
19 });
20
21 conn.on("close", function (code, reason) {
22 console.log("关闭连接");
23 });
24 conn.on("error", function (code, reason) {
25 console.log("异常关闭");
26 });
27 })
28 .listen(3000);
29console.log("WebSocket建立完毕");
30