1. 现代化UI设计:采用类似微信或 Slack 的风格,干净、简洁。
  2. 响应式布局:在桌面和移动设备上都能良好显示。
  3. 核心功能
    • 联系人列表
    • 聊天窗口
    • 消息发送与接收
    • 消息时间戳
    • 模拟在线状态
  4. 纯前端实现:所有交互都通过 HTML, CSS 和原生 JavaScript 实现,无需后端服务器,数据存储在 localStorage 中,刷新页面后消息不会丢失。

最终效果预览

桌面端:

在线聊天静态网页模板html demo
(图片来源网络,侵删)

移动端:


代码实现

您可以直接将以下所有代码复制到一个 .html 文件中,然后用浏览器打开即可看到效果。

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">在线聊天室</title>
    <style>
        /* --- 全局样式和变量 --- */
        :root {
            --primary-color: #007bff;
            --secondary-color: #6c757d;
            --background-color: #f8f9fa;
            --sidebar-bg: #343a40;
            --chat-bg: #ffffff;
            --text-color: #212529;
            --border-color: #dee2e6;
            --online-color: #28a745;
            --message-sent-bg: #dcf8c6;
            --message-received-bg: #f1f0f0;
        }
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
        }
        body {
            font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
            background-color: var(--background-color);
            color: var(--text-color);
            height: 100vh;
            overflow: hidden;
        }
        /* --- 布局容器 --- */
        .app-container {
            display: flex;
            height: 100vh;
        }
        /* --- 侧边栏 (联系人列表) --- */
        .sidebar {
            width: 280px;
            background-color: var(--sidebar-bg);
            display: flex;
            flex-direction: column;
            border-right: 1px solid var(--border-color);
        }
        .sidebar-header {
            padding: 20px;
            border-bottom: 1px solid rgba(255, 255, 255, 0.1);
        }
        .sidebar-header h2 {
            color: #ffffff;
            font-size: 1.5rem;
            font-weight: 600;
        }
        .search-bar {
            margin-top: 15px;
        }
        .search-bar input {
            width: 100%;
            padding: 10px 15px;
            border-radius: 20px;
            border: none;
            background-color: rgba(255, 255, 255, 0.1);
            color: #ffffff;
            font-size: 14px;
        }
        .search-bar input::placeholder {
            color: rgba(255, 255, 255, 0.6);
        }
        .contacts-list {
            flex: 1;
            overflow-y: auto;
            padding: 10px 0;
        }
        .contact-item {
            display: flex;
            align-items: center;
            padding: 15px 20px;
            cursor: pointer;
            transition: background-color 0.2s;
            border-bottom: 1px solid rgba(255, 255, 255, 0.05);
        }
        .contact-item:hover {
            background-color: rgba(255, 255, 255, 0.05);
        }
        .contact-item.active {
            background-color: rgba(255, 255, 255, 0.1);
        }
        .contact-avatar {
            width: 50px;
            height: 50px;
            border-radius: 50%;
            background-color: var(--primary-color);
            display: flex;
            align-items: center;
            justify-content: center;
            color: white;
            font-weight: bold;
            margin-right: 15px;
        }
        .contact-info {
            flex: 1;
        }
        .contact-name {
            color: #ffffff;
            font-weight: 500;
            margin-bottom: 3px;
        }
        .contact-status {
            color: rgba(255, 255, 255, 0.6);
            font-size: 0.85rem;
        }
        .online-indicator {
            width: 10px;
            height: 10px;
            background-color: var(--online-color);
            border-radius: 50%;
            border: 2px solid var(--sidebar-bg);
        }
        /* --- 主聊天区域 --- */
        .main-chat {
            flex: 1;
            display: flex;
            flex-direction: column;
            background-color: var(--chat-bg);
        }
        .chat-header {
            padding: 15px 20px;
            border-bottom: 1px solid var(--border-color);
            display: flex;
            align-items: center;
        }
        .chat-header .contact-avatar {
            width: 40px;
            height: 40px;
            font-size: 1.2rem;
            margin-right: 15px;
        }
        .chat-header .contact-info h3 {
            font-size: 1.1rem;
            margin-bottom: 2px;
        }
        .chat-header .contact-status {
            font-size: 0.8rem;
        }
        .messages-container {
            flex: 1;
            overflow-y: auto;
            padding: 20px;
            background: url('data:image/svg+xml,<svg width="20" height="20" xmlns="http://www.w3.org/2000/svg"><g fill="%23f0f0f0" fill-opacity="0.4"><path d="M0 0h10v10H0zM10 10h10v10H10z"/></g></svg>');
        }
        .message {
            display: flex;
            margin-bottom: 15px;
            animation: fadeIn 0.3s ease-in;
        }
        .message.sent {
            justify-content: flex-end;
        }
        .message-content {
            max-width: 70%;
            padding: 12px 15px;
            border-radius: 18px;
            position: relative;
            word-wrap: break-word;
        }
        .message.received .message-content {
            background-color: var(--message-received-bg);
            border-bottom-left-radius: 4px;
        }
        .message.sent .message-content {
            background-color: var(--message-sent-bg);
            border-bottom-right-radius: 4px;
        }
        .message-time {
            font-size: 0.75rem;
            color: var(--secondary-color);
            margin-top: 5px;
            text-align: right;
        }
        .message.received .message-time {
            text-align: left;
        }
        /* --- 输入区域 --- */
        .input-area {
            padding: 15px 20px;
            border-top: 1px solid var(--border-color);
            display: flex;
            align-items: center;
        }
        .input-area input {
            flex: 1;
            padding: 12px 20px;
            border: 1px solid var(--border-color);
            border-radius: 25px;
            outline: none;
            font-size: 1rem;
        }
        .input-area input:focus {
            border-color: var(--primary-color);
        }
        .send-button {
            margin-left: 15px;
            width: 45px;
            height: 45px;
            border-radius: 50%;
            background-color: var(--primary-color);
            border: none;
            color: white;
            cursor: pointer;
            display: flex;
            align-items: center;
            justify-content: center;
            transition: background-color 0.2s;
        }
        .send-button:hover {
            background-color: #0056b3;
        }
        .send-button svg {
            width: 20px;
            height: 20px;
        }
        /* --- 响应式设计 --- */
        .app-container.mobile .sidebar {
            position: absolute;
            left: -280px;
            height: 100%;
            z-index: 1000;
            transition: left 0.3s ease;
        }
        .app-container.mobile .sidebar.open {
            left: 0;
        }
        .app-container.mobile .main-chat {
            width: 100%;
        }
        .mobile-menu-button {
            display: none;
            background: none;
            border: none;
            color: var(--text-color);
            font-size: 1.5rem;
            cursor: pointer;
        }
        @media (max-width: 768px) {
            .mobile-menu-button {
                display: block;
            }
            .sidebar {
                width: 100%;
                max-width: 280px;
            }
        }
        /* --- 动画 --- */
        @keyframes fadeIn {
            from { opacity: 0; transform: translateY(10px); }
            to { opacity: 1; transform: translateY(0); }
        }
    </style>
