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

(图片来源网络,侵删)
移动端:
代码实现
您可以直接将以下所有代码复制到一个 .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>
如何使用和自定义
- 直接使用:将全部代码复制并保存为
chat.html文件,然后用浏览器打开即可。 - 修改联系人:在 JavaScript 代码顶部的
contacts数组中修改、添加或删除联系人信息。 - 修改样式:所有颜色、尺寸、间距等样式都在
<style>标签的root部分或具体类中定义,您可以根据需要进行修改。 - 扩展功能:
- 图片/文件发送:可以增加一个附件按钮,通过
input type="file"来获取文件,然后将其显示在聊天窗口中。 - 更多表情:可以集成一个表情选择器插件。
- 语音消息:使用
MediaRecorderAPI 录制并发送语音片段。 - WebSocket 实时通信:如果需要真正的实时聊天,可以将此模板与后端(如 Node.js + Socket.io)结合,用 WebSocket 替代
localStorage和模拟回复。
- 图片/文件发送:可以增加一个附件按钮,通过
这个 Demo 为您提供了一个坚实的基础,您可以在此基础上进行二次开发,打造出功能更丰富的聊天应用。

(图片来源网络,侵删)
