JavaScript與Node.js一起打造一款聊天App
聊天是我們?nèi)伺c人交流最直接的方式,互聯(lián)網(wǎng)的加入使我們交流更加便捷。我們手機(jī)上的微信、QQ是我們手機(jī)必不可少的應(yīng)用軟件。那么,我們是否可以做一款聊天應(yīng)用呢?
之前我自己閑著沒事,研究過一些技術(shù),做了一款即時通訊應(yīng)用,下面我將選取幾幅具有代表性的圖片供大家參考。
一、應(yīng)用示圖
以上是這款應(yīng)用的主要頁面,功能可能相對簡陋點(diǎn),不過基本的功能已經(jīng)實(shí)現(xiàn)了,下面我將給出核心代碼,全部源碼地址在文末。
二、部分核心源碼
前臺主要核心邏輯:
這里我只列舉了js核心代碼,查看完整代碼可以去文末。
function sock() {
return io.connect("http://localhost:3003"); // http環(huán)境下
}
// 心跳機(jī)制
document.addEventListener('visibilitychange', function () {
if (document.visibilityState == 'hidden') {
//記錄頁面隱藏時間
sock()
console.log('隱藏了')
}
})
setInterval(() => {
sock()
}, 10000);
var socket = sock()
var re = document.querySelector("#re");
var register1 = document.querySelector(".register");
var init = document.querySelector(".init");
var passr = document.querySelector("#passr");
var passl = document.querySelector("#passl");
var login1 = document.querySelector(".login");
var register_b = document.querySelector("#register_b");
var lo = document.querySelector("#lo");
var chat = document.querySelector("#chat");
var login_b = document.querySelector("#login_b");
var myMes = "";
var vf = "";
var na = "";
var p = "";
var we = "";
var div = "";
var v = "";
var q = 0;
var regCn = /[@:]/im;
var pattern = /^[\u4E00-\u9FA5]{1,5}$/;
// 同意
document.querySelector('.yes').onclick=function () {
document.querySelector('.dark').style.display='none'
}
document.querySelector('.ys').onclick = function () {
document.querySelector('.dark').style.display = 'block'
}
// 初始頁面注冊
document.querySelector("#reg").onclick = function () {
register1.style.display = "block";
init.style.display = "none";
document.querySelector(".bg").style.display = "none";
}
// 初始頁面登錄
document.querySelector("#log").onclick = function () {
login1.style.display = "block";
init.style.display = "none";
document.querySelector(".bg").style.display = "none";
}
// 登錄按鈕
login_b.onclick = function () {
login();
}
// 注冊按鈕
register_b.onclick = function () {
register();
}
//發(fā)送
document.getElementById("btn").onclick = function () {
send();
};
// 內(nèi)容填充
document.getElementById("text").onkeyup = function () {
if (document.getElementById("text").value.length != 0) {
document.getElementById("btn").style.cssText = "background:#98E165;color:#fff;"
} else {
document.getElementById("btn").style.cssText = "background: #DDDEE2;color:#fff"
}
}
document.querySelector("#text").onclick = function () {
document.querySelector('#text').scrollIntoView(false);
}
// 傳名
var users2 = "";
socket.on('users', function (users) {
users2 = users;
// console.log(users2);
});
// 傳密碼
var pass2 = ""
socket.on('pass', function (val) {
pass2 = val;
// console.log(pass2)
});
// 統(tǒng)計(jì)在線人數(shù)
var arrh = []
socket.on('dataval', function (val) {
vf = val;
console.log(vf);
for (let i = 0; i < vf.length; i++) {
// uu++
arrh.push(vf[i])
console.log(arrh)
}
var rf = [...new Set(arrh)]
console.log(rf)
rf = vf
for (let j = 0; j < rf.length; j++) {
var li = document.createElement("li");
li.classList.add("active");
li.innerText = rf[j]
console.log(rf[j])
socket.emit("time", rf[j]);
document.querySelector(".fix").appendChild(li);
}
});
socket.on('join', function (val) {
document.querySelector(".fix").innerHTML = ''
})
socket.on('disconnect', function (val) {
console.log('離開了')
document.querySelector(".fix").innerHTML = ''
})
// 生成數(shù)組
var ar = "";
socket.on('array', function (val) {
ar = val;
// console.log(ar);
});
// 封裝注冊
function register() {
if (re.value.length == 0) {
sweetAlert("請輸入用戶名!");
return false;
} else if (regCn.test(re.value)) {
sweetAlert("格式錯誤,不能夠用和:符號取名,請重新輸入!");
return false;
} else if (pattern.test(re.value)) {
sweetAlert("不能使用中文字符哦!");
return false;
} else if (!(re.value.length == 0 && regCn.test(re.value))) {
if (users2.indexOf(re.value) != -1) {
sweetAlert("已經(jīng)注冊啦,換一個用戶名吧!");
} else {
names(re.value.trim());
pass(passr.value.trim());
sweetAlert("注冊成功,您的用戶名:" + re.value.trim());
document.querySelector(".swal-button").onclick = function () {
window.location.reload();
}
}
}
}
//移動端使用touchend
var event = navigator.userAgent.match(/(iPhone|iPod|Android|ios)/i) ? 'touchend' : 'click';
// 選擇器
var Q = function (id) {
return document.getElementById(id)
};
//右
var _right = new mSlider({
dom: ".layer-right",
direction: "right"
});
Q("btnRight").addEventListener(event, function (e) {
_right.open();
})
// 封裝登錄
function login() {
if (lo.value.length == 0) {
sweetAlert("請輸入用戶名!");
return false;
} else if (regCn.test(lo.value)) {
sweetAlert("格式錯誤,不能夠用和:符號取名,請重新輸入!");
return false;
} else if (pattern.test(lo.value)) {
sweetAlert("不能使用中文字符哦!");
return false;
} else if (!(lo.value.length == 0 && regCn.test(lo.value))) {
if (users2.indexOf(lo.value) != -1) {
for (var i = 0; i < users2.length; i++) {
if (users2[i] === lo.value && pass2[i] === passl.value) {
if (ar.indexOf(lo.value) == -1) {
sweetAlert("恭喜您,登錄成功!");
socket.emit('setName', lo.value.trim());
names1(lo.value.trim());
login1.style.display = "none";
document.querySelector(".bg").style.display = "none";
document.querySelector(".cd span").style.display = "none";
document.querySelector(".title img").style.display = "block";
document.querySelector(".fix").style.display = "block";
document.querySelector(".title").style.display = "block";
_right.open();
document.querySelector(".swal-button").onclick = function () {
document.getElementById("text").focus();
document.querySelector(".fix").addEventListener('click', function (e) {
if (e.target.nodeName === "LI" && e.target.innerText != document.title) {
_right.close();
document.querySelector(".chat_b").style.display = "block";
document.querySelector(".box").style.display = "block";
document.querySelector(".tit").innerText = e.target.innerText;
document.querySelector(".ys").style.display="none";
document.querySelector("#text").focus();
onOff = true;
} else {
sweetAlert("不能跟自己聊天哦~");
}
})
}
} else {
sweetAlert("不能重復(fù)登錄哦!");
return
}
}
if (users2[i] === lo.value && pass2[i] != passl.value) {
sweetAlert("密碼錯誤!");
return;
}
}
} else {
sweetAlert("請先注冊哦!");
login1.style.display = "none";
register1.style.display = "block";
}
}
}
// 傳名
function names(value) {
this.name = value;
socket.emit("reg", name);
}
function names1(value) {
this.name1 = value;
socket.emit("join", name1);
document.title = name1
}
// 傳密碼
function pass(value) {
socket.emit("pass", value);
}
socket.on("join", function (user) {
this.na = user;
})
socket.on("reg", function (user) {
this.na1 = user;
})
// 私發(fā)消息
socket.on('message1', function (data) {
var p1 = document.createElement("div");
var s1 = document.createElement("p");
var s2 = document.createElement("p");
var div1 = document.createElement("div");
var em = document.createElement("em");
var ads = document.createElement("audio");
ads.src = "https://www.maomin.club/data/res.mp3";
ads.className = "ads";
s1.className = "chatlist";
s2.className = "chatlist1";
em.className = "zwasked1";
div1.className = "divbox";
s1.innerText = data.from;
s2.innerText = data.msg;
s1.appendChild(em);
p1.appendChild(s1);
p1.appendChild(s2);
chat.appendChild(ads);
ads.play();
div1.appendChild(p1);
chat.appendChild(div1);
chat.scrollTop = chat.scrollHeight;
});
// 私聊發(fā)送
function send() {
if (document.getElementById("text").value != "") {
socket.emit('sayTo', {
from: lo.value,
to: document.querySelector(".tit").innerText,
msg: document.querySelector("#text").value,
})
var p1 = document.createElement("div");
var s1 = document.createElement("p");
var s2 = document.createElement("p");
var em = document.createElement("em");
var div1 = document.createElement("div");
var ads = document.createElement("audio");
p1.style.cssText = "float:right;";
s2.style.cssText = "color:#333;"
ads.src = "https://www.maomin.club/data/s.wav";
ads.className = "ads";
div1.className = "divbox";
s1.className = "chatlist";
s1.style.cssText = "color:#333 !important;float:right; !important";
s2.className = "chatlist2";
em.className = "zwasked";
s1.innerText = lo.value;
s2.innerText = document.querySelector("#text").value;
s1.appendChild(em);
p1.appendChild(s1);
p1.appendChild(s2);
chat.appendChild(ads);
ads.play();
div1.appendChild(p1);
chat.appendChild(div1);
chat.scrollTop = chat.scrollHeight;
} else {
sweetAlert('請輸入內(nèi)容!');
}
chat.scrollTop = chat.scrollHeight;
document.querySelector("#text").value = "";
document.querySelector("#text").focus();
}
后臺主要核心邏輯:
我這里只列舉了http環(huán)境的,完整代碼中有https環(huán)境的。
var http=require("http");
var fs=require("fs");
var express = require('express');
var ws=require("socket.io");
var path=require("path");
var _ = require('underscore');
var usocket = [];
var usocket1 = [];
var pass=[];
var data=[];
var hashName = {};
var onlineCount = 0;
var app = express();
// 靜態(tài)文件識別
app.use(express.static(path.join(__dirname, './public')));
var server=http.createServer(function (req,res) {
var filename = req.url.split('/')[req.url.split('/').length-1];
var suffix = req.url.split('.')[req.url.split('.').length-1];
if(req.url==='/'){
res.writeHead(200, {'Content-Type': 'text/html'});
var html = fs.readFileSync("./public/index.html");
res.end(html)
}else if(suffix==='css'){
res.writeHead(200, {'Content-Type': 'text/css'});
res.end(get_file_content(path.join(__dirname, 'public', 'css', filename)));
}else if(suffix==='js') {
res.writeHead(200, {'Content-Type': 'text/javascript'});
res.end(get_file_content(path.join(__dirname, 'public', 'js', filename)));
}else if (suffix in ['gif', 'jpeg', 'jpg', 'png']) {
res.writeHead(200, {
'Content-Type': 'image/' + suffix
});
res.end(get_file_content(path.join(__dirname, 'public', 'images', filename)));
}
});
function get_file_content(filepath) {
return fs.readFileSync(filepath);
}
// 獲取在線
function broadcast() {
io.sockets.emit("dataval", hashName);
}
//提供私有socket
function privateSocket(toId) {
return (_.findWhere(io.sockets.sockets, {
id: toId
}));
}
// 封裝刪除
function removeByValue(arr, val) {
for (var i = 0; i < arr.length; i++) {
if (arr[i] == val) {
arr.splice(i, 1);
break;
}
}
}
// 連接socket
var io=ws(server);
io.on("connection",function(socket){
// 寫入成功后讀取測試
fs.readFile('./user.xls', 'utf-8', function (err, data) {
if(data!=null){
var value = data.split('\n');
io.sockets.emit("users", value);
}
});
// 寫入成功后讀取測試
fs.readFile('./password.xls', 'utf-8', function (err,data) {
if(data!=null){
var pass1=data.split('\n');
io.sockets.emit("pass", pass1);
}
});
broadcast();
// 生成名字
socket.on('setName', function (data) {
var name = data;
hashName[name] = socket.id;
// console.log(hashName[name]);
broadcast();
});
// 私聊發(fā)送
socket.on('sayTo', function (data) {
var toName = data.to;
var toId;
console.log(toName);
if (toId = hashName[toName]) {
privateSocket(toId).emit('message1', data);
}
});
// 離開
socket.on('disconnect', function (name) {
name=this.i2;
io.emit("disconnect", name);
removeByValue(data, name);
io.sockets.emit("dataval", data);
})
// 在線
socket.on('time', function (val) {
// console.log(val);
})
// 注冊
socket.on("reg", function (name) {
usocket[name] = socket;
this.i1=name;
io.emit("reg", name);
var myname =this.i1+"\n";
fs.writeFile('./user.xls', myname, {
'flag': 'a'
}, function (err) {
if (err) {
throw err;
}
// 寫入成功后讀取測試
fs.readFile('./user.xls', 'utf-8', function (err,data) {
if (err) {
throw err;
}
});
});
})
// 加入
io.emit('connected', ++onlineCount);
// console.log(data);
io.sockets.emit("array", data);
socket.on("join", function (name) {
usocket1[name] = socket;
this.i2 = name;
io.emit("join", name);
data.push(name);
io.sockets.emit("dataval", data);
})
// 密碼
socket.on("pass",function(val){
pass[val]=socket;
this.i2=val;
io.emit("pass", val);
var password=this.i2+"\n";
fs.writeFile('./password.xls', password, {
'flag': 'a'
}, function (err) {
if (err) {
throw err;
}
});
})
});
server.listen(3003);
console.log("服務(wù)器運(yùn)行中");
三、源碼地址
這個項(xiàng)目是之前寫的,歡迎大家進(jìn)行指正。大家可以復(fù)制下面的源碼地址,拉取下來就可以在本地實(shí)現(xiàn)一個聊天服務(wù)。如果你有服務(wù)器可以把它部署在服務(wù)器上,這樣你就可以有一個屬于自己的聊天App了。大家可以根據(jù)源碼進(jìn)行學(xué)習(xí),有不明白的可以隨時問我。
作者:Vam的金豆之路
主要領(lǐng)域:前端開發(fā)
我的微信:maomin9761
微信公眾號:前端歷劫之路