零基礎(chǔ)實(shí)現(xiàn)node+express個(gè)性化聊天室的示例
本篇文章使用node+express+jquery寫一個(gè)個(gè)性化聊天室,一起來get一下~(源碼地址見文章末尾)
效果圖

項(xiàng)目結(jié)構(gòu)

實(shí)現(xiàn)功能
- 登錄檢測(cè)
- 系統(tǒng)自動(dòng)提示用戶狀態(tài)(進(jìn)入/離開)
- 顯示在線用戶
- 支持發(fā)送和接收消息
- 自定義字體顏色
- 支持發(fā)送表情
- 支持發(fā)送圖片
下面將一一講解如何實(shí)現(xiàn)
前期準(zhǔn)備
node及npm環(huán)境、express、socket.io
具體實(shí)現(xiàn)
1、將聊天室部署到服務(wù)器
先用node搭建一個(gè)服務(wù)器,部署在localhost:3000端口,先嘗試向?yàn)g覽器發(fā)送一個(gè)“hello world”,新建server.js文件。
var app = require('express')(); // 引入express模塊
var http = require('http').Server(app);
app.get('/', function(req, res){ // 路由為localhost:3000時(shí)向客戶端響應(yīng)“hello world”
res.send('<h1>Hello world</h1>'); // 發(fā)送數(shù)據(jù)
});
http.listen(3000, function(){ // 監(jiān)聽3000端口
console.log('listening on *:3000');
});
打開瀏覽器輸入網(wǎng)址:localhost:3000是這樣的

一個(gè)node服務(wù)器搭建成功。
接下來用express向?yàn)g覽器返回一個(gè)html頁面
#安裝express模塊 npm install --save express
將server.js的代碼改一下:
var express = require('express');
var app = express();
var http = require('http').Server(app);
// 路由為/默認(rèn)www靜態(tài)文件夾
app.use('/', express.static(__dirname + '/www'));
express.static(__dirname + '/www');是將www文件夾托管為靜態(tài)資源,意味著這個(gè)文件夾里的文件(html、css、js)彼此可以用相對(duì)路徑。在www文件夾中添加index.html文件以及相應(yīng)的css(相應(yīng)css代碼就不貼了,詳情見源碼),如下,該頁面用了font-awesome小圖標(biāo)
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>chat</title>
<link rel="stylesheet" href="style/index.css" rel="external nofollow" >
<link rel="stylesheet" href="style/font-awesome-4.7.0/css/font-awesome.min.css" rel="external nofollow" >
</head>
<body>
<div class="all">
<div class="name">
<!-- <h2>請(qǐng)輸入你的昵稱</h2> -->
<input type="text" id="name" placeholder="請(qǐng)輸入昵稱..." autocomplete="off">
<button id="nameBtn">確 定</button>
</div>
<div class="main">
<div class="header">
<img src="image/logo.jpg">
happy聊天室
</div>
<div id="container">
<div class="conversation">
<ul id="messages"></ul>
<form action="">
<div class="edit">
<input type="color" id="color" value="#000000">
<i title="雙擊取消選擇" class="fa fa-smile-o" id="smile">
</i><i title="雙擊取消選擇" class="fa fa-picture-o" id="img"></i>
<div class="selectBox">
<div class="smile">
</div>
<div class="img">
</div>
</div>
</div>
<!-- autocomplete禁用自動(dòng)完成功能 -->
<textarea id="m"></textarea>
<button class="btn rBtn" id="sub">發(fā)送</button>
<button class="btn" id="clear">關(guān)閉</button>
</form>
</div>
<div class="contacts">
<h1>在線人員(<span id="num">0</span>)</h1>
<ul id="users"></ul>
<p>當(dāng)前無人在線喲~</p>
</div>
</div>
</div>
</div>
</body>
</html>
打開localhost:3000,會(huì)看到如下:

聊天室成功部署到服務(wù)器。
2、檢測(cè)登錄
在客戶端和服務(wù)器之間傳送消息需要用到socket.io
#安裝socket.io模塊 npm install --save socket.io
將server.js改動(dòng)如下:
var app = require('express')();
var http = require('http').Server(app);
var io = require('socket.io')(http);
app.use('/', express.static(__dirname + '/www'));
io.on('connection', function(socket){ // 用戶連接時(shí)觸發(fā)
console.log('a user connected');
});
http.listen(3000, function(){
console.log('listening on *:3000');
});
當(dāng)打開localhost:3000的時(shí)候會(huì)觸發(fā)服務(wù)器端io的connection事件,會(huì)在服務(wù)器打印“a user connected”,但是我們想統(tǒng)計(jì)一下連接該服務(wù)器的用戶人數(shù),如果有用戶連接就打印“n users connected”,n為用戶人數(shù),怎么辦呢?
在server.js設(shè)置一個(gè)全局?jǐn)?shù)組為user,每當(dāng)一個(gè)用戶連接成功就在連接事件中將用戶的昵稱push進(jìn)user,打印user.length即可知道已成功連接用戶的人數(shù)。
等一等。
在用戶連接的時(shí)輸入昵稱登錄,我們應(yīng)該檢測(cè)一下用戶的昵稱是否已存在,避免昵稱相同的情況發(fā)生,在服務(wù)器監(jiān)聽一個(gè)登錄事件來判斷該情況,由于一切都發(fā)生在用戶連接之后,所以觸發(fā)事件應(yīng)該寫在connection事件的回調(diào)函數(shù)中。
io.on('connection', (socket)=> {
// 渲染在線人員
io.emit('disUser', usersInfo);
// 登錄,檢測(cè)用戶名
socket.on('login', (user)=> {
if(users.indexOf(user.name) > -1) { // 昵稱是否存在
socket.emit('loginError'); // 觸發(fā)客戶端的登錄失敗事件
} else {
users.push(user.name); //儲(chǔ)存用戶的昵稱
usersInfo.push(user); // 儲(chǔ)存用戶的昵稱和頭像
socket.emit('loginSuc'); // 觸發(fā)客戶端的登錄成功事件
socket.nickname = user.name;
io.emit('system', { // 向所有用戶廣播該用戶進(jìn)入房間
name: user.name,
status: '進(jìn)入'
});
io.emit('disUser', usersInfo); // 渲染右側(cè)在線人員信息
console.log(users.length + ' user connect.'); // 打印連接人數(shù)
}
});
system和disUser事件先不管,之后再說 區(qū)分io.emit(foo)、socket.emit(foo)、socket.broadcast.emit(foo)
io.emit(foo); //會(huì)觸發(fā)所有客戶端用戶的foo事件 socket.emit(foo); //只觸發(fā)當(dāng)前客戶端用戶的foo事件 socket.broadcast.emit(foo); //觸發(fā)除了當(dāng)前客戶端用戶的其他用戶的foo事件
接下來是客戶端代碼chat-client.js
$(function() {
// io-client
// 連接成功會(huì)觸發(fā)服務(wù)器端的connection事件
var socket = io();
// 點(diǎn)擊輸入昵稱
$('#nameBtn').click(()=> {
var imgN = Math.floor(Math.random()*4)+1; // 隨機(jī)分配頭像
if($('#name').val().trim()!=='')
socket.emit('login', { // 觸發(fā)服務(wù)器端登錄事件
name: $('#name').val(),
img: 'image/user' + imgN + '.jpg'
});
return false;
});
// 登錄成功,隱藏登錄層
socket.on('loginSuc', ()=> {
$('.name').hide();
})
socket.on('loginError', ()=> {
alert('用戶名已存在,請(qǐng)重新輸入!');
$('#name').val('');
});
});
倘若登錄成功,會(huì)看到如下頁面:

登錄檢測(cè)完成。
3、系統(tǒng)自動(dòng)提示用戶狀態(tài)(進(jìn)入/離開)
該功能是為了實(shí)現(xiàn)上圖所示的系統(tǒng)提示“XXX進(jìn)入聊天室”,在登錄成功時(shí)觸發(fā)system事件,向所有用戶廣播信息,注意此時(shí)用的是io.emit而不是socket.emit,客戶端代碼如下
// 系統(tǒng)提示消息
socket.on('system', (user)=> {
var data = new Date().toTimeString().substr(0, 8);
$('#messages').append(`<p class='system'><span>${data}</span><br /><span>${user.name} ${user.status}了聊天室<span></p>`);
// 滾動(dòng)條總是在最底部
$('#messages').scrollTop($('#messages')[0].scrollHeight);
});
4、顯示在線用戶
客戶端監(jiān)聽一個(gè)顯示在線用戶的事件disUser,在以下三個(gè)時(shí)間段服務(wù)器端就觸發(fā)一次該事件重新渲染一次
- 程序開始啟動(dòng)時(shí)
- 每當(dāng)用戶進(jìn)入房間
- 每當(dāng)用戶離開房間
// chat-client.js
// 顯示在線人員
socket.on('disUser', (usersInfo)=> {
displayUser(usersInfo);
});
// 顯示在線人員
function displayUser(users) {
$('#users').text(''); // 每次都要重新渲染
if(!users.length) {
$('.contacts p').show();
} else {
$('.contacts p').hide();
}
$('#num').text(users.length);
for(var i = 0; i < users.length; i++) {
var $html = `<li>
<img src="${users[i].img}">
<span>${users[i].name}</span>
</li>`;
$('#users').append($html);
}
}
5、支持發(fā)送和接收消息
用戶發(fā)送消息時(shí)觸發(fā)服務(wù)器端的sendMsg事件,并將消息內(nèi)容作為參數(shù),服務(wù)器端監(jiān)聽到sendMsg事件之后向其他所有用戶廣播該消息,用的socket.broadcast.emit(foo)
// server.js
// 發(fā)送消息事件
socket.on('sendMsg', (data)=> {
var img = '';
for(var i = 0; i < usersInfo.length; i++) {
if(usersInfo[i].name == socket.nickname) {
img = usersInfo[i].img;
}
}
socket.broadcast.emit('receiveMsg', { // 向除了發(fā)送者之外的其他用戶廣播
name: socket.nickname,
img: img,
msg: data.msg,
color: data.color,
side: 'left'
});
socket.emit('receiveMsg', { // 向發(fā)送者發(fā)送消息,為什么分開發(fā)送?因?yàn)閏ss樣式不同
name: socket.nickname,
img: img,
msg: data.msg,
color: data.color,
side: 'right'
});
});
服務(wù)器端接受到來自用戶的消息后會(huì)觸發(fā)客戶端的receiveMsg事件,并將用戶發(fā)送的消息作為參數(shù)傳遞,該事件會(huì)向聊天面板添加聊天內(nèi)容,以下為chat-client.js代碼
// 點(diǎn)擊按鈕或回車鍵發(fā)送消息
$('#sub').click(sendMsg);
$('#m').keyup((ev)=> {
if(ev.which == 13) {
sendMsg();
}
});
// 接收消息
socket.on('receiveMsg', (obj)=> { // 將接收到的消息渲染到面板上
$('#messages').append(`
<li class='${obj.side}'>
<img src="${obj.img}">
<div>
<span>${obj.name}</span>
<p>${obj.msg}</p>
</div>
</li>
`);
// 滾動(dòng)條總是在最底部
$('#messages').scrollTop($('#messages')[0].scrollHeight);
});
// 發(fā)送消息
function sendMsg() {
if($('#m').val() == '') { // 輸入消息為空
alert('請(qǐng)輸入內(nèi)容!');
return false;
}
socket.emit('sendMsg', {
msg: $('#m').val()
});
$('#m').val('');
return false;
}
6、自定義字體顏色
得益于html5的input新特性,可以通過type為color的input調(diào)用系統(tǒng)調(diào)色板
<!-- $('#color').val();為選中顏色,格式為#FFCCBB -->
<input type='color' id='color'>
客戶端根據(jù)用戶選擇的顏色渲染內(nèi)容樣式,代碼很容易看懂,這里就不贅述了。
7、支持發(fā)送表情
發(fā)送表情其實(shí)很簡(jiǎn)單,將表情圖片放在li中,當(dāng)用戶點(diǎn)擊li時(shí)就將表情的src中的序號(hào)解析出來,用[emoji+表情序號(hào)]的格式存放在聊天框里,點(diǎn)擊發(fā)送后再解析為src。就是一個(gè)解析加還原的過程,這一過程中我們的服務(wù)器代碼不變,需要改變的是客戶端監(jiān)聽的receiveMsg事件。
// chat-client.js
// 顯示表情選擇面板
$('#smile').click(()=> {
$('.selectBox').css('display', "block");
});
$('#smile').dblclick((ev)=> {
$('.selectBox').css('display', "none");
});
$('#m').click(()=> {
$('.selectBox').css('display', "none");
});
// 用戶點(diǎn)擊發(fā)送表情
$('.emoji li img').click((ev)=> {
ev = ev || window.event;
var src = ev.target.src;
var emoji = src.replace(/\D*/g, '').substr(6, 8); // 提取序號(hào)
var old = $('#m').val(); // 用戶輸入的其他內(nèi)容
$('#m').val(old+'[emoji'+emoji+']');
$('.selectBox').css('display', "none");
});
客戶端收到之后將表情序號(hào)還原為src,更改如下
// chat-client.js
// 接收消息
socket.on('receiveMsg', (obj)=> {
// 提取文字中的表情加以渲染
var msg = obj.msg;
var content = '';
while(msg.indexOf('[') > -1) { // 其實(shí)更建議用正則將[]中的內(nèi)容提取出來
var start = msg.indexOf('[');
var end = msg.indexOf(']');
content += '<span>'+msg.substr(0, start)+'</span>';
content += '<img src="image/emoji/emoji%20('+msg.substr(start+6, end-start-6)+').png">';
msg = msg.substr(end+1, msg.length);
}
content += '<span>'+msg+'</span>';
$('#messages').append(`
<li class='${obj.side}'>
<img src="${obj.img}">
<div>
<span>${obj.name}</span>
<p style="color: ${obj.color};">${content}</p>
</div>
</li>
`);
// 滾動(dòng)條總是在最底部
$('#messages').scrollTop($('#messages')[0].scrollHeight);
});
可以成功發(fā)送表情了。

8、支持發(fā)送圖片
首先是圖片按鈕樣式,發(fā)送圖片的按鈕是type為file的input。這里有一個(gè)改變樣式的小技巧,那就是將input的透明度設(shè)為0,z-index為5,將你想要得樣式放在div中,z-index設(shè)為1覆蓋在input上。
<input type="file" id="file">
<i class="fa fa-picture-o" id="img"></i>
css:
.edit #file {
width: 32.36px;
height: 29px;
opacity: 0;
z-index: 5;
}
.edit #img {
z-index: 0;
margin-left: -43px;
}
完美

