本篇文章使用node+express+jquery写一个个性化聊天室,一块儿来get一下~(源码地址见文章末尾)javascript
下面将一一讲解如何实现css
node及npm环境、express、socket.iohtml
先用node搭建一个服务器,部署在localhost:3000端口,先尝试向浏览器发送一个“hello world”,新建server.js文件。html5
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是这样的java
接下来用express向浏览器返回一个html页面node
#安装express模块 npm install --save express 复制代码
将server.js的代码改一下:jquery
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小图标css3
<!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,会看到以下:git
在客户端和服务器之间传送消息须要用到socket.iogithub
#安装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(''); }); }); 复制代码
假若登陆成功,会看到以下页面:
该功能是为了实现上图所示的系统提示“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); }); 复制代码
客户端监听一个显示在线用户的事件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); } } 复制代码
用户发送消息时触发服务器端的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; } 复制代码
得益于html5的input新特性,能够经过type为color的input调用系统调色板
<!-- $('#color').val();为选中颜色,格式为#FFCCBB --> <input type='color' id='color'> 复制代码
客户端根据用户选择的颜色渲染内容样式,代码很容易看懂,这里就不赘述了。
发送表情其实很简单,将表情图片放在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 style="color: ${obj.color};">${content}</p> </div> </li> `); // 滚动条老是在最底部 $('#messages').scrollTop($('#messages')[0].scrollHeight); }); 复制代码
能够成功发送表情了。
首先是图片按钮样式,发送图片的按钮是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; } 复制代码
完美
// 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 style="padding: 0;">${obj.msg}</p> </div> </li> `); $('#messages').scrollTop($('#messages')[0].scrollHeight); return; } // 提取文字中的表情加以渲染 // 下面不变 }); 复制代码
如今咱们能够发送图片了
当用户点击抖动按钮时会emit服务端的抖动事件,服务端会广播该事件使得每一个客户端都会抖动窗口。
// chat-client.js // 用户发送抖动 $('.edit #shake').click(function() { socket.emit('shake'); }); // server.js // 发送窗口抖动 socket.on('shake', ()=> { socket.emit('shake', { name: '您' }); socket.broadcast.emit('shake', { name: socket.nickname }); }); 复制代码
实现窗口抖动用的css3动画
.edit .selectBox { position: absolute; bottom: 34px; left: 0px; } .shaking { animation: run 0.2s infinite; } @keyframes run { 0% { left: 0; } 25% { left: -7px; } 50% { left: 7px; } 100% { left: 0; } } 复制代码
圆满完成一个功能齐全的聊天室!
源码地址:windlany/happy-chat,本文断断续续写了两天,真是写文章比敲代码还累...其实写一个聊天室并不难,这算是node起步做品吧。有兴趣的能够fork下来根据本身需求改改,以为不错请给我一个star。
参考连接