Node.js + express + socket 实现在线实时多人聊天室

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/Cyan1614/article/details/73715932

项目目录结构:




前端部分:


登录页面Login部分:


login.html


<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>login</title>
    <link rel="stylesheet" href="css/login.css">
</head>

<body>
    <div class="login-box flex-box">
        <!--登录标题栏-->
        <h2 class="sign-title box-width">LOGIN</h2>
        <!--头像栏-->
        <div class="picture-carousel">
            <div class="arrow left-arrow">
                <div class="before-arrow"></div>
            </div>
            <img class="p1 img-setting" src="img/1.png" alt="1.png">
            <img class="p2 img-setting" src="img/2.png" alt="2.png">
            <img class="p3 img-setting" src="img/3.png" alt="3.png">
            <img class="p2 img-setting" src="img/4.png" alt="4.png">
            <img class="p1 img-setting" src="img/5.png" alt="5.png">
            <div class="arrow right-arrow">
                <div class="after-arrow"></div>
            </div>
        </div>
        <!--用户名栏-->
        <div class="name-box box-width">
            <input type="text" class="user-name box-width" placeholder="Please Type Your Name">
        </div>
        <!--确认栏-->
        <div class="button-box box-width">
            <input type="button" class="login-button box-width" value="Login The Chatroom">
        </div>
        <!--错误信息栏-->
        <div class="error-box box-width">
            <span class="error-message">Welcome to chatroom!</span>
        </div>
    </div>
</body>
<script src="js/login.js"></script>

</html>

login.css

* {
    padding: 0;
    margin: 0;
    font-family: "Microsoft Yahei";
}

html,
body {
    width: 100%;
    height: 100%;
    font-family: "Microsoft Yahei";
    display: flex;
    justify-content: center;
    align-items: center;
}

