You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

单端聊天应用messages div抖动及滚动固定问题求助

单端聊天应用问题解决方案

问题描述

我正在开发一款单端聊天应用,目前遇到两个问题:

  1. 中间的messages div内容(包含滚动条)持续抖动;
  2. 无法将滚动条固定在顶部。

以下是完整代码:

主代码

<?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%)可能加剧布局偏移。

修复步骤:

  1. 替换重复渲染逻辑:改用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);
      ?>
      
  2. 移除布局抖动的CSS:删除html标签的margin-left: calc(100vw - 100%),若需处理横向滚动条,改为在body添加overflow-x: hidden。

问题2:固定滚动条在顶部

当前代码强制调用scrollToBottom()并在每次拉取消息后自动滚动到底部,导致无法停留在顶部。

修复步骤:

  1. 删除强制滚动到底部的代码:移除原JS中的scrollToBottom()调用及相关判断逻辑;
  2. 明确设置初始滚动位置:添加messages.scrollTop = 0;确保容器初始滚动在顶部;
  3. 保留用户手动滚动状态:不添加自动滚动逻辑,让滚动条位置由用户控制。

额外优化建议

  1. 修复重复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;
    }
    
  2. 防止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;
    }
    
  3. AJAX提交表单:避免页面刷新,监听表单提交事件用fetch发送请求,提升用户体验。

内容的提问来源于stack exchange,提问作者Shorna

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.23 14:15:31