本篇文章使用node+express+jquery寫一個個性化聊天室,一起來get一下~(源碼地址見文章末尾)

效果圖

項目結構

實現功能

  1. 登錄檢測
  2. 系統自動提示用戶狀態(進入/離開)
  3. 顯示在線用戶
  4. 支持發送和接收消息
  5. 自定義字體顏色
  6. 支持發送表情
  7. 支持發送圖片

下面將一一講解如何實現

前期準備

node及npm環境、express、socket.io

具體實現

1、將聊天室部署到伺服器

先用node搭建一個伺服器,部署在localhost:3000埠,先嘗試向瀏覽器發送一個「hello world」,新建server.js文件。

var app = require(express)(); // 引入express模塊
var http = require(http).Server(app);

app.get(/, function(req, res){ // 路由為localhost:3000時向客戶端響應「hello world」
res.send(<h1>Hello world</h1>); // 發送數據
});

http.listen(3000, function(){ // 監聽3000埠
console.log(listening on *:3000);
});

打開瀏覽器輸入網址:localhost:3000是這樣的

一個node伺服器搭建成功。

接下來用express向瀏覽器返回一個html頁面

#安裝express模塊
npm install --save express

將server.js的代碼改一下:

var express = require(express);
var app = express();
var http = require(http).Server(app);

// 路由為/默認www靜態文件夾
app.use(/, express.static(__dirname + /www));

  • express.static(__dirname + /www);是將www文件夾託管為靜態資源,意味著這個文件夾裏的文件(html、css、js)彼此可以用相對路徑。

在www文件夾中添加index.html文件以及相應的css(相應css代碼就不貼了,詳情見源碼),如下,該頁面用了font-awesome小圖標

<!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">
<link rel="stylesheet" href="style/font-awesome-4.7.0/css/font-awesome.min.css">
</head>
<body>
<div class="all">
<div class="name">
<!-- <h2>請輸入你的暱稱</h2> -->
<input type="text" id="name" placeholder="請輸入暱稱..." 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禁用自動完成功能 -->
<textarea id="m"></textarea>
<button class="btn rBtn" id="sub">發送</button>
<button class="btn" id="clear">關閉</button>
</form>
</div>
<div class="contacts">
<h1>在線人員(<span id="num">0</span>)</h1>
<ul id="users"></ul>
<p>當前無人在線喲~</p>
</div>
</div>
</div>
</div>
</body>
</html>

打開localhost:3000,會看到如下:

聊天室成功部署到伺服器。

2、檢測登錄

在客戶端和伺服器之間傳送消息需要用到socket.io

#安裝socket.io模塊
npm install --save socket.io

將server.js改動如下:

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){ // 用戶連接時觸發
console.log(a user connected);
});

http.listen(3000, function(){
console.log(listening on *:3000);
});

當打開localhost:3000的時候會觸發伺服器端io的connection事件,會在伺服器列印「a user connected」,但是我們想統計一下連接該伺服器的用戶人數,如果有用戶連接就列印「n users connected」,n為用戶人數,怎麼辦呢?

在server.js設置一個全局數組為user,每當一個用戶連接成功就在連接事件中將用戶的暱稱push進user,列印user.length即可知道已成功連接用戶的人數。

等一等。

在用戶連接的時輸入暱稱登錄,我們應該檢測一下用戶的暱稱是否已存在,避免暱稱相同的情況發生,在伺服器監聽一個登錄事件來判斷該情況,由於一切都發生在用戶連接之後,所以觸發事件應該寫在connection事件的回調函數中。