body {
    background: linear-gradient(-135deg, #51D15B, #42A855);
    background: -moz-linear-gradient(-135deg, #51D15B, #42A855);
    background: -webkit-linear-gradient(-135deg, #51D15B, #42A855);
    background: -o-linear-gradient(-135deg, #51D15B, #42A855);
}

.flex-box {
    display: flex;
    justify-content: center;
    align-items: center;
}

.box-width {
    width: 80%;
}


/*最外层*/

.login-box {
    width: 20%;
    min-width: 304px;
    max-width: 404px;
    height: 50%;
    min-height: 368px;
    max-height: 468px;
    flex-direction: column;
    box-shadow: 1px 1px 15px #7B8C99;
    background: #fff;
}


/*LOGIN标题*/

.sign-title {
    color: #42A855;
    border: 2px solid #42A855;
    border-top: transparent;
    border-left: transparent;
    border-right: transparent;
}


/*图片切换*/

.picture-carousel {
    position: relative;
    display: flex;
    margin: 10%;
}


/*图片切换箭头*/

.arrow {
    z-index: 3;
    position: absolute;
    font-size: 60px;
    height: 100%;
    width: 30%;
    display: flex;
    justify-content: center;
    align-items: center;
    color: #ffffff;
}

.arrow:hover {
    cursor: pointer;
}

.left-arrow {
    left: 0;
}

.before-arrow {
    width: 0px;
    height: 0px;
    border-width: 30px;
    border-style: solid;
    border-color: transparent #51D15B transparent transparent;
}

.right-arrow {
    right: 0;
}

.after-arrow{
     width: 0px;
    height: 0px;
    border-width: 30px;
    border-style: solid;
    border-color: transparent  transparent transparent #51D15B; 
}

.picture-carousel img {
    width: 80px;
    height: 80px;
    transition: all 0.2s linear;
    -moz-transition: all 0.2s ease-out;
    -webkit-transition: all 0.2s ease-out;
    -o-transition: all 0.2s ease-out;
}

.img-setting {
    margin: 0px -15px;
}

.p1 {
    transform: scale(0.6);
    z-index: 1;
}

.p1:hover {
    transform: scale(0.8);
}

.p2 {
    transform: scale(0.8);
    z-index: 2;
}

.p2:hover {
    transform: scale(1);
}

.p3 {
    transform: scale(1);
    z-index: 3;
}

.p3:hover {
    transform: scale(1.2);
}


/*用户名*/

.name-box {
    display: flex;
    justify-content: center;
    border: 1px solid #51D15B;
}

.name-box .user-name {
    width: 100%;
    text-align: center;
    padding: 10px;
    outline-color: #42A855;
    border: none;
    font-size: 16px;
}


/*登录按钮*/

.button-box {
    display: flex;
    justify-content: center;
    margin: 10px 0px 20px;
}

.button-box .login-button {
    width: 100%;
    padding: 10px 20px;
    outline: none;
    border: none;
    background: #42A855;
    color: white;
    font-size: 16px;
}


/*错误信息*/

.error-box {
    color: #42A855;
    border: 2px solid #42A855;
    border-top: transparent;
    border-left: transparent;
    border-right: transparent;
}

.error-box span {
    visibility: hidden;
    color: #d43f3a;
    font-size: 14px;
}


login.js

//用于存储图片顺序
var imgArray = ['1', '2', '3', '4', '5']; 

//获取箭头
var leftArrow = document.getElementsByClassName('left-arrow')[0];
var rightArrow = document.getElementsByClassName('right-arrow')[0];

//获取用户名
var userName = document.getElementsByClassName('user-name')[0];

//获取登录按钮
var loginButton = document.getElementsByClassName('login-button')[0];

// 获取错误信息栏
var errorMessage = document.getElementsByClassName('error-message')[0];

// 添加左箭头监听事件
leftArrow.addEventListener('click', function () {
    imgArray.unshift(imgArray[imgArray.length - 1]); //把最后的元素放在第一位
    imgArray.pop();
    carouselImg();

});

// 添加右箭头监听事件
rightArrow.addEventListener('click', function () {
    imgArray.push(imgArray[0]); //把第一个元素放在最后
    imgArray.shift();
    carouselImg();

});

// 切换图片
function carouselImg() {
    for (var count = 0; count < imgArray.length; count++) {
        document.getElementsByTagName('img')[count].src = 'img/' + imgArray[count] + '.png';
        document.getElementsByTagName('img')[count].alt = imgArray[count] + '.png';
    };
};

// 添加登录按钮监听事件
loginButton.addEventListener('click', function () {
    if (userName.value === '') {
        errorMessage.innerHTML = 'Please Type You Name';
        errorMessage.style.visibility = 'visible';
    } else if (userName.value.length > 8) {
        errorMessage.innerHTML = 'Your Name Cannot Over 8 Words';
        errorMessage.style.visibility = 'visible';
    } else {
        window.location.href =
            encodeURI('index.html?selectpicture=' + document.getElementsByClassName('p3')[0].alt +
                '&username=' + userName.value);
    }
});

// Enter按键绑定登录事件
document.onkeydown = function (event) {
    var e = event || window.event;
    if (e && e.keyCode == 13) {
        loginButton.click();
    }
};

效果图



聊天页面index部分:


index.html


<!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">
    <title>chat-room</title>
    <link rel="stylesheet" href="css/index.css">
</head>

<body>
    <div class="chat-box">
        <!--聊天框头部-->
        <div class="chat-header">
            <div class="button-box">
                <input type="button" class="log-out" value="LOGOUT">
            </div>
        </div>
        <!--聊天框主体-->
        <div class="chat-body">
            <!--聊天框左侧-->
            <div class="chat-body-left">
                <!--聊天框左侧聊天内容-->
                <div class="chat-content"></div>
                <!--聊天框左侧聊天输入框-->
                <div class="chat-edit">
                    <input type="text" class="edit-box" placeholder="Please Type You Message" maxlength="15"> 
                    <input type="button" class="edit-button" value="SEND">
                </div>
            </div>
            <!--聊天框右侧-->
            <div class="chat-body-right">
                <!--聊天框右侧统计人数-->
                <div class="online-count">Online:0</div>
                <!--聊天框右侧用户名-->
                <div class="user-name">user-name</div>
                <!--聊天框右侧头像-->
                <img class="user-img" />
            </div>
        </div>
    </div>
</body>

<script src="js/socket.io.js"></script>
<script src="js/index.js"></script>

</html>


index.css


* {
    margin: 0;
    padding: 0;
    font-family: "Mircrosoft Yahei";
    /*border: 1px solid black;*/
}

html,
body {
    width: 100%;
    height: 100%;
}


/*背景色*/

body {
    display: flex;
    justify-content: center;
    align-items: center;
    background: linear-gradient(-135deg, #51D15B, #42A855);
    background: -moz-linear-gradient(-135deg, #51D15B, #42A855);
    background: -webkit-linear-gradient(-135deg, #51D15B, #42A855);
    background: -o-linear-gradient(-135deg, #51D15B, #42A855);
}


/*最外层*/

.chat-box {
    width: 50%;
    max-width: 720px;
    min-width: 400px;
    height: 80%;
    min-height: 530px;
    max-height: 530px;
    display: flex;
    flex-direction: column;
    background: #fff;
    box-shadow: 1px 1px 15px #333333;
}


/*头部*/

.chat-header {
    margin: 5px;
    box-shadow: 1px 1px 15px #7B8C99;
}

.button-box {
    display: flex;
    justify-content: flex-end;
}

.log-out {
    height: 100%;
    font-size: 14px;
    font-weight: bold;
    padding: 5px 15px;
    color: #79C2EA;
    background: #fff;
    outline: none;
    border: none;
    border-radius: 15px;
    cursor: pointer;
}


/*主体*/

.chat-body {
    height: 90%;
    display: flex;
    flex-direction: row;
    justify-content: space-around;
    align-items: center;
    margin: 5px;
    padding: 5px;
}


/*主体左侧*/

.chat-body-left {
    height: 100%;
    width: 70%;
    display: flex;
    flex-direction: column;
    justify-content: space-around;
    margin: 5px;
}


/*左侧内容*/

.chat-content {
    margin-bottom: 5px;
    height: 100%;
    box-shadow: 1px 1px 15px #7B8C99;
    overflow: scroll;
}


/*聊天气泡*/

.my-message-box {
    display: flex;
    justify-content: flex-end;
    align-content: center;
    margin: 5px;
}

.other-message-box {
    display: flex;
    justify-content: flex-start;
    align-content: center;
    margin: 5px;
}

.message-content {
    display: flex;
    justify-content: center;
    align-content: center;
    background-color: #51D15B;
    padding: 5px 10px;
    border-radius: 15px;
    color: #fff;
}

.other-message-content {
    display: flex;
    justify-content: center;
    align-content: center;
    background-color: #79C2EA;
    padding: 5px 10px;
    border-radius: 15px;
    color: #fff;
}

.message-content span {
    padding: 20px 0px;
}

.other-message-content span {
    padding: 20px 0px;
}

.message-arrow {
    width: 0px;
    height: 0px;
    border-width: 8px;
    border-style: solid;
    border-color: transparent transparent transparent #51D15B;
    align-self: center;
}

.other-message-arrow {
    width: 0px;
    height: 0px;
    border-width: 8px;
    border-style: solid;
    border-color: transparent #79C2EA transparent transparent;
    align-self: center;
}

.user-information {
    display: flex;
    flex-direction: column;
    align-content: flex-end;
}

.other-user-information {
    display: flex;
    flex-direction: column;
    align-content: flex-end;
}

.user-chat-img {
    width: 50px;
    height: 50px;
}

.user-chat-name {
    color: #333333;
    font-size: 16px;
    text-align: center;
}

/*聊天输入框*/
.chat-edit {
    margin-top: 5px;
    display: flex;
    justify-content: space-between;
    align-items: center;
    box-shadow: 1px 1px 15px #7B8C99;
    overflow: hidden;
}
/*聊天输入框输入区域*/
.edit-box {
    width: 80%;
    height: 100%;
    margin: 5px;
    border: none;
    outline: none;
}

/*聊天框输入按钮*/
.edit-button {
    height: 100%;
    padding: 5px 15px;
    background: #fff;
    color: #79C2EA;
    outline: none;
    border: none;
    border-radius: 15px;
    cursor: pointer;
    font-size: 14px;
    font-weight: bold;
}


/*主体右侧*/

.chat-body-right {
    height: 100%;
    width: 30%;
    display: flex;
    flex-direction: column;
    justify-content: center;
    align-items: center;
    margin: 5px;
    box-shadow: 1px 1px 15px #7B8C99;
}


/*右侧内容*/

.user-name {
    margin: 15px;
    font-size: 18px;
    font-weight: bold;
    color: #79C2EA;
}

.user-img {
    width: 100px;
    height: 100px;
    margin: 5px;
}

.online-count {
    font-size: 18px;
    font-weight: bold;
    color: #79C2EA;
}

/*兼容小屏幕*/
@media screen and (max-width: 420px) {
    .chat-box {
        width: 50%;
        max-width: 720px;
        min-width: 300px;
        height: 80%;
        min-height: 530px;
        max-height: 530px;
    }
    .chat-body-left {
        height: 100%;
        width: 100%;
        display: flex;
        flex-direction: column;
        justify-content: space-around;
        margin: 5px;
    }
    .chat-body-right {
        display: none;
    }
}


index.js


// 获取url里面的内容
var url = decodeURI(location.href).split('?')[1].split('&');

// 获取聊天内容框
var chatContent = document.getElementsByClassName('chat-content')[0];

// 获取聊天输入框
var editBox = document.getElementsByClassName('edit-box')[0];

// 获取聊天输入框发送按钮
var editButton = document.getElementsByClassName('edit-button')[0];

// 获取用户名栏
var userName = document.getElementsByClassName('user-name')[0];

// 获取在线人数栏
var onlineCount = document.getElementsByClassName('online-count')[0];

// 把登录页面的名称放在右侧
userName.innerHTML = url[1].split('=')[1];
var userImg = document.getElementsByClassName('user-img')[0];

// 把登录页面的头像放在右侧
userImg.src = 'img/' + url[0].split('=')[1];
var logOut = document.getElementsByClassName('log-out')[0];

// 发送按钮绑定点击事件
editButton.addEventListener('click', sendMessage);

// 登出按钮绑定点击事件
logOut.addEventListener('click', closePage);

// 绑定Enter键和发送事件
document.onkeydown = function (event) {
    var e = event || window.event;
    if (e && e.keyCode === 13) {
        if (editBox.value !== '') {
            editButton.click();
        }
    }
};

// 关闭页面
function closePage() {
    var userAgent = navigator.userAgent;
    if (userAgent.indexOf("Firefox") != -1 || userAgent.indexOf("Chrome") != -1) {
        window.location.href = "about:blank";
    } else {
        window.opener = null;
        window.open("", "_self");
        window.close();
    }
}
// socket部分
var socket = io();

// 当接收到消息并且不是本机时生成聊天气泡
socket.on('message', function (information) {
    if (information.name !== userName.textContent) {
        createOtherMessage(information);
    }
});

// 当接收到有人连接进来
socket.on('connected', function (onlinecount) {
    console.log(onlinecount);
    onlineCount.innerHTML = 'Online:' + onlinecount;
});

// 当接收到有人断开后
socket.on('disconnected', function (onlinecount) {
    console.log(onlinecount);
    onlineCount.innerHTML = 'Online:' + onlinecount;
});

// 发送本机的消息
function sendMessage() {
    if (editBox.value != '') {
        var myInformation = {
            name: userName.textContent,
            chatContent: editBox.value,
            img: userImg.src
        };
        socket.emit('message', myInformation);
        createMyMessage();
        editBox.value = '';
    }

};

// 生成本机的聊天气泡
function createMyMessage() {
    var myMessageBox = document.createElement('div');
    myMessageBox.className = 'my-message-box';

    var messageContent = document.createElement('div');
    messageContent.className = 'message-content';
    var text = document.createElement('span');
    text.innerHTML = editBox.value;
    messageContent.appendChild(text);
    myMessageBox.appendChild(messageContent);

    var arrow = document.createElement('div')
    arrow.className = 'message-arrow';
    myMessageBox.appendChild(arrow);

    var userInformation = document.createElement('div');
    userInformation.className = 'user-information';
    var userChatImg = document.createElement('img');
    userChatImg.className = 'user-chat-img';
    userChatImg.src = userImg.src;
    var userChatName = document.createElement('div');
    userChatName.className = 'user-chat-name';
    userChatName.innerHTML = userName.textContent;
    userInformation.appendChild(userChatImg);
    userInformation.appendChild(userChatName);
    myMessageBox.appendChild(userInformation);

    chatContent.appendChild(myMessageBox);

    chatContent.scrollTop = chatContent.scrollHeight;
}

// 生成其他用户的聊天气泡
function createOtherMessage(information) {
    var otherMessageBox = document.createElement('div');
    otherMessageBox.className = 'other-message-box';

    var otherUserInformation = document.createElement('div');
    otherUserInformation.className = 'other-user-information';
    var userChatImg = document.createElement('img');
    userChatImg.className = 'user-chat-img';
    userChatImg.src = information.img;
    var userChatName = document.createElement('span');
    userChatName.className = 'user-chat-name';
    userChatName.innerHTML = information.name;
    otherUserInformation.appendChild(userChatImg);
    otherUserInformation.appendChild(userChatName);
    otherMessageBox.appendChild(otherUserInformation);

    var otherMessageArrow = document.createElement('div');
    otherMessageArrow.className = 'other-message-arrow';
    otherMessageBox.appendChild(otherMessageArrow);

    var otherMessageContent = document.createElement('div');
    otherMessageContent.className = 'other-message-content';
    var text = document.createElement('span');
    text.innerHTML = information.chatContent;
    otherMessageContent.appendChild(text);
    otherMessageBox.appendChild(otherMessageContent);

    chatContent.appendChild(otherMessageBox);

    chatContent.scrollTop = chatContent.scrollHeight;
}


效果图




后端部分:


server.js


// 引入必须模块
var express = require('express');
var app = express();
var http = require('http').Server(app);
var io = require('socket.io')(http);
var path = require('path');

// 在线人数统计
var onlineCount = 0;
app.use(express.static(__dirname));

// 路径映射
app.get('/login.html', function (request, response) {
    response.sendFile('login.html');
});

// 当有用户连接进来时
io.on('connection', function (socket) {
    console.log('a user connected');

    // 发送给客户端在线人数
    io.emit('connected', ++onlineCount);

    // 当有用户断开
    socket.on('disconnect', function () {
        console.log('user disconnected');

        // 发送给客户端断在线人数
        io.emit('disconnected', --onlineCount);
        console.log(onlineCount);
    });

    // 收到了客户端发来的消息
    socket.on('message', function (message) {
        // 给客户端发送消息
        io.emit('message', message);
    });

});

var server = http.listen(4000, function () {
    console.log('Sever is running');
});


总结:

后端部分使用了Node.js + express 实现
前端部分的布局使用了CSS3的flex来布局
聊天页面的交互使用socket进行了聊天消息的发送与接收的消息机制,避免了用Ajax有时会丢失聊天消息的情况
兼容性目前测试通过的有Chrome 58、Opera 45、Firefox 53、IE 11


待解决的问题:
用户名重复没有检测,后续打算添加用户注册功能,需要使用数据库

无法查看聊天记录,后续打算使用数据库进行聊天记录存储
代码规范还有待提高
界面设计有待提高

接下来还会更新本项目


github地址:https://github.com/CyanChan/chatroom

在线演示地址:http://119.29.19.27:4000/login.html


猜你喜欢

转载自blog.csdn.net/Cyan1614/article/details/73715932