接下來是點(diǎn)擊按鈕發(fā)送圖片,我們用了fileReader對(duì)象,這里有一篇不錯(cuò)的文章講解了fileReader,fileReader是一個(gè)對(duì)象,可以將我們選中的文件已64位輸出然后將結(jié)果存放在reader.result中,我們選中圖片之后,reader.result就存放的是圖片的src
// chat-client.js
// 用戶發(fā)送圖片
$('#file').change(function() {
var file = this.files[0]; // 上傳單張圖片
var reader = new FileReader();
//文件讀取出錯(cuò)的時(shí)候觸發(fā)
reader.onerror = function(){
console.log('讀取文件失敗,請(qǐng)重試!');
};
// 讀取成功后
reader.onload = function() {
var src = reader.result; // 讀取結(jié)果
var img = '<img class="sendImg" src="'+src+'">';
socket.emit('sendMsg', { // 發(fā)送
msg: img,
color: color,
type: 'img' // 發(fā)送類型為img
});
};
reader.readAsDataURL(file); // 讀取為64位
});
由于發(fā)送的是圖片,所以對(duì)頁面布局難免有影響,為了頁面美觀客戶端在接收其他用戶發(fā)送的消息的時(shí)候會(huì)先判斷發(fā)送的是文本還是圖片,根據(jù)不同的結(jié)果展示不同布局。判斷的方法是在客戶發(fā)送消息的時(shí)候傳入一個(gè)type,根據(jù)type的值來確實(shí)發(fā)送內(nèi)容的類型。所以上面發(fā)送圖片代碼中觸發(fā)了sendMsg事件,傳入?yún)?shù)多了一個(gè)type屬性。
響應(yīng)的,我們應(yīng)該在chat-client.js中修改receiveMsg事件監(jiān)聽函數(shù),改為根據(jù)傳入type做不同操作
chat-client.js
// 接收消息
socket.on('receiveMsg', (obj)=> {
// 發(fā)送為圖片
if(obj.type == 'img') {
$('#messages').append(`
<li class='${obj.side}'>
<img src="${obj.img}">
<div>
<span>${obj.name}</span>
<p style="padding: 0;">${obj.msg}</p>
</div>
</li>
`);
$('#messages').scrollTop($('#messages')[0].scrollHeight);
return;
}
// 提取文字中的表情加以渲染
// 下面不變
});
現(xiàn)在我們可以發(fā)送圖片了

