单端聊天应用messages div抖动及滚动固定问题求助
单端聊天应用问题解决方案
问题描述
我正在开发一款单端聊天应用,目前遇到两个问题:
- 中间的messages div内容(包含滚动条)持续抖动;
- 无法将滚动条固定在顶部。
以下是完整代码:
主代码
<?php include 'db.php'; if(isset($_POST['send'])) { header("Location:chat.php"); $chat=$_POST['chat']; $sql="INSERT INTO chat_info(chat) values('$chat')"; $query=mysqli_query($conn,$sql); } ?> <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>chat application</title> <style> html { /* THE TRICK */ margin-left: calc(100vw - 100%); } body { background-color: white; margin: 0px; padding: 0px; } #p1 { /* background-color: rgb(241, 243, 242); */ color: black; text-align: center; font-size: 50; font-weight: bold; } .div1 { background-color: yellowgreen; width: 800px; margin-left: 350px; height: 500px; margin-top: 20px; border: 3px solid rgb(227, 33, 33); } .da { /* margin-top: -10px; */ height: 40px; background-color: rgb(48, 145, 231); border: 3px solid rgb(236, 239, 240); } #chat { width: 600px; height: 50px; } #chat{ border: 2px solid black; display: flex; margin-left: 20px; margin-top: 30px; margin-bottom: 20px; float: left; } #sendbutton{ /* margin-top: -100px; */ border: 2px solid red; padding-top: -30px; width: 80px; height: 40px; float: left; /* margin-top: 400px; */ margin-left: 12px; /* margin-bottom: 30px; */ /* align-items: center; */ } .btn{ margin-top: 40px; } .div2{ /* overflow-y: scroll; */ border:3px solid purple; /* position: fixed; */ height:350px; width: 800px; } #td1{ float: right; margin-left:400px; background-color:white; border-radius:5px; height:30px; padding :5px; /* margin:3px; */ margin-top:3px; } #td2{ /* float:right; */ } #messages{ height:350px; overflow-y:scroll !important; /* scroll-behavior: smooth; */ overscroll-behavior: none; } </style> </head> <body> <div class="div1"> <div class="da"> <p id="p1">Lets Chat </p> </div> <div class="div2" id="messages"> <div class="message"> <table > <?php //error_reporting(0); include 'db.php'; $sql1="SELECT * from chat_info"; $query1=mysqli_query($conn,$sql1); while($info=mysqli_fetch_array($query1)) { ?> <tr> <td id="td1"><?php echo $info['chat']; ?></td> <td id="td2"><?php echo formatDate($info['date']);?></td> </tr> <?php } ?> </table> </div> </div> <script> const messages = document.getElementById('messages'); function appendMessage() { const message = document.getElementsByClassName('message')[0]; const newMessage = message.cloneNode(true); messages.appendChild(newMessage); } function getMessages() { // Prior to getting your messages. shouldScroll = messages.scrollTop + messages.clientHeight === messages.scrollHeight; /* * Get your messages, we'll just simulate it by appending a new one syncronously. */ appendMessage(); // After getting your messages. if (!shouldScroll) { scrollToBottom(); } } function scrollToBottom() { messages.scrollTop = messages.scrollHeight; } scrollToBottom(); setInterval(getMessages, 100); </script> <div class="writearea"> <form action="chat.php" method="post"> <textarea name="chat" id="chat" cols="30" rows="10" placeholder="write a message" required></textarea> <input type="submit" value="send" name="send" id="sendbutton" class="btn"> </form> </div> </div> </body> </html>
数据库连接PHP函数
<?php $host="localhost"; $user="root"; $db="chat"; $conn=mysqli_connect($host,$user,"",$db); if(! function_exists('formatDate')) { function formatDate($date){ return date('g:i a',strtotime($date)); } } ?>
技术解决方案
问题1:解决messages div持续抖动
抖动核心原因是每100ms克隆整个历史消息容器并重复添加,导致DOM元素暴增、容器高度频繁变化,同时CSS的calc(100vw - 100%)可能加剧布局偏移。
修复步骤:
- 替换重复渲染逻辑:改用AJAX拉取新增消息,而非重复克隆全部内容
- 替换页面中的JS代码为:
const messages = document.getElementById('messages'); // 获取初始加载的最后一条消息ID(假设chat_info表有id主键) let lastMessageId = <?php $lastIdQuery = mysqli_query($conn, "SELECT MAX(id) as max_id FROM chat_info"); $lastIdRow = mysqli_fetch_assoc($lastIdQuery); echo $lastIdRow['max_id'] ?? 0; ?>; // 前端复用日期格式化逻辑 function formatDate(dateStr) { const date = new Date(dateStr); let hours = date.getHours(); const ampm = hours >= 12 ? 'pm' : 'am'; hours = hours % 12 || 12; const minutes = date.getMinutes().toString().padStart(2, '0'); return `${hours}:${minutes} ${ampm}`; } async function getNewMessages() { try { const response = await fetch(`get_new_messages.php?last_id=${lastMessageId}`); const newMessages = await response.json(); if (newMessages.length > 0) { const table = document.querySelector('.message table'); newMessages.forEach(msg => { const tr = document.createElement('tr'); // 改用类名避免重复ID tr.innerHTML = `<td class="td1">${msg.chat}</td><td class="td2">${formatDate(msg.date)}</td>`; table.appendChild(tr); }); // 更新最后一条消息ID lastMessageId = newMessages[newMessages.length - 1].id; } } catch (err) { console.error('获取消息失败:', err); } } // 设置初始滚动位置为顶部 messages.scrollTop = 0; // 延长轮询间隔至300ms,减少渲染频率 setInterval(getNewMessages, 300); - 新增
get_new_messages.php文件,用于返回新增消息:<?php include 'db.php'; $lastId = isset($_GET['last_id']) ? intval($_GET['last_id']) : 0; // 仅查询新增消息,避免重复加载 $sql = "SELECT * FROM chat_info WHERE id > ? ORDER BY id ASC"; $stmt = mysqli_prepare($conn, $sql); mysqli_stmt_bind_param($stmt, "i", $lastId); mysqli_stmt_execute($stmt); $result = mysqli_stmt_get_result($stmt); $messages = []; while ($row = mysqli_fetch_assoc($result)) { $messages[] = $row; } echo json_encode($messages); ?>
- 替换页面中的JS代码为:
- 移除布局抖动的CSS:删除
html标签的margin-left: calc(100vw - 100%),若需处理横向滚动条,改为在body添加overflow-x: hidden。
问题2:固定滚动条在顶部
当前代码强制调用scrollToBottom()并在每次拉取消息后自动滚动到底部,导致无法停留在顶部。
修复步骤:
- 删除强制滚动到底部的代码:移除原JS中的
scrollToBottom()调用及相关判断逻辑; - 明确设置初始滚动位置:添加
messages.scrollTop = 0;确保容器初始滚动在顶部; - 保留用户手动滚动状态:不添加自动滚动逻辑,让滚动条位置由用户控制。
额外优化建议
- 修复重复ID问题:将
<td id="td1">改为<td class="td1">,<td id="td2">改为<td class="td2">,同步修改CSS选择器:.td1{ float: right; margin-left:400px; background-color:white; border-radius:5px; height:30px; padding :5px; margin-top:3px; } - 防止SQL注入:修改消息插入的PHP代码为预处理语句:
if(isset($_POST['send'])) { $chat=$_POST['chat']; $sql = "INSERT INTO chat_info(chat) values(?)"; $stmt = mysqli_prepare($conn, $sql); mysqli_stmt_bind_param($stmt, "s", $chat); mysqli_stmt_execute($stmt); header("Location:chat.php"); exit; } - AJAX提交表单:避免页面刷新,监听表单提交事件用fetch发送请求,提升用户体验。
内容的提问来源于stack exchange,提问作者Shorna
相关产品推荐
相关产品推荐