io.on(connection, (socket)=> {
// 渲染在線人員
io.emit(disUser, usersInfo);

// 登錄,檢測用戶名
socket.on(login, (user)=> {
if(users.indexOf(user.name) > -1) { // 暱稱是否存在
socket.emit(loginError); // 觸發客戶端的登錄失敗事件
} else {
users.push(user.name); //儲存用戶的暱稱
usersInfo.push(user); // 儲存用戶的暱稱和頭像
socket.emit(loginSuc); // 觸發客戶端的登錄成功事件
socket.nickname = user.name;
io.emit(system, { // 向所有用戶廣播該用戶進入房間
name: user.name,
status: 進入
});
io.emit(disUser, usersInfo); // 渲染右側在線人員信息
console.log(users.length + user connect.); // 列印連接人數
}
});

  • system和disUser事件先不管,之後再說
  • 區分io.emit(foo)、socket.emit(foo)、socket.broadcast.emit(foo)

/*
io.emit(foo); //會觸發所有客戶端用戶的foo事件
socket.emit(foo); //只觸發當前客戶端用戶的foo事件
socket.broadcast.emit(foo); //觸發除了當前客戶端用戶的其他用戶的foo事件
*/

接下來是客戶端代碼chat-client.js

$(function() {
// io-client
// 連接成功會觸發伺服器端的connection事件
var socket = io();

// 點擊輸入暱稱
$(#nameBtn).click(()=> {
var imgN = Math.floor(Math.random()*4)+1; // 隨機分配頭像
if($(#name).val().trim()!==)
socket.emit(login, { // 觸發伺服器端登錄事件
name: $(#name).val(),
img: image/user + imgN + .jpg
});
return false;
});
// 登錄成功,隱藏登錄層
socket.on(loginSuc, ()=> {
$(.name).hide();
})
socket.on(loginError, ()=> {
alert(用戶名已存在,請重新輸入!);
$(#name).val();
});
});

倘若登錄成功,會看到如下頁面:

登錄檢測完成。

3、系統自動提示用戶狀態(進入/離開)

該功能是為了實現上圖所示的系統提示「XXX進入聊天室」,在登錄成功時觸發system事件,向所有用戶廣播信息,注意此時用的是io.emit而不是socket.emit,客戶端代碼如下

// 系統提示消息
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>`);
// 滾動條總是在最底部
$(#messages).scrollTop($(#messages)[0].scrollHeight);
});

4、顯示在線用戶

客戶端監聽一個顯示在線用戶的事件disUser,在以下三個時間段伺服器端就觸發一次該事件重新渲染一次

  • 程序開始啟動時
  • 每當用戶進入房間
  • 每當用戶離開房間

// 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、支持發送和接收消息

用戶發送消息時觸發伺服器端的sendMsg事件,並將消息內容作為參數,伺服器端監聽到sendMsg事件之後向其他所有用戶廣播該消息,用的socket.broadcast.emit(foo)

// server.js
// 發送消息事件
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, { // 向除了發送者之外的其他用戶廣播
name: socket.nickname,
img: img,
msg: data.msg,
color: data.color,
side: left
});
socket.emit(receiveMsg, { // 向發送者發送消息,為什麼分開發送?因為css樣式不同
name: socket.nickname,
img: img,
msg: data.msg,
color: data.color,
side: right
});
});

伺服器端接受到來自用戶的消息後會觸發客戶端的receiveMsg事件,並將用戶發送的消息作為參數傳遞,該事件會向聊天面板添加聊天內容,以下為chat-client.js代碼

// 點擊按鈕或回車鍵發送消息
$(#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>
`);
// 滾動條總是在最底部
$(#messages).scrollTop($(#messages)[0].scrollHeight);
});

// 發送消息
function sendMsg() {
if($(#m).val() == ) { // 輸入消息為空
alert(請輸入內容!);
return false;
}
socket.emit(sendMsg, {
msg: $(#m).val()
});
$(#m).val();
return false;
}

6、自定義字體顏色

得益於html5的input新特性,可以通過type為color的input調用系統調色板

<!-- $(#color).val();為選中顏色,格式為#FFCCBB -->
<input type=color id=color>

客戶端根據用戶選擇的顏色渲染內容樣式,代碼很容易看懂,這裡就不贅述了。

7、支持發送表情

發送表情其實很簡單,將表情圖片放在li中,當用戶點擊li時就將表情的src中的序號解析出來,用[emoji+表情序號]的格式存放在聊天框裏,點擊發送後再解析為src。就是一個解析加還原的過程,這一過程中我們的伺服器代碼不變,需要改變的是客戶端監聽的receiveMsg事件。

// chat-client.js

// 顯示錶情選擇面板
$(#smile).click(()=> {
$(.selectBox).css(display, "block");
});
$(#smile).dblclick((ev)=> {
$(.selectBox).css(display, "none");
});
$(#m).click(()=> {
$(.selectBox).css(display, "none");
});

// 用戶點擊發送表情
$(.emoji li img).click((ev)=> {
ev = ev || window.event;
var src = ev.target.src;
var emoji = src.replace(/D*/g, ).substr(6, 8); // 提取序號
var old = $(#m).val(); // 用戶輸入的其他內容
$(#m).val(old+[emoji+emoji+]);
$(.selectBox).css(display, "none");
});

客戶端收到之後將表情序號還原為src,更改如下

// chat-client.js

// 接收消息
socket.on(receiveMsg, (obj)=> {
// 提取文字中的表情加以渲染
var msg = obj.msg;
var content = ;
while(msg.indexOf([) > -1) { // 其實更建議用正則將[]中的內容提取出來
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 stylex="color: ${obj.color};">${content}</p>
</div>
</li>
`);
// 滾動條總是在最底部
$(#messages).scrollTop($(#messages)[0].scrollHeight);
});

可以成功發送表情了。

8、支持發送圖片

首先是圖片按鈕樣式,發送圖片的按鈕是type為file的input。這裡有一個改變樣式的小技巧,那就是將input的透明度設為0,z-index為5,將你想要得樣式放在div中,z-index設為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;
}

完美

接下來是點擊按鈕發送圖片,我們用了fileReader對象,這裡有一篇不錯的文章講解了fileReader,fileReader是一個對象,可以將我們選中的文件已64位輸出然後將結果存放在reader.result中,我們選中圖片之後,reader.result就存放的是圖片的src

// chat-client.js

// 用戶發送圖片
$(#file).change(function() {
var file = this.files[0]; // 上傳單張圖片
var reader = new FileReader();

//文件讀取出錯的時候觸發
reader.onerror = function(){
console.log(讀取文件失敗,請重試!);
};
// 讀取成功後
reader.onload = function() {
var src = reader.result; // 讀取結果
var img = <img class="sendImg" src="+src+">;
socket.emit(sendMsg, { // 發送
msg: img,
color: color,
type: img // 發送類型為img
});
};
reader.readAsDataURL(file); // 讀取為64位
});

由於發送的是圖片,所以對頁面佈局難免有影響,為了頁面美觀客戶端在接收其他用戶發送的消息的時候會先判斷發送的是文本還是圖片,根據不同的結果展示不同佈局。判斷的方法是在客戶發送消息的時候傳入一個type,根據type的值來確實發送內容的類型。所以上面發送圖片代碼中觸發了sendMsg事件,傳入參數多了一個type屬性。

響應的,我們應該在chat-client.js中修改receiveMsg事件監聽函數,改為根據傳入type做不同操作

chat-client.js
// 接收消息
socket.on(receiveMsg, (obj)=> {
// 發送為圖片
if(obj.type == img) {
$(#messages).append(`
<li class=${obj.side}>
<img src="${obj.img}">
<div>
<span>${obj.name}</span>
<p stylex="padding: 0;">${obj.msg}</p>
</div>
</li>
`);
$(#messages).scrollTop($(#messages)[0].scrollHeight);
return;
}

// 提取文字中的表情加以渲染
// 下面不變
});

現在我們可以發送圖片了

圓滿完成一個功能齊全的聊天室!

源碼地址:windlany/happy-chat,本文斷斷續續寫了兩天,真是寫文章比敲代碼還累...其實寫一個聊天室並不難,這算是node起步作品吧。有興趣的可以fork下來根據自己需求改改,覺得不錯請給我一個star。

參考鏈接

  • Socket.IO
  • Express 4.x - API 中文手冊
  • fileReader

推薦閱讀:

相關文章