</head>
<body>
    <div class="app-container" id="appContainer">
        <!-- 侧边栏 -->
        <aside class="sidebar" id="sidebar">
            <div class="sidebar-header">
                <h2>聊天室</h2>
                <div class="search-bar">
                    <input type="text" placeholder="搜索联系人...">
                </div>
            </div>
            <div class="contacts-list" id="contactsList">
                <!-- 联系人列表将通过 JavaScript 动态生成 -->
            </div>
        </aside>
        <!-- 主聊天区 -->
        <main class="main-chat">
            <div class="chat-header">
                <button class="mobile-menu-button" id="mobileMenuButton">☰</button>
                <div class="contact-avatar" id="currentChatAvatar">A</div>
                <div class="contact-info">
                    <h3 id="currentChatName">选择一个联系人</h3>
                    <p class="contact-status" id="currentChatStatus">点击左侧联系人开始聊天</p>
                </div>
            </div>
            <div class="messages-container" id="messagesContainer">
                <!-- 消息将通过 JavaScript 动态生成 -->
            </div>
            <div class="input-area">
                <input type="text" id="messageInput" placeholder="输入消息..." disabled>
                <button class="send-button" id="sendButton" disabled>
                    <svg fill="currentColor" viewBox="0 0 24 24">
                        <path d="M2.01 21L23 12 2.01 3 2 10l15 2-15 2z"/>
                    </svg>
                </button>
            </div>
        </main>
    </div>
    <script>
        // --- 数据模型 ---
        const contacts = [
            { id: 1, name: '张三', avatar: 'Z', lastMessage: '好的,明天见!', time: '10:30', online: true },
            { id: 2, name: '李四', avatar: 'L', lastMessage: '文件已发送', time: '昨天', online: false },
            { id: 3, name: '王五', avatar: 'W', lastMessage: '哈哈哈,太搞笑了', time: '周三', online: true },
            { id: 4, name: '产品经理', avatar: 'P', lastMessage: '请查看最新的需求文档', time: '周二', online: true },
            { id: 5, name: '设计师小美', avatar: 'M', lastMessage: 'UI稿已经更新了', time: '周一', online: false },
        ];
        // 当前选中的聊天对象
        let currentChatId = null;
        // 从 localStorage 加载消息,如果没有则使用默认消息
        let allMessages = JSON.parse(localStorage.getItem('chatMessages')) || {
            1: [
                { id: 1, text: '嗨,最近怎么样?', sent: false, time: '10:00' },
                { id: 2, text: '挺好的,你呢?', sent: true, time: '10:05' },
                { id: 3, text: '我也不错,明天有空一起吃饭吗?', sent: false, time: '10:10' },
                { id: 4, text: '好的,明天见!', sent: true, time: '10:30' },
            ],
            2: [
                { id: 1, text: '项目报告发你邮箱了', sent: true, time: '昨天 15:00' },
                { id: 2, text: '收到,我看看', sent: false, time: '昨天 15:05' },
                { id: 3, text: '文件已发送', sent: false, time: '昨天 15:10' },
            ]
        };
        // --- DOM 元素 ---
        const contactsListEl = document.getElementById('contactsList');
        const messagesContainerEl = document.getElementById('messagesContainer');
        const messageInputEl = document.getElementById('messageInput');
        const sendButtonEl = document.getElementById('sendButton');
        const currentChatNameEl = document.getElementById('currentChatName');
        const currentChatAvatarEl = document.getElementById('currentChatAvatar');
        const currentChatStatusEl = document.getElementById('currentChatStatus');
        const appContainerEl = document.getElementById('appContainer');
        const mobileMenuButtonEl = document.getElementById('mobileMenuButton');
        const sidebarEl = document.getElementById('sidebar');
        // --- 渲染函数 ---
        // 渲染联系人列表
        function renderContacts() {
            contactsListEl.innerHTML = '';
            contacts.forEach(contact => {
                const contactEl = document.createElement('div');
                contactEl.className = `contact-item ${currentChatId === contact.id ? 'active' : ''}`;
                contactEl.dataset.contactId = contact.id;
                contactEl.innerHTML = `
                    <div class="contact-avatar">${contact.avatar}</div>
                    <div class="contact-info">
                        <div class="contact-name">${contact.name}</div>
                        <div class="contact-status">${contact.lastMessage}</div>
                    </div>
                    ${contact.online ? '<div class="online-indicator"></div>' : ''}
                `;
                contactEl.addEventListener('click', () => selectContact(contact.id));
                contactsListEl.appendChild(contactEl);
            });
        }
        // 渲染消息
        function renderMessages() {
            if (!currentChatId) {
                messagesContainerEl.innerHTML = '<p style="text-align: center; color: var(--secondary-color); margin-top: 50px;">请从左侧选择一个联系人开始聊天</p>';
                return;
            }
            const messages = allMessages[currentChatId] || [];
            messagesContainerEl.innerHTML = '';
            messages.forEach(message => {
                const messageEl = document.createElement('div');
                messageEl.className = `message ${message.sent ? 'sent' : 'received'}`;
                messageEl.innerHTML = `
                    <div class="message-content">
                        ${message.text}
                        <div class="message-time">${message.time}</div>
                    </div>
                `;
                messagesContainerEl.appendChild(messageEl);
            });
            // 滚动到底部
            messagesContainerEl.scrollTop = messagesContainerEl.scrollHeight;
        }
        // --- 事件处理函数 ---
        // 选择联系人
        function selectContact(contactId) {
            currentChatId = contactId;
            const contact = contacts.find(c => c.id === contactId);
            // 更新聊天头部信息
            currentChatNameEl.textContent = contact.name;
            currentChatAvatarEl.textContent = contact.avatar;
            currentChatStatusEl.textContent = contact.online ? '在线' : '离线';
            // 启用输入框和发送按钮
            messageInputEl.disabled = false;
            sendButtonEl.disabled = false;
            // 更新联系人列表的激活状态
            document.querySelectorAll('.contact-item').forEach(item => {
                item.classList.toggle('active', item.dataset.contactId == contactId);
            });
            // 在移动端关闭侧边栏
            if (window.innerWidth <= 768) {
                sidebarEl.classList.remove('open');
            }
            // 重新渲染消息
            renderMessages();
        }
        // 发送消息
        function sendMessage() {
            const text = messageInputEl.value.trim();
            if (!text || !currentChatId) return;
            const now = new Date();
            const timeString = `${now.getHours().toString().padStart(2, '0')}:${now.getMinutes().toString().padStart(2, '0')}`;
            const newMessage = {
                id: Date.now(), // 使用时间戳作为唯一ID
                text: text,
                sent: true,
                time: timeString
            };
            // 添加到消息数组
            if (!allMessages[currentChatId]) {
                allMessages[currentChatId] = [];
            }
            allMessages[currentChatId].push(newMessage);
            // 保存到 localStorage
            localStorage.setItem('chatMessages', JSON.stringify(allMessages));
            // 更新联系人的最后一条消息
            const contact = contacts.find(c => c.id === currentChatId);
            if (contact) {
                contact.lastMessage = text;
                contact.time = '刚刚';
                renderContacts();
            }
            // 清空输入框并重新渲染消息
            messageInputEl.value = '';
            renderMessages();
            // 模拟对方回复
            setTimeout(() => {
                simulateReply();
            }, 1000 + Math.random() * 2000); // 1-3秒后回复
        }
        // 模拟对方回复
        function simulateReply() {
            if (!currentChatId) return;
            const replies = [
                '收到你的消息了!',
                '好的,明白了。',
                '这个想法不错!',
                '让我想想...',
                '哈哈,有道理。',
                '没问题!'
            ];
            const now = new Date();
            const timeString = `${now.getHours().toString().padStart(2, '0')}:${now.getMinutes().toString().padStart(2, '0')}`;
            const replyMessage = {
                id: Date.now(),
                text: replies[Math.floor(Math.random() * replies.length)],
                sent: false,
                time: timeString
            };
            allMessages[currentChatId].push(replyMessage);
            localStorage.setItem('chatMessages', JSON.stringify(allMessages));
            const contact = contacts.find(c => c.id === currentChatId);
            if (contact) {
                contact.lastMessage = replyMessage.text;
                contact.time = '刚刚';
                renderContacts();
            }
            renderMessages();
        }
        // --- 事件监听器 ---
        sendButtonEl.addEventListener('click', sendMessage);
        messageInputEl.addEventListener('keydown', (e) => {
            if (e.key === 'Enter') {
                sendMessage();
            }
        });
        // 移动端菜单按钮
        mobileMenuButtonEl.addEventListener('click', () => {
            sidebarEl.classList.toggle('open');
        });
        // 点击侧边栏外部区域关闭菜单 (移动端)
        document.addEventListener('click', (e) => {
            if (window.innerWidth <= 768 && 
                !sidebarEl.contains(e.target) && 
                !mobileMenuButtonEl.contains(e.target) &&
                sidebarEl.classList.contains('open')) {
                sidebarEl.classList.remove('open');
            }
        });
        // --- 初始化 ---
        renderContacts();
        renderMessages();
    </script>
</body>
</html>

如何使用和自定义

  1. 直接使用:将全部代码复制并保存为 chat.html 文件,然后用浏览器打开即可。
  2. 修改联系人:在 JavaScript 代码顶部的 contacts 数组中修改、添加或删除联系人信息。
  3. 修改样式:所有颜色、尺寸、间距等样式都在 <style> 标签的 root 部分或具体类中定义,您可以根据需要进行修改。
  4. 扩展功能
    • 图片/文件发送:可以增加一个附件按钮,通过 input type="file" 来获取文件,然后将其显示在聊天窗口中。
    • 更多表情:可以集成一个表情选择器插件。
    • 语音消息:使用 MediaRecorder API 录制并发送语音片段。
    • WebSocket 实时通信:如果需要真正的实时聊天,可以将此模板与后端(如 Node.js + Socket.io)结合,用 WebSocket 替代 localStorage 和模拟回复。

这个 Demo 为您提供了一个坚实的基础,您可以在此基础上进行二次开发,打造出功能更丰富的聊天应用。

在线聊天静态网页模板html demo
(图片来源网络,侵删)