圓滿完成一個(gè)功能齊全的聊天室!
源碼地址:windlany/happy-chat,本文斷斷續(xù)續(xù)寫了兩天,真是寫文章比敲代碼還累...其實(shí)寫一個(gè)聊天室并不難,這算是node起步作品吧。
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
- node+express+ejs制作簡(jiǎn)單頁面上手指南
- nodejs教程 安裝express及配置app.js文件的詳細(xì)步驟
- node.js Web應(yīng)用框架Express入門指南
- node+express+jade制作簡(jiǎn)單網(wǎng)站指南
- NodeJS框架Express的模板視圖機(jī)制分析
- 淺析node連接數(shù)據(jù)庫(express+mysql)
- Nodejs Express4.x開發(fā)框架隨手筆記
- NodeJS Express框架中處理404頁面一個(gè)方式
- win7下安裝配置node.js+express開發(fā)環(huán)境
相關(guān)文章
安裝nvm并使用nvm安裝nodejs及配置環(huán)境變量的全過程
有時(shí)候使用nvm管理node會(huì)發(fā)現(xiàn)無法使用node或npm,主要原因是環(huán)境變量沒有配置成功,下面這篇文章主要給大家介紹了關(guān)于安裝nvm并使用nvm安裝nodejs及配置環(huán)境變量的相關(guān)資料,需要的朋友可以參考下2023-03-03
node.js中的querystring.unescape方法使用說明
這篇文章主要介紹了node.js中的querystring.unescape方法使用說明,本文介紹了querystring.unescape的方法說明、語法、接收參數(shù)、使用實(shí)例和實(shí)現(xiàn)源碼,需要的朋友可以參考下2014-12-12
nest.js,egg.js,midway,express,koa的區(qū)別小結(jié)
本文主要介紹了nest.js,egg.js,midway,express,koa的區(qū)別小結(jié),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2023-05-05
一次NodeJS內(nèi)存泄漏排查的實(shí)戰(zhàn)記錄
這篇文章主要給大家介紹了一次NodeJS內(nèi)存泄漏排查的實(shí)戰(zhàn)記錄,文中給出了詳細(xì)的排查過程以及內(nèi)存泄漏的解決方法,大家可以學(xué)習(xí)一下以備不時(shí)之需,需要的朋友可以參考下2022-03-03
Node.js?中使用fetch?按JSON格式發(fā)post請(qǐng)求的問題解析
最近在測(cè)試一個(gè)api,可以用curl命令直接訪問,指定header相關(guān)配置,request?body(JSON),成功后返回一個(gè)JSON,這篇文章主要介紹了Node.js?中使用fetch?按JSON格式發(fā)post請(qǐng)求,需要的朋友可以參考下2023-04-04

