項目已經部署,請訪問: "chat.mycollagelife.com"
這是整個實戰系列的第三章,這三章關聯性較大,建議先閱讀第一 , 第二兩章,本章實現了在線成員的動態顯示,和頭像的匹配,單聊的設置,以及消息的美化,該系列教程非常詳細,建議耐心讀完。
實現的結果如圖:
登錄:
羣聊,用戶列表
單聊:
該項目已經上傳至github https://github.com/neuqzxy/chat ,覺的可以的話,給個星星吧。
技術要求:
本章沒有使用到什麼其他的技術,如果你符合第二章的要求,就可以完成本章內容
實現在線用戶列表
準備工作
1. 先拷貝一份第二章的源碼到chat++文件夾中。
2. 邏輯分析:
在線用戶列表是通過服務器端的全部成員數組來實現的,
首先,我們在登錄之後要初始化用戶列表,這需要服務器端返回一個數組,最少包括用戶名和頭像信息。
之後,處於登錄狀態,每次新增成員只需要服務器端廣播該成員用戶名和頭像即可。
3. 界面調整:
我們要將界面分爲兩部分左部爲用戶列表,右部爲聊天界面,並且聊天區域要設置最大的高度,超出顯示滾動條。
創建靜態文件
首先,用bootstrap的樣式,將頁面分成兩部分,現在的所有內容爲一部分,用戶列表爲另一部分,設置用戶列表id爲usergroup
<div style="display: none;" id="chatbox">
<div id="usergroup" class="col-md-2">
</div>
<div class="col-md-9 col-md-offset-1">
<div class="alert alert-info alert-dismissible fade in" role="alert" id="myalert">
<button type="button" class="close" data-dismiss="alert" aria-label="Close"><span aria-hidden="true">×</span></button>
</div>
<div class="alert alert-info alert-dismissible fade in" role="alert" id="myalert1">
<button type="button" class="close" data-dismiss="alert" aria-label="Close"><span aria-hidden="true">×</span></button>
<span></span>
</div>
<div id="content"></div>
<div id="inputgrop">
<div class="col-md-10">
<input type="text" placeholder="saying somgthing" class="form-control chatinput" id="chatinput">
</div>
<form action="" style="display: none;" id="resetform"><input type="file" style="display: none" id="imginput"></form>
<div class="col-md-2">
<button type="button" class="btn btn-primary" id="imgbutton">發送圖片</button>
</div>
</div>
</div>
</div>
接下來我們使用bootstrap中的標籤,顯示在線成員字樣,然後下面就是成員列表了:
<span class="label label-primary" style="font-size: 2em;">在線成員</span>
成員列表我們使用bootstrap中的列表組來實現
<div id="usergroup" class="col-md-2">
<span class="label label-primary" style="font-size: 2em;">在線成員</span>
<div class="list-group"></div>
</div>
現在看一下,已經成型了,但是聊天輸入框的定位失效了,接下來我們打開開發者工具,發現html,chatbox 和裏面兩個div的高度全變成失效了,我們設置以下屬性:
html,body {
background: #ccc;
padding: 20px 0 20px 0;
height: 100%;
}
#chatbox {
height: 100%;
}
然後再聊天區域的div裏設置一個class,設置高度100%。
<div class="col-md-9 chatwindow col-md-offset-1">
.chatwindow {
height: 100%;
}
現在就可以啦。初始化頁面時顯示用戶列表
當我們登錄進去的時候要渲染出現在在線的所有用戶,並且讓自己排在第一位。
我們打開app.js在loginSuccess中添加一行,將所有用戶數組傳過去
//然後觸發loginSuccess事件告訴瀏覽器登陸成功了,廣播形式觸發
data.userGroup = users; //將所有用戶數組傳過去
io.emit('loginSuccess',data); //將data原封不動的再發給該瀏覽器
在main.js中,參照bootstrap的鏈接樣式:
<div class="list-group">
<a href="#" class="list-group-item active">
Cras justo odio
</a>
<a href="#" class="list-group-item">Dapibus ac facilisis in</a>
<a href="#" class="list-group-item">Morbi leo risus</a>
<a href="#" class="list-group-item">Porta ac consectetur ac</a>
<a href="#" class="list-group-item">Vestibulum at eros</a>
</div>
我們這麼寫: /**
* 用戶列表渲染
* 先添加自己,在從data中找到別人添加進去
*/
_$listGroup.append(`<a href="#" class="list-group-item">${_username}</a>`);
//添加別人
for(let _user of data.userGroup) {
if (_user.username !== _username) {
_$listGroup.append(`<a href="#" class="list-group-item">${_user.username}</a>`);
}
}
注意傳入data。 這樣就渲染出來了。
用戶上下線時列表更新
這部分邏輯要重新寫監聽事件:
首先我們在loginSuccess中寫上線時列表更新,加一行就行:
socket.on('loginSuccess',(data)=>{
/**
* 如果服務器返回的用戶名和剛剛發送的相同的話,就登錄
* 否則說明有地方出問題了,拒絕登錄
*/
if(data.username === _username) {
beginChat(data);
}else {
$('#myalert1 span').html(`<span>您的好友<strong>${data.username}</strong>上線了!</span>`);
setTimeout(function() {
$("#myalert1").hide();
}, 1000);
$("#myalert1").show();
//用戶列表添加該用戶
_$listGroup.append(`<a href="#" class="list-group-item">${data.username}</a>`);
}
});
然後我們寫用戶離開的監聽。
//斷開連接後做的事情
socket.on('disconnect',()=>{ //注意,該事件不需要自定義觸發器,系統會自動調用
usersNum --;
console.log(`當前有${usersNum}個用戶連接上服務器了`);
//觸發用戶離開的監聽
socket.broadcast.emit("oneLeave",{username: socket.username});
//刪除用戶
users.forEach(function (user,index) {
if(user.username === socket.username) {
users.splice(index,1); //找到該用戶,刪除
}
})
})
之所以用socket.broadcast.io是因爲我們不需要離開的用戶知道了。
在main.js中監聽,這裏我們有兩種方式,第一種:用數組記錄下用戶的列表,刪除該用戶,使用數組重新渲染。第二種:提前在DOM中標記,找到所在元素,刪除。
這裏顯然第二種方式更加適合。
我們找到剛纔添加的那些用戶的<a>標籤,給他們添加name屬性,值爲用戶名。
然後寫:
socket.on('oneLeave',(data)=>{
//找到該用戶並刪除
_$listGroup.find($(`a[name='${data.username}']`)).remove();
});
用戶下線提示,及功能封裝
還記得我們的好友上線提醒嗎?我們現在寫下線提醒:
我們複製一個紅色警告框,
<div class="alert alert-danger alert-dismissible fade in" role="alert" id="myalert2">
<button type="button" class="close" data-dismiss="alert" aria-label="Close"><span aria-hidden="true">×</span></button>
<span></span>
</div>
然後讓他隱藏,在beginChat裏寫:
$("#myalert2").hide();
到了這裏我們直接寫一個函數,將好友上下線的功能寫進去:
/**
*
* @param flag 爲1代表好友上線,-1代表好友下線
* @param data 存儲用戶信息
*/
let comAndLeave = function (flag,data) {
//上線顯示警告框,用戶列表添加一個
if(flag === 1) {
$('#myalert1 span').html(`<span>您的好友<strong>${data.username}</strong>上線了!</span>`);
setTimeout(function() {
$("#myalert1").hide();
}, 1000);
$("#myalert1").show();
//用戶列表添加該用戶
_$listGroup.append(`<a href="#" name="${data.username}" class="list-group-item">${data.username}</a>`);
} else {
//下線顯示警告框,用戶列表刪除一個
$('#myalert2 span').html(`<span>您的好友<strong>${data.username}</strong>下線了!</span>`);
setTimeout(function() {
$("#myalert2").hide();
}, 1000);
$("#myalert2").show();
//找到該用戶並刪除
_$listGroup.find($(`a[name='${data.username}']`)).remove();
}
};
函數內容很簡單,全都是複製之前的代碼,現在loginSuccess和onLeave都只需要調用一下該函數就行了
socket.on('oneLeave',(data)=>{
comAndLeave(-1,data);
});
socket.on('loginSuccess',(data)=>{
/**
* 如果服務器返回的用戶名和剛剛發送的相同的話,就登錄
* 否則說明有地方出問題了,拒絕登錄
*/
if(data.username === _username) {
beginChat(data);
}else {
comAndLeave(1,data);
}
});
設置用戶頭像
到了這一步還不夠,我們想有一個頭像,這個頭像是隨機分配,但是在所有用戶界面看都是相同的,所以我們需要隨機選一個圖片並廣播給所有人。
我們進入阿里的圖標庫,選你喜歡的圖片吧,可以添加至項目中以外鏈的形式加入到源碼中。我已經選好了:
最後一個是自己,每個人隨機頭像自己是看不到的,在自己的界面只能看到最後的那個人頭。
具體用法官網很清楚,我使用的是Symbol,因爲他支持彩色。
我們打開使用幫助:
設置URL,css:
<script src="http://at.alicdn.com/t/font_o86wdrgtu766r.js"></script>
<style>
.icon {
width: 1em; height: 1em;
vertical-align: -0.15em;
fill: currentColor;
overflow: hidden;
}
</style>
注意,每個人的URL不同。
在showMessage,showImg,beginChat等地方設置本人頭像:
/**
* Created by zhouxinyu on 2017/8/6.
*/
$(function(){
const url = 'http://127.0.0.1:3000';
let _username = null;
let _$inputname = $("#name");
let _$loginButton = $("#loginbutton");
let _$chatinput = $("#chatinput");
let _$inputGroup = $("#inputgrop");
let _$imgButton = $("#imgbutton");
let _$imgInput = $("#imginput");
let _$listGroup = $(".list-group");
let socket = io.connect(url);
//設置用戶名,當用戶登錄的時候觸發
let setUsername = function () {
_username = _$inputname.val().trim(); //得到輸入框中用戶輸入的用戶名
//判斷用戶名是否存在
if(_username) {
socket.emit('login',{username: _username}); //如果用戶名存在,就代表可以登錄了,我們就觸發登錄事件,就相當於告訴服務器我們要登錄了
}
};
let beginChat = function (data) {
/**
* 1.隱藏登錄框,取消它綁定的事件
* 2.顯示聊天界面
*/
$("#loginbox").hide('slow');
_$inputname.off('keyup');
_$loginButton.off('click');
/**
* 顯示聊天界面,並顯示一行文字,表示是誰的聊天界面
* 一個2s的彈框,顯示歡迎字樣
* 這裏我使用了ES6的語法``中可以使用${}在裏面寫的變量可以直接被瀏覽器渲染
*/
$(`<h2 style="text-align: center">${_username}的聊天室</h2>`).insertBefore($("#content"));
$(`<strong>歡迎你</strong><span>${_username}!</span>`).insertAfter($('#myalert button'));
$("#myalert1").hide();
$("#myalert2").hide();
$('#myalert').alert();
setTimeout(function () {
$('#myalert').alert('close');
},2000);
$("#chatbox").show('slow');
/**
* 用戶列表渲染
* 先添加自己,在從data中找到別人添加進去
*/
_$listGroup.append(`<a href="#" name="${_username}" class="list-group-item"><svg class="icon" aria-hidden="true" style="font-size: 2em"><use xlink:href="#icon-yonghu"></use></svg> ${_username}</a>`);
//添加別人
for(let _user of data.userGroup) {
if (_user.username !== _username) {
_$listGroup.append(`<a href="#" name="${_user.username}" class="list-group-item">${_user.username}</a>`);
}
}
};
let sendMessage = function () {
/**
* 得到輸入框的聊天信息,如果不爲空,就觸發sendMessage
* 將信息和用戶名發送過去
*/
let _message = _$chatinput.val();
if(_message) {
socket.emit('sendMessage',{username: _username, message: _message});
}
};
let setInputPosition = function () {
let height = $(window).height()>$('#content p:last').offset().top+$('#content p:last').height()*2?$(window).height():$('#content p:last').offset().top+$('#content p:last').height()*2;
_$inputGroup.css({'top': height});
};
let showMessage = function (data) {
//先判斷這個消息是不是自己發出的,然後再以不同的樣式顯示
if(data.username === _username){
$("#content").append(`<p style='background: lightskyblue'><svg class="icon" aria-hidden="true" style="font-size: 2em"><use xlink:href="#icon-yonghu"></use></svg> <span>${data.username} : </span> ${data.message}</p>`);
}else {
$("#content").append(`<p style='background: lightpink'><span>${data.username} : </span> ${data.message}</p>`);
}
setInputPosition();
};
let sendImg = function (event) {
/**
* 先判斷瀏覽器是否支持FileReader
*/
if (typeof FileReader === 'undefined') {
alert('您的瀏覽器不支持,該更新了');
//使用bootstrap的樣式禁用Button
_$imgButton.attr('disabled', 'disabled');
} else {
let file = event.target.files[0]; //先得到選中的文件
//判斷文件是否是圖片
if(!/image\/\w+/.test(file.type)){ //如果不是圖片
alert ("請選擇圖片");
return false;
}
/**
* 然後使用FileReader讀取文件
*/
let reader = new FileReader();
reader.readAsDataURL(file);
/**
* 讀取完自動觸發onload函數,我們觸發sendImg事件給服務器
*/
reader.onload = function (e) {
socket.emit('sendImg',{username: _username, dataUrl: this.result});
}
}
};
let showImg = function (data) {
//先判斷這個消息是不是自己發出的,然後再以不同的樣式顯示
if(data.username === _username) {
$('#content').append(`<p class="bg-primary" style=' text-align:center;'><svg class="icon" aria-hidden="true" style="font-size: 2em"><use xlink:href="#icon-yonghu"></use></svg> <strong style="font-size: 1.5em;">${_username} </strong>: <img class="img-thumbnail" src="${data.dataUrl}" style="max-height: 100px"/></p>`);
} else {
$('#content').append(`<p class="bg-info" style=''><strong style="font-size: 1.5em;">${_username} </strong>: <img class="img-thumbnail" src="${data.dataUrl}" style="max-height: 100px"/></p>`);
}
setInputPosition();
};
/**
*
* @param flag 爲1代表好友上線,-1代表好友下線
* @param data 存儲用戶信息
*/
let comAndLeave = function (flag,data) {
//上線顯示警告框,用戶列表添加一個
if(flag === 1) {
$('#myalert1 span').html(`<span>您的好友<strong>${data.username}</strong>上線了!</span>`);
setTimeout(function() {
$("#myalert1").hide();
}, 1000);
$("#myalert1").show();
//用戶列表添加該用戶
_$listGroup.append(`<a href="#" name="${data.username}" class="list-group-item"><svg class="icon" aria-hidden="true"><use xlink:href="#icon-yonghu" style="font-size: 2em"></use></svg>${data.username}</a>`);
} else {
//下線顯示警告框,用戶列表刪除一個
$('#myalert2 span').html(`<span>您的好友<strong>${data.username}</strong>下線了!</span>`);
setTimeout(function() {
$("#myalert2").hide();
}, 1000);
$("#myalert2").show();
//找到該用戶並刪除
_$listGroup.find($(`a[name='${data.username}']`)).remove();
}
};
/* 前端事件 */
/*登錄事件*/
_$loginButton.on('click',function (event) { //監聽按鈕的點擊事件,如果點擊,就說明用戶要登錄,就執行setUsername函數
setUsername();
});
_$inputname.on('keyup',function (event) { //監聽輸入框的回車事件,這樣用戶回車也能登錄。
if(event.keyCode === 13) { //如果用戶輸入的是回車鍵,就執行setUsername函數
setUsername();
}
});
/*聊天事件*/
_$chatinput.on('keyup',function (event) {
if(event.keyCode === 13) {
sendMessage();
_$chatinput.val('');
}
});
//點擊圖片按鈕觸發input
_$imgButton.on('click',function (event) {
_$imgInput.click();
return false;
});
_$imgInput.change(function (event) {
sendImg(event);
//重置一下form元素,否則如果發同一張圖片不會觸發change事件
$("#resetform")[0].reset();
});
/* socket.io部分邏輯 */
socket.on('loginSuccess',(data)=>{
/**
* 如果服務器返回的用戶名和剛剛發送的相同的話,就登錄
* 否則說明有地方出問題了,拒絕登錄
*/
if(data.username === _username) {
beginChat(data);
}else {
comAndLeave(1,data);
}
});
socket.on('receiveMessage',(data)=>{
/**
* 監聽到事件發生,就顯示信息
*/
showMessage(data);
});
socket.on('usernameErr',(data)=>{
/**
* 我們給外部div添加 .has-error
* 拷貝label插入
* 控制顯示的時間爲1.5s
*/
$(".login .form-inline .form-group").addClass("has-error");
$('<label class="control-label" for="inputError1">用戶名重複</label>').insertAfter($('#name'));
setTimeout(function() {
$('.login .form-inline .form-group').removeClass('has-error');
$("#name + label").remove();
}, 1500)
});
socket.on('receiveImg',(data)=>{
/**
* 監聽到receiveImg發生,就顯示圖片
*/
showImg(data);
});
socket.on('oneLeave',(data)=>{
comAndLeave(-1,data);
});
});
然後我們給所有人設置一個隨機頭像
//隨機設置頭像的地址
let touXiang = function (url) {
let _url = url || (Math.random()*8 | 0);
switch (_url) {
case 0 :
return "icon-river__easyiconnet1";
case 1 :
return "icon-river__easyiconnet";
case 2 :
return "icon-photo_camera__easyiconnet";
case 3 :
return "icon-planet_earth__easyiconnet";
case 4 :
return "icon-palace__easyiconnet";
case 5 :
return "icon-mountain__easyiconnet";
case 6 :
return "icon-parachute__easyiconnet";
case 7 :
return "icon-map__easyiconnet";
case 8 :
return "icon-mountains__easyiconnet";
case -1 :
return "icon-yonghu"
}
};
這個就是一個隨機生成頭像url的函數,沒什麼難度。
//設置用戶名,當用戶登錄的時候觸發
let setUsername = function () {
_username = _$inputname.val().trim(); //得到輸入框中用戶輸入的用戶名
//判斷用戶名是否存在
if(_username) {
socket.emit('login',{username: _username, touXiangUrl: touXiang()}); //如果用戶名存在,就代表可以登錄了,我們就觸發登錄事件,就相當於告訴服務器我們要登錄了
}
};
然後我們將頭像URL也傳給服務器
//如果用戶名存在。將該用戶的信息存進數組中
if(socket.username){
users.push({
username: data.username,
message: [],
dataUrl:[],
touXiangUrl:data.touXiangUrl
});
我們現在只需要在每一個傳username的地方再傳一個頭像地址就行,然後,在showMwssage,showImg,beginChat地方設置我們的頭像即可
/**
* Created by zhouxinyu on 2017/8/6.
*/
$(function(){
const url = 'http://127.0.0.1:3000';
let _username = null;
let _$inputname = $("#name");
let _$loginButton = $("#loginbutton");
let _$chatinput = $("#chatinput");
let _$inputGroup = $("#inputgrop");
let _$imgButton = $("#imgbutton");
let _$imgInput = $("#imginput");
let _$listGroup = $(".list-group");
let _touXiangUrl = null;
let socket = io.connect(url);
//隨機設置頭像的地址
let touXiang = function (url) {
let _url = url || (Math.random()*8 | 0);
switch (_url) {
case 0 :
return "icon-river__easyiconnet1";
case 1 :
return "icon-river__easyiconnet";
case 2 :
return "icon-photo_camera__easyiconnet";
case 3 :
return "icon-planet_earth__easyiconnet";
case 4 :
return "icon-palace__easyiconnet";
case 5 :
return "icon-mountain__easyiconnet";
case 6 :
return "icon-parachute__easyiconnet";
case 7 :
return "icon-map__easyiconnet";
case 8 :
return "icon-mountains__easyiconnet";
case -1 :
return "icon-yonghu"
}
};
//設置用戶名,當用戶登錄的時候觸發
let setUsername = function () {
_username = _$inputname.val().trim(); //得到輸入框中用戶輸入的用戶名
_touXiangUrl = touXiang();
//判斷用戶名是否存在
if(_username) {
socket.emit('login',{username: _username, touXiangUrl: _touXiangUrl}); //如果用戶名存在,就代表可以登錄了,我們就觸發登錄事件,就相當於告訴服務器我們要登錄了
}
};
let beginChat = function (data) {
/**
* 1.隱藏登錄框,取消它綁定的事件
* 2.顯示聊天界面
*/
$("#loginbox").hide('slow');
_$inputname.off('keyup');
_$loginButton.off('click');
/**
* 顯示聊天界面,並顯示一行文字,表示是誰的聊天界面
* 一個2s的彈框,顯示歡迎字樣
* 這裏我使用了ES6的語法``中可以使用${}在裏面寫的變量可以直接被瀏覽器渲染
*/
$(`<h2 style="text-align: center">${_username}的聊天室</h2>`).insertBefore($("#content"));
$(`<strong>歡迎你</strong><span>${_username}!</span>`).insertAfter($('#myalert button'));
$("#myalert1").hide();
$("#myalert2").hide();
$('#myalert').alert();
setTimeout(function () {
$('#myalert').alert('close');
},2000);
$("#chatbox").show('slow');
/**
* 用戶列表渲染
* 先添加自己,在從data中找到別人添加進去
*/
_$listGroup.append(`<a href="#" name="${_username}" class="list-group-item"><svg class="icon" aria-hidden="true" style="font-size: 2em"><use xlink:href="#icon-yonghu"></use></svg> ${_username}</a>`);
//添加別人
for(let _user of data.userGroup) {
if (_user.username !== _username) {
_$listGroup.append(`<a href="#" name="${_user.username}" class="list-group-item"><svg class="icon" aria-hidden="true" style="font-size: 2em"><use xlink:href="#${_user.touXiangUrl}"></use></svg> ${_user.username}</a>`);
}
}
};
let sendMessage = function () {
/**
* 得到輸入框的聊天信息,如果不爲空,就觸發sendMessage
* 將信息和用戶名發送過去
*/
let _message = _$chatinput.val();
if(_message) {
socket.emit('sendMessage',{username: _username, message: _message, touXiangUrl: _touXiangUrl});
}
};
let setInputPosition = function () {
let height = $(window).height()>$('#content p:last').offset().top+$('#content p:last').height()*2?$(window).height():$('#content p:last').offset().top+$('#content p:last').height()*2;
_$inputGroup.css({'top': height});
};
let showMessage = function (data) {
//先判斷這個消息是不是自己發出的,然後再以不同的樣式顯示
if(data.username === _username){
$("#content").append(`<p style='background: lightskyblue'><svg class="icon" aria-hidden="true" style="font-size: 2em"><use xlink:href="#icon-yonghu"></use></svg> <span>${data.username} : </span> ${data.message}</p>`);
}else {
$("#content").append(`<p style='background: lightpink'><svg class="icon" aria-hidden="true" style="font-size: 2em"><use xlink:href="#${data.touXiangUrl}"></use></svg> <span>${data.username} : </span> ${data.message}</p>`);
}
setInputPosition();
};
let sendImg = function (event) {
/**
* 先判斷瀏覽器是否支持FileReader
*/
if (typeof FileReader === 'undefined') {
alert('您的瀏覽器不支持,該更新了');
//使用bootstrap的樣式禁用Button
_$imgButton.attr('disabled', 'disabled');
} else {
let file = event.target.files[0]; //先得到選中的文件
//判斷文件是否是圖片
if(!/image\/\w+/.test(file.type)){ //如果不是圖片
alert ("請選擇圖片");
return false;
}
/**
* 然後使用FileReader讀取文件
*/
let reader = new FileReader();
reader.readAsDataURL(file);
/**
* 讀取完自動觸發onload函數,我們觸發sendImg事件給服務器
*/
reader.onload = function (e) {
socket.emit('sendImg',{username: _username, dataUrl: this.result, touXiangUrl: _touXiangUrl});
}
}
};
let showImg = function (data) {
//先判斷這個消息是不是自己發出的,然後再以不同的樣式顯示
if(data.username === _username) {
$('#content').append(`<p class="bg-primary" style=' text-align:center;'><svg class="icon" aria-hidden="true" style="font-size: 2em"><use xlink:href="#icon-yonghu"></use></svg> <strong style="font-size: 1.5em;">${_username} </strong>: <img class="img-thumbnail" src="${data.dataUrl}" style="max-height: 100px"/></p>`);
} else {
$('#content').append(`<p class="bg-info" style=''><svg class="icon" aria-hidden="true" style="font-size: 2em"><use xlink:href="#${data.touXiangUrl}"></use></svg> <strong style="font-size: 1.5em;">${data.username} </strong>: <img class="img-thumbnail" src="${data.dataUrl}" style="max-height: 100px"/></p>`);
}
setInputPosition();
};
/**
*
* @param flag 爲1代表好友上線,-1代表好友下線
* @param data 存儲用戶信息
*/
let comAndLeave = function (flag,data) {
//上線顯示警告框,用戶列表添加一個
if(flag === 1) {
$('#myalert1 span').html(`<span>您的好友<strong>${data.username}</strong>上線了!</span>`);
setTimeout(function() {
$("#myalert1").hide();
}, 1000);
$("#myalert1").show();
//用戶列表添加該用戶
_$listGroup.append(`<a href="#" name="${data.username}" class="list-group-item"><svg class="icon" aria-hidden="true" style="font-size: 2em"><use xlink:href="#${data.touXiangUrl}"></use></svg>${data.username}</a>`);
} else {
//下線顯示警告框,用戶列表刪除一個
$('#myalert2 span').html(`<span>您的好友<strong>${data.username}</strong>下線了!</span>`);
setTimeout(function() {
$("#myalert2").hide();
}, 1000);
$("#myalert2").show();
//找到該用戶並刪除
_$listGroup.find($(`a[name='${data.username}']`)).remove();
}
};
/* 前端事件 */
/*登錄事件*/
_$loginButton.on('click',function (event) { //監聽按鈕的點擊事件,如果點擊,就說明用戶要登錄,就執行setUsername函數
setUsername();
});
_$inputname.on('keyup',function (event) { //監聽輸入框的回車事件,這樣用戶回車也能登錄。
if(event.keyCode === 13) { //如果用戶輸入的是回車鍵,就執行setUsername函數
setUsername();
}
});
/*聊天事件*/
_$chatinput.on('keyup',function (event) {
if(event.keyCode === 13) {
sendMessage();
_$chatinput.val('');
}
});
//點擊圖片按鈕觸發input
_$imgButton.on('click',function (event) {
_$imgInput.click();
return false;
});
_$imgInput.change(function (event) {
sendImg(event);
//重置一下form元素,否則如果發同一張圖片不會觸發change事件
$("#resetform")[0].reset();
});
/* socket.io部分邏輯 */
socket.on('loginSuccess',(data)=>{
/**
* 如果服務器返回的用戶名和剛剛發送的相同的話,就登錄
* 否則說明有地方出問題了,拒絕登錄
*/
if(data.username === _username) {
beginChat(data);
}else {
comAndLeave(1,data);
}
});
socket.on('receiveMessage',(data)=>{
/**
* 監聽到事件發生,就顯示信息
*/
showMessage(data);
});
socket.on('usernameErr',(data)=>{
/**
* 我們給外部div添加 .has-error
* 拷貝label插入
* 控制顯示的時間爲1.5s
*/
$(".login .form-inline .form-group").addClass("has-error");
$('<label class="control-label" for="inputError1">用戶名重複</label>').insertAfter($('#name'));
setTimeout(function() {
$('.login .form-inline .form-group').removeClass('has-error');
$("#name + label").remove();
}, 1500)
});
socket.on('receiveImg',(data)=>{
/**
* 監聽到receiveImg發生,就顯示圖片
*/
showImg(data);
});
socket.on('oneLeave',(data)=>{
comAndLeave(-1,data);
});
});
實現單聊
首先,我們給本人的a標籤設置不可點擊屬性:在class中寫disable
_$listGroup.append(`<a href="#" name="${_username}" class="list-group-item disabled"><svg class="icon" aria-hidden="true" style="font-size: 2em"><use xlink:href="#icon-yonghu"></use></svg> ${_username}</a>`);
然後我們找兩個模態框:
一個用於發送單聊消息,一個用於顯示。修改一下
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>聊天室</title>
<script src="JavaScripts/jquery-3.2.1.js"></script>
<link rel="stylesheet" href="stylesheets/bootstrap.css">
<script src="JavaScripts/bootstrap.js"></script>
<script src="JavaScripts/main.js"></script>
<script src="/socket.io/socket.io.js"></script>
<script src="http://at.alicdn.com/t/font_o86wdrgtu766r.js"></script>
<style>
.icon {
width: 1em; height: 1em;
vertical-align: -0.15em;
fill: currentColor;
overflow: hidden;
}
</style>
<style>
html,body {
background: #ccc;
padding: 20px 0 20px 0;
height: 100%;
}
h1 {
text-align: center;
}
.login {
text-align: center;
}
#chatbox {
height: 100%;
}
#inputgrop {
display: block;
width: 100%;
position: absolute;
min-height: 40px;
bottom: 0;
}
#content {
height: auto;
}
.alert {
z-index: 100;
position: absolute;
top: 0;
width: 100%;
}
.chatwindow {
height: 100%;
}
</style>
</head>
<body>
<div id="loginbox">
<h1>登錄</h1>
<hr>
<div class="login">
<div class="form-inline">
<div class="form-group">
<label>用戶名</label>
<input type="text" class="form-control" id="name" placeholder="請輸入用戶名">
</div>
<br><br>
<button type="button" class="btn btn-info btn-lg" id="loginbutton">登錄</button>
</div>
</div>
</div>
<div style="display: none;" id="chatbox">
<div id="usergroup" class="col-md-2">
<span class="label label-primary" style="font-size: 2em;">在線成員</span>
<div class="list-group"></div>
</div>
<div class="col-md-9 chatwindow col-md-offset-1">
<div class="alert alert-info alert-dismissible fade in" role="alert" id="myalert">
<button type="button" class="close" data-dismiss="alert" aria-label="Close"><span aria-hidden="true">×</span></button>
</div>
<div class="alert alert-info alert-dismissible fade in" role="alert" id="myalert1">
<button type="button" class="close" data-dismiss="alert" aria-label="Close"><span aria-hidden="true">×</span></button>
<span></span>
</div>
<div class="alert alert-danger alert-dismissible fade in" role="alert" id="myalert2">
<button type="button" class="close" data-dismiss="alert" aria-label="Close"><span aria-hidden="true">×</span></button>
<span></span>
</div>
<div id="content"></div>
<div id="inputgrop">
<div class="col-md-10">
<input type="text" placeholder="saying somgthing" class="form-control chatinput" id="chatinput">
</div>
<form action="" style="display: none;" id="resetform"><input type="file" style="display: none" id="imginput"></form>
<div class="col-md-2">
<button type="button" class="btn btn-primary" id="imgbutton">發送圖片</button>
</div>
</div>
</div>
<!-- 發送模態框(Modal) -->
<div class="modal fade" id="myModal" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">
×
</button>
<h4 class="modal-title" id="myModalLabel">
模態框(Modal)標題
</h4>
</div>
<div class="modal-body">
<input type="text" placeholder="say something" class="form-control chatinput" id="inputtoone">
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal" id="closesendtoo">關閉
</button>
<button type="button" class="btn btn-primary" id="sendtoo">
發送
</button>
</div>
</div><!-- /.modal-content -->
</div><!-- /.modal -->
</div>
<!-- 收到模態框(Modal) -->
<div class="modal fade" id="myModal1" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">
×
</button>
<h4 class="modal-title" id="myModalLabel1">
模態框(Modal)標題
</h4>
</div>
<div class="modal-body shoudao">
</div>
<div class="modal-footer">
<button type="button" class="btn btn-primary" data-dismiss="modal" id="closesendtoo">
知道了
</button>
</div>
</div><!-- /.modal-content -->
</div><!-- /.modal -->
</div>
<button id="showmodal" data-toggle="modal" data-target="#myModal1" style="display: none;"></button>
</div>
</body>
</html>
我們的button是用來觸發模態框的,收到消息後需要觸發顯示
我們在列表的好友的a標籤中添加:
data-toggle="modal" data-target="#myModal"
這兩個屬性即可點擊觸發modal
然後監聽點擊事件,並換文字:
//監聽成員點擊事件
_$listGroup.on('click',function (event) {
initModal(event);
});
initModal = function (event) {
let _name = $(event.target).attr('name');
$("#myModalLabel").text(`發給${_name}`);
};
然後就點擊發送了,我們發送給服務器,
這裏我們設了一個全局變量 _to 裏面是收件人的用戶名
//監聽私聊的按鈕,觸發私聊事件
$("#sendtoo").on('click',function (event) {
/**
* 得到用戶輸入的消息,如果部位空,就發送,清空內容關閉模態框
*/
let _text = $("#inputtoone").val();
if (typeof _text !== 'undefined') {
socket.emit('sendToOne', {to: _to, text: _text, username: _username});
$("#inputtoone").val('');
$("#closesendtoo").click();
}
});
打開app.js,寫監聽:
我們是通過不同的socket來區別不同客戶端的,每一個連接的socket都是不同的,所以,我們需要將socket和用戶名匹配,我們使用數組:
//如果用戶名存在。將該用戶的信息存進數組中
if(socket.username){
users.push({
username: data.username,
message: [],
dataUrl: [],
touXiangUrl: data.touXiangUrl
});
//保存socket
_sockets[socket.username] = socket;
//然後觸發loginSuccess事件告訴瀏覽器登陸成功了,廣播形式觸發
data.userGroup = users; //將所有用戶數組傳過去
io.emit('loginSuccess',data); //將data原封不動的再發給該瀏覽器
}
socket.on('sendToOne',(data)=>{
//判斷該用戶是否存在,如果存在就觸發receiveToOne事件
for (let _user of users) {
if (_user.username === data.to) {
_sockets[data.to].emit('receiveToOne',data);
}
}
});
下面我們在客戶端得到該事件的data
socket.on('receiveToOne',(data)=>{
$("#myModalLabel1").text(`來自${data.username}`);
$(".shoudao").text(`${data.text}`);
$("#showmodal").click();
});
三行代碼就搞定了。
下面試一下吧,能過成功私聊了。
最後,我們修改一下聊天消息的樣式:
在html中添加:
<style>
.sender {
clear: both
}
.sender div:nth-of-type(1) {
float: left
}
.sender div:nth-of-type(2) {
background-color: #7fffd4;
float: left;
margin: 0 20px 10px 15px;
padding: 10px 10px 10px 0;
border-radius: 7px
}
.receiver div:first-child img,
.sender div:first-child img {
width: 50px;
height: 50px
}
.receiver {
clear: both
}
.receiver div:nth-child(1) {
float: right
}
.receiver div:nth-of-type(2) {
float: right;
background-color: gold;
margin: 0 10px 10px 20px;
padding: 10px 0 10px 10px;
border-radius: 7px
}
.left_triangle {
height: 0;
width: 0;
border-width: 8px;
border-style: solid;
border-color: transparent #7fffd4 transparent transparent;
position: relative;
left: -16px;
top: 3px
}
.right_triangle {
height: 0;
width: 0;
border-width: 8px;
border-style: solid;
border-color: transparent transparent transparent gold;
position: relative;
right: -16px;
top: 3px
}
</style>
然後修改顯示消息和圖片的代碼:
html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>聊天室</title>
<script src="JavaScripts/jquery-3.2.1.js"></script>
<link rel="stylesheet" href="stylesheets/bootstrap.css">
<script src="JavaScripts/bootstrap.js"></script>
<script src="JavaScripts/main.js"></script>
<script src="/socket.io/socket.io.js"></script>
<script src="http://at.alicdn.com/t/font_o86wdrgtu766r.js"></script>
<style>
.sender {
clear: both
}
.sender div:nth-of-type(1) {
float: left
}
.sender div:nth-of-type(2) {
background-color: #7fffd4;
float: left;
margin: 0 20px 10px 15px;
padding: 10px 10px 10px 0;
border-radius: 7px
}
.receiver div:first-child img,
.sender div:first-child img {
width: 50px;
height: 50px
}
.receiver {
clear: both
}
.receiver div:nth-child(1) {
float: right
}
.receiver div:nth-of-type(2) {
float: right;
background-color: gold;
margin: 0 10px 10px 20px;
padding: 10px 0 10px 10px;
border-radius: 7px
}
.left_triangle {
height: 0;
width: 0;
border-width: 8px;
border-style: solid;
border-color: transparent #7fffd4 transparent transparent;
position: relative;
left: -16px;
top: 3px
}
.right_triangle {
height: 0;
width: 0;
border-width: 8px;
border-style: solid;
border-color: transparent transparent transparent gold;
position: relative;
right: -16px;
top: 3px
}
</style>
<style>
.icon {
width: 1em; height: 1em;
vertical-align: -0.15em;
fill: currentColor;
overflow: hidden;
}
</style>
<style>
html,body {
background: #ccc;
padding: 20px 0 20px 0;
height: 100%;
}
h1 {
text-align: center;
}
.login {
text-align: center;
}
#chatbox {
height: 100%;
}
#inputgrop {
display: block;
width: 100%;
position: absolute;
min-height: 40px;
bottom: 0;
}
#content {
height: auto;
}
.alert {
z-index: 100;
position: absolute;
top: 0;
width: 100%;
}
.chatwindow {
height: 100%;
}
</style>
</head>
<body>
<div id="loginbox">
<h1>登錄</h1>
<hr>
<div class="login">
<div class="form-inline">
<div class="form-group">
<label>用戶名</label>
<input type="text" class="form-control" id="name" placeholder="請輸入用戶名">
</div>
<br><br>
<button type="button" class="btn btn-info btn-lg" id="loginbutton">登錄</button>
</div>
</div>
</div>
<div style="display: none;" id="chatbox">
<div id="usergroup" class="col-md-2">
<span class="label label-primary" style="font-size: 2em;">在線成員</span>
<div class="list-group"></div>
</div>
<div class="col-md-9 chatwindow col-md-offset-1">
<div class="alert alert-info alert-dismissible fade in" role="alert" id="myalert">
<button type="button" class="close" data-dismiss="alert" aria-label="Close"><span aria-hidden="true">×</span></button>
</div>
<div class="alert alert-info alert-dismissible fade in" role="alert" id="myalert1">
<button type="button" class="close" data-dismiss="alert" aria-label="Close"><span aria-hidden="true">×</span></button>
<span></span>
</div>
<div class="alert alert-danger alert-dismissible fade in" role="alert" id="myalert2">
<button type="button" class="close" data-dismiss="alert" aria-label="Close"><span aria-hidden="true">×</span></button>
<span></span>
</div>
<div id="content"></div>
<div id="inputgrop">
<div class="col-md-10">
<input type="text" placeholder="saying somgthing" class="form-control chatinput" id="chatinput">
</div>
<form action="" style="display: none;" id="resetform"><input type="file" style="display: none" id="imginput"></form>
<div class="col-md-2">
<button type="button" class="btn btn-primary" id="imgbutton">發送圖片</button>
</div>
</div>
</div>
<!-- 發送模態框(Modal) -->
<div class="modal fade" id="myModal" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">
×
</button>
<h4 class="modal-title" id="myModalLabel">
模態框(Modal)標題
</h4>
</div>
<div class="modal-body">
<input type="text" placeholder="say something" class="form-control chatinput" id="inputtoone">
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal" id="closesendtoo">關閉
</button>
<button type="button" class="btn btn-primary" id="sendtoo">
發送
</button>
</div>
</div><!-- /.modal-content -->
</div><!-- /.modal -->
</div>
<!-- 收到模態框(Modal) -->
<div class="modal fade" id="myModal1" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">
×
</button>
<h4 class="modal-title" id="myModalLabel1">
模態框(Modal)標題
</h4>
</div>
<div class="modal-body shoudao">
</div>
<div class="modal-footer">
<button type="button" class="btn btn-primary" data-dismiss="modal" id="closesendtoo">
知道了
</button>
</div>
</div><!-- /.modal-content -->
</div><!-- /.modal -->
</div>
<button id="showmodal" data-toggle="modal" data-target="#myModal1" style="display: none;"></button>
</div>
</body>
</html>
main.js:
/**
* Created by zhouxinyu on 2017/8/6.
*/
$(function(){
const url = 'http://127.0.0.1:3000';
let _username = null;
let _$inputname = $("#name");
let _$loginButton = $("#loginbutton");
let _$chatinput = $("#chatinput");
let _$inputGroup = $("#inputgrop");
let _$imgButton = $("#imgbutton");
let _$imgInput = $("#imginput");
let _$listGroup = $(".list-group");
let _touXiangUrl = null;
let _to = null;
let socket = io.connect(url);
//隨機設置頭像的地址
let touXiang = function (url) {
let _url = url || (Math.random()*8 | 0);
switch (_url) {
case 0 :
return "icon-river__easyiconnet1";
case 1 :
return "icon-river__easyiconnet";
case 2 :
return "icon-photo_camera__easyiconnet";
case 3 :
return "icon-planet_earth__easyiconnet";
case 4 :
return "icon-palace__easyiconnet";
case 5 :
return "icon-mountain__easyiconnet";
case 6 :
return "icon-parachute__easyiconnet";
case 7 :
return "icon-map__easyiconnet";
case 8 :
return "icon-mountains__easyiconnet";
case -1 :
return "icon-yonghu"
}
};
initModal = function (event) {
_to = $(event.target).attr('name');
$("#myModalLabel").text(`發給${_to}`);
};
//設置用戶名,當用戶登錄的時候觸發
let setUsername = function () {
_username = _$inputname.val().trim(); //得到輸入框中用戶輸入的用戶名
_touXiangUrl = touXiang();
//判斷用戶名是否存在
if(_username) {
socket.emit('login',{username: _username, touXiangUrl: _touXiangUrl}); //如果用戶名存在,就代表可以登錄了,我們就觸發登錄事件,就相當於告訴服務器我們要登錄了
}
};
let beginChat = function (data) {
/**
* 1.隱藏登錄框,取消它綁定的事件
* 2.顯示聊天界面
*/
$("#loginbox").hide('slow');
_$inputname.off('keyup');
_$loginButton.off('click');
/**
* 顯示聊天界面,並顯示一行文字,表示是誰的聊天界面
* 一個2s的彈框,顯示歡迎字樣
* 這裏我使用了ES6的語法``中可以使用${}在裏面寫的變量可以直接被瀏覽器渲染
*/
$(`<h2 style="text-align: center">${_username}的聊天室</h2>`).insertBefore($("#content"));
$(`<strong>歡迎你</strong><span>${_username}!</span>`).insertAfter($('#myalert button'));
$("#myalert1").hide();
$("#myalert2").hide();
$('#myalert').alert();
setTimeout(function () {
$('#myalert').alert('close');
},2000);
$("#chatbox").show('slow');
/**
* 用戶列表渲染
* 先添加自己,在從data中找到別人添加進去
*/
_$listGroup.append(`<a href="#" name="${_username}" class="list-group-item disabled"><svg class="icon" aria-hidden="true" style="font-size: 2em"><use xlink:href="#icon-yonghu"></use></svg> ${_username}</a>`);
//添加別人
for(let _user of data.userGroup) {
if (_user.username !== _username) {
_$listGroup.append(`<a href="#" name="${_user.username}" class="list-group-item" data-toggle="modal" data-target="#myModal"><svg class="icon" aria-hidden="true" style="font-size: 2em"><use xlink:href="#${_user.touXiangUrl}"></use></svg> ${_user.username}</a>`);
}
}
};
let sendMessage = function () {
/**
* 得到輸入框的聊天信息,如果不爲空,就觸發sendMessage
* 將信息和用戶名發送過去
*/
let _message = _$chatinput.val();
if(_message) {
socket.emit('sendMessage',{username: _username, message: _message, touXiangUrl: _touXiangUrl});
}
};
let setInputPosition = function () {
let height = $(window).height()>$('#content p:last').offset().top+$('#content p:last').height()*2?$(window).height():$('#content p:last').offset().top+$('#content p:last').height()*2;
_$inputGroup.css({'top': height});
};
let showMessage = function (data) {
//先判斷這個消息是不是自己發出的,然後再以不同的樣式顯示
if(data.username === _username) {
$('#content').append(`<div class="receiver">
<div>
<svg class="icon img-circle" aria-hidden="true" style="font-size: 2em;">
<use xlink:href="#icon-yonghu"></use>
</svg>
<strong style="font-size: 1.5em;">
${data.username}
</strong>
</div>
<div>
<div class="right_triangle"></div>
<span> ${data.message}</span>
</div>
</div>`);
} else {
$('#content').append(`<div class="sender">
<div>
<svg class="icon img-circle" aria-hidden="true" style="font-size: 2em;">
<use xlink:href="#${data.touXiangUrl}"></use>
</svg>
<strong style="font-size: 1.5em;">${data.username} </strong>
</div>
<div>
<div class="left_triangle"></div>
<span> ${data.message}</span>
</div>
</div>`);
}
setInputPosition();
};
let sendImg = function (event) {
/**
* 先判斷瀏覽器是否支持FileReader
*/
if (typeof FileReader === 'undefined') {
alert('您的瀏覽器不支持,該更新了');
//使用bootstrap的樣式禁用Button
_$imgButton.attr('disabled', 'disabled');
} else {
let file = event.target.files[0]; //先得到選中的文件
//判斷文件是否是圖片
if(!/image\/\w+/.test(file.type)){ //如果不是圖片
alert ("請選擇圖片");
return false;
}
/**
* 然後使用FileReader讀取文件
*/
let reader = new FileReader();
reader.readAsDataURL(file);
/**
* 讀取完自動觸發onload函數,我們觸發sendImg事件給服務器
*/
reader.onload = function (e) {
socket.emit('sendImg',{username: _username, dataUrl: this.result, touXiangUrl: _touXiangUrl});
}
}
};
let showImg = function (data) {
//先判斷這個消息是不是自己發出的,然後再以不同的樣式顯示
if(data.username === _username) {
$('#content').append(`<div class="receiver">
<div>
<svg class="icon img-circle" aria-hidden="true" style="font-size: 2em;">
<use xlink:href="#icon-yonghu"></use>
</svg>
<strong style="font-size: 1.5em;">
${data.username}
</strong>
</div>
<div>
<div class="right_triangle"></div>
<span><img class="img-thumbnail" src="${data.dataUrl}" style="max-height: 100px"/></span>
</div>
</div>`);
} else {
$('#content').append(`<div class="sender">
<div>
<svg class="icon img-circle" aria-hidden="true" style="font-size: 2em;">
<use xlink:href="#${data.touXiangUrl}"></use>
</svg>
<strong style="font-size: 1.5em;">${data.username} </strong>
</div>
<div>
<div class="left_triangle"></div>
<span><img class="img-thumbnail" src="${data.dataUrl}" style="max-height: 100px"/></span>
</div>
</div>`);
}
setInputPosition();
};
/**
*
* @param flag 爲1代表好友上線,-1代表好友下線
* @param data 存儲用戶信息
*/
let comAndLeave = function (flag,data) {
//上線顯示警告框,用戶列表添加一個
if(flag === 1) {
$('#myalert1 span').html(`<span>您的好友<strong>${data.username}</strong>上線了!</span>`);
setTimeout(function() {
$("#myalert1").hide();
}, 1000);
$("#myalert1").show();
//用戶列表添加該用戶
_$listGroup.append(`<a href="#" name="${data.username}" class="list-group-item" data-toggle="modal" data-target="#myModal"><svg class="icon" aria-hidden="true" style="font-size: 2em"><use xlink:href="#${data.touXiangUrl}"></use></svg>${data.username}</a>`);
} else {
//下線顯示警告框,用戶列表刪除一個
$('#myalert2 span').html(`<span>您的好友<strong>${data.username}</strong>下線了!</span>`);
setTimeout(function() {
$("#myalert2").hide();
}, 1000);
$("#myalert2").show();
//找到該用戶並刪除
_$listGroup.find($(`a[name='${data.username}']`)).remove();
}
};
/* 前端事件 */
/*登錄事件*/
_$loginButton.on('click',function (event) { //監聽按鈕的點擊事件,如果點擊,就說明用戶要登錄,就執行setUsername函數
setUsername();
});
_$inputname.on('keyup',function (event) { //監聽輸入框的回車事件,這樣用戶回車也能登錄。
if(event.keyCode === 13) { //如果用戶輸入的是回車鍵,就執行setUsername函數
setUsername();
}
});
/*聊天事件*/
_$chatinput.on('keyup',function (event) {
if(event.keyCode === 13) {
sendMessage();
_$chatinput.val('');
}
});
//點擊圖片按鈕觸發input
_$imgButton.on('click',function (event) {
_$imgInput.click();
return false;
});
_$imgInput.change(function (event) {
sendImg(event);
//重置一下form元素,否則如果發同一張圖片不會觸發change事件
$("#resetform")[0].reset();
});
//監聽成員點擊事件
_$listGroup.on('click',function (event) {
initModal(event);
});
//監聽私聊的按鈕,觸發私聊事件
$("#sendtoo").on('click',function (event) {
/**
* 得到用戶輸入的消息,如果部位空,就發送,清空內容關閉模態框
*/
let _text = $("#inputtoone").val();
if (typeof _text !== 'undefined') {
socket.emit('sendToOne', {to: _to, text: _text, username: _username});
$("#inputtoone").val('');
$("#closesendtoo").click();
}
});
/* socket.io部分邏輯 */
socket.on('loginSuccess',(data)=>{
/**
* 如果服務器返回的用戶名和剛剛發送的相同的話,就登錄
* 否則說明有地方出問題了,拒絕登錄
*/
if(data.username === _username) {
beginChat(data);
}else {
comAndLeave(1,data);
}
});
socket.on('receiveMessage',(data)=>{
/**
* 監聽到事件發生,就顯示信息
*/
showMessage(data);
});
socket.on('usernameErr',(data)=>{
/**
* 我們給外部div添加 .has-error
* 拷貝label插入
* 控制顯示的時間爲1.5s
*/
$(".login .form-inline .form-group").addClass("has-error");
$('<label class="control-label" for="inputError1">用戶名重複</label>').insertAfter($('#name'));
setTimeout(function() {
$('.login .form-inline .form-group').removeClass('has-error');
$("#name + label").remove();
}, 1500)
});
socket.on('receiveImg',(data)=>{
/**
* 監聽到receiveImg發生,就顯示圖片
*/
showImg(data);
});
socket.on('oneLeave',(data)=>{
comAndLeave(-1,data);
});
socket.on('receiveToOne',(data)=>{
$("#myModalLabel1").text(`來自${data.username}`);
$(".shoudao").text(`${data.text}`);
$("#showmodal").click();
});
});
app.js
/**
* Created by zhouxinyu on 2017/8/6.
*/
const express = require('express'); // const是ES6的語法,代表常量,準確來說就是指向不發生改變。如果不習慣就用var代替
const app = express(); // express官網就是這麼寫的就是用來創建一個express程序,賦值給app。如果不理解就當公式記住
const server = require('http').Server(app);
const path = require('path'); // 這是node的路徑處理模塊,可以格式化路徑
const io = require('socket.io')(server); //將socket的監聽加到app設置的模塊裏。
const users = []; //用來保存所有的用戶信息
let usersNum = 0;
const _sockets = []; //將socket和用戶名匹配
server.listen(3000,()=>{ // ()=>是箭頭函數,ES6語法,如果不習慣可以使用 function() 來代替 ()=>
console.log("server running at 127.0.0.1:3000"); // 代表監聽3000端口,然後執行回調函數在控制檯輸出。
});
/**
* app.get(): express中的一箇中間件,用於匹配get請求,所謂中間件就是在該輪http請求中依次執行的一系列函數。
* '/': 它匹配get請求的根路由 '/'也就是 127.0.0.1:3000/就匹配到他了
* (req,res): ES6語法的箭頭函數,你暫時可以理解爲function(req,res){}。
* req帶表瀏覽器的請求對象,res代表服務器的返回對象
*/
app.get('/',(req,res)=>{
res.redirect('/chat.html'); // express的重定向函數。如果瀏覽器請求了根路由'/',瀏覽器就給他重定向到 '127.0.0.1:3000/chat.html'路由中
});
/**
* __dirname表示當前文件所在的絕對路徑,所以我們使用path.join將app.js的絕對路徑和public加起來就得到了public的絕對路徑。
* 用path.join是爲了避免出現 ././public 這種奇怪的路徑
* express.static就幫我們託管了public文件夾中的靜態資源。
* 只要有 127.0.0.1:3000/XXX 的路徑都會去public文件夾下找XXX文件然後發送給瀏覽器。
*/
app.use('/',express.static(path.join(__dirname,'./public'))); //一句話就搞定。
/*socket*/
io.on('connection',(socket)=>{ //監聽客戶端的連接事件
/**
* 所有有關socket事件的邏輯都在這裏寫
*/
usersNum ++;
console.log(`當前有${usersNum}個用戶連接上服務器了`);
socket.on('login',(data)=>{
/**
* 先保存在socket中
* 循環數組判斷用戶名是否重複,如果重複,則觸發usernameErr事件
* 將用戶名刪除,之後的事件要判斷用戶名是否存在
*/
socket.username = data.username;
for (let user of users) {
if(user.username === data.username){
socket.emit('usernameErr',{err: '用戶名重複'});
socket.username = null;
break;
}
}
//如果用戶名存在。將該用戶的信息存進數組中
if(socket.username){
users.push({
username: data.username,
message: [],
dataUrl: [],
touXiangUrl: data.touXiangUrl
});
//保存socket
_sockets[socket.username] = socket;
//然後觸發loginSuccess事件告訴瀏覽器登陸成功了,廣播形式觸發
data.userGroup = users; //將所有用戶數組傳過去
io.emit('loginSuccess',data); //將data原封不動的再發給該瀏覽器
}
});
/**
* 監聽sendMessage,我們得到客戶端傳過來的data裏的message,並存起來。
* 我使用了ES6的for-of循環,和ES5 的for-in類似。
* for-in是得到每一個key,for-of 是得到每一個value
*/
socket.on('sendMessage',(data)=>{
for(let _user of users) {
if(_user.username === data.username) {
_user.message.push(data.message);
//信息存儲之後觸發receiveMessage將信息發給所有瀏覽器
io.emit('receiveMessage',data);
break;
}
}
});
/**
* 仿照sendMessage監聽sendImg事件
*/
socket.on("sendImg",(data)=>{
for(let _user of users) {
if(_user.username === data.username) {
_user.dataUrl.push(data.dataUrl);
//存儲後將圖片廣播給所有瀏覽器
io.emit("receiveImg",data);
break;
}
}
});
socket.on('sendToOne',(data)=>{
//判斷該用戶是否存在,如果存在就觸發receiveToOne事件
for (let _user of users) {
if (_user.username === data.to) {
_sockets[data.to].emit('receiveToOne',data);
}
}
});
//斷開連接後做的事情
socket.on('disconnect',()=>{ //注意,該事件不需要自定義觸發器,系統會自動調用
usersNum --;
console.log(`當前有${usersNum}個用戶連接上服務器了`);
//觸發用戶離開的監聽
socket.broadcast.emit("oneLeave",{username: socket.username});
//刪除用戶
users.forEach(function (user,index) {
if(user.username === socket.username) {
users.splice(index,1); //找到該用戶,刪除
}
})
})
});
到這裏,第三章的內容就結束了,該系列教程的項目源碼已經上傳至github https://github.com/neuqzxy/chat 歡迎瀏覽拷貝,如果覺得不錯,給個星星吧