前端部分用到的知识:websocket,h5,contenteditable属性
服务端部分:node, websocket
部分效果:
功能细节需要注意的地方
前端部分:
(1)输入框要可以输入表情图片( 不能用textarea,要用contenteditable='true'来实现)
(2)消息数量的显示限制,比如我最多只显示最新的30条消息 (通过对dom节点的长度判断和移除实现)
(3) 最新消息要始终显示在底部(通过scrollTop来实现)
(4)对信息分类进行区分,是用户进入,离开,普通消息,还是送花进行划分
服务端部分:
websocket相关知识的运用
代码:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <link rel="stylesheet" type="text/css" href="css/style.css" /> <title>聊天室</title> </head> <body> <div class="container"> <header>不充钱你觉得你会变得更强吗!!!</header> <div class="cont"> <video src="http://clips.vorwaerts-gmbh.de/big_buck_bunny.mp4" controls="controls"></video> </div> <div class="right"> <div class="right_top"> <div class="item ac_border">讨论</div> <div class="item" id="person">成员</div> </div> <div class="r_item"> <div class="right_cont"> <ul id="messageWrap"></ul> </div> <div class="right_bot"> <div class="r_b_t clearfix"> <div class="emoji " title="选择标签"></div> <div class="flower" title="献花"></div> </div> <div class="inputMeg_f"> <!--inputMeg外添加div inputMeg_f 的原因是为了自定义滚动条的手势是箭头,如果不加,改成inputMeg设置滚动条样式,那么滚动条的熟悉是输入手势--> <div class="inputMeg" contenteditable="true" placeholder="请输入文字"></div> </div> <div class="send_btn">发送</div> <div id="emojiBox" class="clearfix"></div> </div> </div> <div class="r_item" style="display: none"> <ul class="personWrap"></ul> </div> </div> </div> </body> <script src="../jquery.js"></script> <script> $(".right_top .item").click(function () { $(this).siblings().removeClass('ac_border') $(this).addClass('ac_border') $('.r_item').css('display','none').eq($(this).index()).css('display','block') }) function checkValue() { $(".emoji").off('click').click(function (e) { $("#emojiBox").css('display', 'block') var ev = e || window.event; ev.stopPropagation(); }) $(".container").off('click').click(function () { $("#emojiBox").css('display', 'none') }) } checkValue(); //生成表情 var emojiHtml = ''; var emojiBox = document.getElementById('emojiBox'); for (var i = 0; i < 7; i++) { for (var j = 0; j < 15; j++) { var dom = document.createElement('div'); dom.className = 'emojiItem'; dom.style.backgroundPositionX = -24 * j + 'px'; dom.style.backgroundPositionY = -29 * i + 'px'; emojiBox.appendChild(dom) chooseEmoji(i, j, dom) } } function chooseEmoji(i, j, dom) { dom.onclick = function (e) { const src = 'img/icon' + (i * 15 + j) + '.gif'; var img = $('<img class="emojiImg" src=' + src + '>') $('.inputMeg').append(img) $("#emojiBox").css('display', 'none') var ev = e || window.event; ev.stopPropagation(); } } var userName=''; //当前登录的用户 //websocket var websocket = new WebSocket( 'ws://localhost:8001/'); //连接的地址,是ws协议,不是http协议(本地地址localhost:8001,要想手机也能访问到,改成本地ip192.168.0.107:8001) websocket.onopen = function () { //监听建立连接 $('.send_btn').off('click').click(function () { var text = $('.inputMeg').html() if (text != '' && text != '请输入文字') { websocket.send(JSON.stringify({data:text,type:'message'})) //发送消息 $('.inputMeg').html('') } }); $('.flower').off('click').click(function(){ //送花 var dom= '<span>"'+userName+'"</span>送给<br> "春哥" 一朵小花<i class="flowIcon"></i>' websocket.send(JSON.stringify({data:dom,type:'flower'})) //发送消息 }) } websocket.onmessage = function (e) { var res = JSON.parse(e.data); message(res) } function message(res) { var dom = document.createElement('li'); switch (res.type) { case 'enter': dom.innerHTML = res.data; dom.style.color = 'green'; userName=res.nickname; person(res); break; case 'leave': dom.innerHTML = res.data; dom.style.color = 'red'; person(res) break; case 'message': name.innerHTML = res.nickname + ': '; dom.innerHTML = "<span class='nickName'>" + res.nickname + ": </span> " + res.data + "" break; case 'flower': dom.className='flowerLi'; dom.innerHTML=res.data; break; default: break; } document.getElementById('messageWrap').appendChild(dom); limitLength(30) scrollBottom(); //成员显示 } function scrollBottom() { //显示最新消息在底部 var h1 = document.getElementsByClassName('right_cont')[0].offsetHeight; var h2 = document.getElementById('messageWrap').offsetHeight; if (h2 > h1) { $('.right_cont').scrollTop(h2 - h1); } } function limitLength(num) { //限制聊天室最多能显示几条消息 var li = $('#messageWrap li') if (li.length > num) { li.eq(0).remove(); } } function person(res){ //成员显示 var html='' for(var i=0;i<res.client.length;i++){ html+= '<li><span class="nickname">'+res.client[i]+'</span></li>' } $('.personWrap').html(html); $('#person').html('成员('+res.client.length+')') } </script> </html> server.js: var ws = require("nodejs-websocket") var port=8001; var clientCount=0; var nicknameArr=[]; var server = ws.createServer(function (conn) { clientCount++; conn.nickname='user'+clientCount; nicknameArr.push(conn.nickname) var mes={type:'enter',data:'欢迎 '+conn.nickname+' 进入聊天',nickname:conn.nickname,client:nicknameArr} broadcast(JSON.stringify(mes)) conn.on("text", function (str) { //监听客户端发送过来的消息 var zstr=JSON.parse(str) var mes={type:zstr.type,data:zstr.data,nickname:conn.nickname,client:nicknameArr} broadcast(JSON.stringify(mes)) }) conn.on("close", function (code, reason) { clientCount--; for(var i=nicknameArr.length-1;i>=0;i--){ //删除退出的用户 if(conn.nickname==nicknameArr[i]){ nicknameArr.splice(i,1) } } var mes={type:'leave',data:conn.nickname+' 离开聊天',nickname:conn.nickname,client:nicknameArr} broadcast(JSON.stringify(mes)) }); conn.on('error',function(err){ console.log('handle err') }) }).listen(port) function broadcast(str){ //获取客户端连接的人数并返回消息 server.connections.forEach(function(connection){ connection.sendText(str) }) }