您好,登錄后才能下訂單哦!
近來剛開始學node.js,基礎,深入,express,koa,react,keystone,mongress,等等各種各樣的關于Node.js的東西,每個都學的迷迷糊糊的,原因在于沒有練習,感覺學的每一個知識點就像一塊拼圖,最終卻沒有拼到一起,于是就想多寫幾個小項目練練手,于是,就有了本文。。。
----------------------------------我是分割線-----------------------------------------------
恩,就按我寫代碼的順序來記錄一下我的小成果。
github地址:https://github.com/maichonglyd/mySmailChat.git 該項目是在本地運行
先看文件結構,其實也沒幾個文件:
mySmailChat
|---web
| |---script
| | |---main.js
| |---index.css
| |---index.html
|---server.js
寫代碼總是有個先后順序的,做項目也是,那先做哪一塊后做哪一塊就會對整個項目有那么些影響,我在想,一個網頁聊天室,首先應該有的是網頁,不論是聊天還是后臺沒有網頁上的顯示就不能準確的知道代碼執行的結果,于是我就先做了網頁的頁面。
index.html:
<!DOCTYPE html> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <title>我的聊天室</title> <link rel="stylesheet" type="text/css" href="./index.css"> </head> <body> <div class="container"> <div class="titles"> <h2 >my smail chat</h2> </div> <div class="chatValue" id="chatValue"> </div> <div class="sends"> <input type="text" class="writeBox" placeHolder="請輸入文本" id="chatMsg"></input> <input type="button" class="sendButton" id="chatSendBtn" value="發送"></input> </div> </div> <div id="loginPage"> <p id="info">正在連接....</p> <div id="nickWrapper"> <input type="text" placeHolder="請輸入昵稱" id="nicknameInput" /> <input type="button" value="確定" id="loginBtn" /> </div> </div> <script src="/socket.io/socket.io.js"></script> <script src="script/main.js"></script> </body> </html>
整個頁面顯示如下:
在頁面上我用了npm倉庫中的socket.io,使用socket來連接服務端,進行即時通信,另外使用了main.js來裝載我們前端頁面上的邏輯處理代碼,呃,css樣式還用說么?好吧!!下面是css樣式的代碼:
index.css
body,html{ background-color: #000; vertical-align: center; } div.container{ width: 800px; height: 900px; margin-left: auto; margin-right: auto; background-color: #222; } div.titles{ text-align: center; background-color: #333; color: #888; padding: 10px; } div.chatValue{ width: 790px; margin:5px; height: 690px; overflow:auto; } div.sends{ width: 100%; height: 100px; background-color: #333; } input.writeBox{ height: 90px; width: 710px; margin-left: 3px; margin-top: 3px; margin-bottom: 3px; font-size: 20px; background-color: #333; border: 1px solid #222; color: #888; } input.sendButton{ width: 78px; height: 94px; margin-top: 3px; margin-bottom: 3px; margin-right: 3px; float: right; border: 1px solid #222; background-color: #444; color: #888; font-size: 24px; } #loginPage{ position: fixed; top: 0; left: 0; right: 0; bottom: 0; background-color: rgba(50,50,50,0.6); text-align: center; color: #888; display: block; padding-top: 400px; } .systemMsg{ width: 100%; text-align: center; color: #f00; }
這里面的內容就不用多說了,到目前為止,前端頁面已經做好了,下面就開始做服務端,先讓這個頁面能顯示出來:
server.js:
const express = require('express'); const app = express(); const http = require('http').Server(app); const io = require('socket.io')(http); let userList = []; http.listen(3000); app.use("/",express.static(__dirname+"/web"));
本項目依賴的包有express, http, socket.io.
express是node.js中管理路由響應請求的模塊,根據請求的URL返回相應的HTML頁面。這里我們使用一個事先寫好的靜態頁面返回給客戶端,只需使用express指定要返回的頁面的路徑即可。如果不用這個包,我們需要將HTML代碼與后臺JavaScript代碼寫在一起進行請求的響應,不太方便。
http 是node.js的核心模塊,不了解的同學就自己查一下吧,這里就不多說了。
socket.io 是用來做socket連接的模塊,我們需要把這幾個包相互綁定一下,于是就有了
const http = require('http').Server(app); const io = require('socket.io')(http);
這兩行代碼,然后我們監聽了3000端口,指定了要返回給前端的靜態頁面路徑。
app.use("/",express.static(__dirname+"/web"));
在這里我們打開瀏覽器 輸入 http://localhost:3000 就能看見我們寫的界面了。當然輸入昵稱那個圖應該不會太正,那個圖的效果是需要連接上服務器進行一些js操作才會看起來更好看嘀,好吧,那我就需要先把服務器架起來,讓前端頁面可以連接上:
server.js:
io.on("connection",userConnction); function userConnction(socket){ console.log("a user connecting!!"); let user = new User("",socket); userList.push(user); } class User{ constructor(name,socket){ this.nickName = name; this.socket = socket; } setName(name){ this.nickName = name; } setSocket(){ this.socket = socket; } getName(){ return this.nickName; } getSocket(){ return this.socket; } }
io 監聽連接成功事件,觸發回調函數userConnection,服務端會輸出一句話說明有用戶連接上了。然后把這個用戶先保存到數組里,既然是后臺監聽,那只有前端去主動連接了,好吧,那就前端主動連接:
main.js:
var nickName = ""; window.onload = function(){ init(); } function init(){ var info = document.getElementById("info"); var nickWrapper = document.getElementById("nickWrapper"); var nicknameInput = document.getElementById("nicknameInput"); var loginBtn = document.getElementById("loginBtn"); var chatMsg = document.getElementById("chatMsg"); var chatSendBtn = document.getElementById("chatSendBtn"); var loginPage = document.getElementById("loginPage"); info.textContent = "正在連接...." nickWrapper.style.display = "none"; socket = io.connect("http://localhost:3000"); socket.on("connect", function (){ info.textContent = "請輸入昵稱!"; nickWrapper.style.display = "block"; nicknameInput.focus(); }); }
希望上面那一片doucument.getElementById 沒有讓你看暈。我是把頁面里用到的元素都先提前獲取到了,下面就直接寫獲取后的變量名,不再寫那么長了。
這里我們應該在網頁剛加載完的時候就要先連接服務器,
socket = io.connect("http://localhost:3000");
連上之后把輸入昵稱那個圖變成遮罩層蓋住所有的操作區域,讓用戶輸入名字,把焦點指向輸入名字的文本框。
socket.on("connect", function (){......};
輸入完名字之后我們需要把名字傳到后臺,應該在點擊事件中完成:
main.js:
loginBtn.addEventListener("click",function (){ var _nickName = nicknameInput.value; if(_nickName.trim().length!=0){ socket.emit("login",_nickName); nickName = _nickName; }else{ nicknameInput.focus(); nicknameInput.value = ""; } },false);
點擊事件中我們先獲取文本框的值,去掉兩端的空格之后如果長度不等于0那就說明輸入的有內容,然后就向后臺派發事件 login,再把名字傳過去。同時我們聲明一個全局變量nickName,記錄下自己的名字。
socket.emit("login",_nickName); nickName = _nickName;
既然我們向后臺派發了事件,那后臺應該要有接收的地方:
server.js:
socket.on("login",function (data){ if(checkName(data)){ socket.emit("login","ok"); addName(data,socket); io.sockets.emit("system_login",data,userList.length); }else{ socket.emit("login","have"); } }); function checkName(nickName){ for(let i = 0,length=userList.length;i<length;i++){ if(nickName == userList[i].nickName){ return false; } } return true; } function addName(name,socket){ for(let i = 0,length=userList.length;i<length;i++){ if(userList[i].socket === socket){ userList[i].nickName = name; console.log("nickName:",name); } } }
后臺監聽前臺派發的login事件,通過checkName函數來檢查是否有重復名字,如果有就向前臺派發login事件,帶上參數"have",告訴前臺名字已有,沒有重復也派發lonin事件,帶上參數"ok"告訴前臺名字可用。然后用addName把名字記錄下來。
socket.emit("login","ok"); addName(data,socket);
然后告訴所有的用戶有新用戶登錄進來了.
io.sockets.emit("system_login",data,userList.length);
這里有個容易迷糊的地方:
login事件,是這樣的,socket派發的事件只需要前后臺對應就行,前臺派發login,后臺要有相應的接受,后臺派發login,前臺要有相應的接收,但前臺派發login,前臺就算有Login的接收也是沒用的。
在后端通知前端名字可用派發了login事件,那前臺要有相應的接收:
main.js:
socket.on("login",function (data){ if(data == "ok"){ loginPage.style.display = "none"; document.title = "我的聊天室 | " + nickName; chatMsg.focus(); }else if(data == "have"){ info.textContent = "昵稱被占用,請重新輸入!"; nicknameInput.value = ""; nicknameInput.focus(); } });
接收到以后,通過附帶的參數來確定名字是否可用,可用就隱藏遮罩層,把自己的名字顯示到網頁的標題上,把焦點給發送信息的文本框,不可用就提示昵稱被占用,清空輸入名字的文本框,并給予焦點。
剛才后臺還派發了一個事件,是告訴所有用戶有新用戶登錄進來了,我們需要接收這個事件:
main.js:
socket.on("system_login",function (data,count){ if(data == nickName){ insertMsg("system_login","我","已加入聊天室,目前有" + count + "位用戶在線"); }else{ insertMsg("system_login",data,"已加入聊天室,目前有" + count + "位用戶在線"); } }); function insertMsg(type,name,msg){ var color = "#f00"; var space = ":"; switch(type){ case "system_login": case "system_logout": color="#f00"; space = ""; break; case "userChat": if(name == nickName){ color = "#080"; name = "我"; }else{ color = "#088"; } space = ":<br/> " break; } var chatValue = document.getElementById("chatValue"); var newNode = document.createElement("div"); newNode.innerHTML = "<p width='790px' style='word-wrap:break-word'><font color='"+color+"'>"+name +space+msg+"</font></p>"; chatValue.appendChild(newNode); chatValue.scrollTop = chatValue.scrollHeight; }
這里我做了一個處理,如果新登錄用戶是自己的名字,那就顯示 “我xxxx”;
這里我用insertMsg函數處理要顯示在聊天框里的內容,其實就是通過判斷不同的消息來源顯示不同的字號,顏色及分割符,因為我們的聊天框是一個DIV,我們要往里面添加新的元素來顯示每個人說的話,包括系統,
var chatValue = document.getElementById("chatValue"); var newNode = document.createElement("div"); newNode.innerHTML = "<p width='790px' style='word-wrap:break-word'><font color='"+color+"'>"+name +space+msg+"</font></p>"; chatValue.appendChild(newNode);
聊天記錄是不斷從下面添加,越是下面的記錄,越是最新的,所以我們應該把滾動條設置到最底部:
chatValue.scrollTop = chatValue.scrollHeight;
既然有登錄那就有退出,退出也會有一個事件,我們應該監聽這個事件,在監聽到有用戶退出的時候后臺應該告訴所有用戶有人走了:
server.js:
socket.on("disconnect",userDisconnect); function userDisconnect(){ let i = 0; while(i<userList.length){ if(userList[i].socket.disconnected){ if(userList[i].nickName.trim().length!=0){ io.sockets.emit("system_logout",userList[i].nickName,userList.length-1); } userList.splice(i,1); i--; } i++; } }
這里我用循環來判斷是否有用戶已斷開連接:
if(userList[i].socket.disconnected)
當有用戶斷開的話就發個通知告訴所有用戶這個人離開了,還剩下多少人:
io.sockets.emit("system_logout",userList[i].nickName,userList.length-1);
好吧,后臺又發通知了,前臺也要接收:
main.js:
socket.on("system_logout",function (data,count){ insertMsg("system_logout",data,"已離開聊天室,目前有" + count + "位用戶在線"); });
到目前為止,準備工做已經做的差不多了,還有最重要的一件事沒做,那就是聊天,聊天內容需要我們點擊按鈕發送到服務器:
main.js:
chatSendBtn.addEventListener("click",function(){ var _msg = chatMsg.value; if(_msg.trim().length !=0){ socket.emit("userChat",_msg); } chatMsg.value = ""; chatMsg.focus(); });
發送的內容長度不算空格也不等于0的話,說明發送的是有真實內容的,那就派發給后臺:
socket.emit("userChat",_msg);
同時要清空說話框并把焦點給它。
chatMsg.value = ""; chatMsg.focus();
后臺,后臺,快出來,我有話要對大家說:
server.js:
socket.on("userChat",function(data){ for(let i = 0,length=userList.length;i<length;i++){ if(socket === userList[i].getSocket()){ let msgObj = { nickName : userList[i].getName(), msg : data }; io.sockets.emit("userChat",msgObj); } } });
后臺腦子里記了很多人,需要知道你是誰,再把你要說的話和你的名字告訴大家,于是就需要檢測socket對應的名字是誰然后連同發送的話一起推送給大家:
io.sockets.emit("userChat",msgObj);
呃,忘了解釋一下:io.sockets.emit 是給所有連接上的客戶端派發事件,某個socket.emit是給當前這個socket派發事件。
好吧,又是前臺要接收:
main.js:
socket.on("userChat",function (data){ insertMsg("userChat",data.nickName,data.msg); });
到了這里,項目基本上完成了。看看效果吧,
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。