PHP+AJAX实现滚动到顶部加载历史消息的技术问询
实现聊天应用的反向无限滚动(滚动顶部加载历史消息)
需求说明
开发聊天应用时,为降低服务器负载,默认仅加载最后20条消息,用户打开聊天框后默认定位到消息底部。需要实现反向无限滚动:当用户滚动到消息顶部时,加载更早的20条历史消息,直到所有消息加载完毕。
当前代码情况
后端SQL查询(PHP)
当前查询仅能获取最后20条消息:
$stmt = $this->pdo->prepare( "SELECT * FROM ( SELECT * FROM messages ORDER BY id DESC LIMIT 20 ) m WHERE ( (msg_to = :omsg AND msg_from = :imsg) OR (msg_to = :imsg AND msg_from = :omsg) ) AND (clear_by = 0 OR clear_by != :omsg) ORDER BY id ASC");
前端滚动监听(jQuery)
当前仅监听滚动到顶部触发请求,但存在重复请求、未传递分页标识等问题:
$('.chat-box').scroll(function (event) { var st = $(this).scrollTop(); // 滚动到顶部时前置旧消息 if(st <= 10){ $.ajax({ url: 'processes/chat.php', type: 'POST', data: 'type=loadmore&to='+to, dataType: 'JSON', success: function(data){ $(".retrive-chat").prepend(data.html); } }); } });
解决方案
1. 后端修改:支持分页加载历史消息
需要接收当前已加载的最小消息ID(即最早的那条消息ID),以此为条件查询更早的消息:
// 加载更多历史消息的查询语句 $stmt = $this->pdo->prepare( "SELECT * FROM messages WHERE ( (msg_to = :omsg AND msg_from = :imsg) OR (msg_to = :imsg AND msg_from = :omsg) ) AND (clear_by = 0 OR clear_by != :omsg) AND id < :min_id -- 筛选比当前最早消息ID更小的历史消息 ORDER BY id DESC LIMIT 20"); // 绑定参数:新增最小消息ID参数 $stmt->bindParam(':min_id', $minId, PDO::PARAM_INT); $stmt->bindParam(':omsg', $omsg, PDO::PARAM_INT); $stmt->bindParam(':imsg', $imsg, PDO::PARAM_INT); $stmt->execute(); // 将结果按ID升序排列,保证消息顺序正确 $messages = $stmt->fetchAll(PDO::FETCH_ASSOC); $messages = array_reverse($messages);
2. 前端优化:避免重复请求+维持滚动位置
- 添加加载状态锁,防止滚动时重复触发AJAX请求
- 记录当前已加载的最小消息ID,传递给后端
- 加载历史消息后,计算滚动偏移量,避免用户浏览位置跳动
修改后的前端代码:
// 全局变量:加载状态锁、当前已加载的最小消息ID、是否还有更多消息 let isLoading = false; let minMsgId = null; let hasMore = true; $('.chat-box').scroll(function (event) { const $chatBox = $(this); const st = $chatBox.scrollTop(); // 仅在未加载、还有更多消息、滚动到顶部附近时触发请求 if (st <= 10 && !isLoading && hasMore) { isLoading = true; // 首次加载后,从DOM中获取当前已加载的最小消息ID if (!minMsgId) { minMsgId = parseInt($(".retrive-chat .message-item:first").data('msg-id')); } $.ajax({ url: 'processes/chat.php', type: 'POST', data: { type: 'loadmore', to: to, min_id: minMsgId // 传递最小消息ID给后端 }, dataType: 'JSON', success: function(data){ if (data.html && data.html.length > 0) { // 更新当前已加载的最小消息ID minMsgId = parseInt(data.new_min_id); // 计算加载前后的消息高度差,维持用户滚动位置 const prevHeight = $(".retrive-chat").height(); $(".retrive-chat").prepend(data.html); const newHeight = $(".retrive-chat").height(); $chatBox.scrollTop(newHeight - prevHeight); } else { // 没有更多历史消息,停止触发请求 hasMore = false; } isLoading = false; }, error: function(){ isLoading = false; } }); } });
3. 额外注意事项
- 每条消息的DOM元素需要添加
data-msg-id属性,存储消息ID,方便前端获取最小ID - 后端返回数据时,除了消息HTML片段,还要返回本次加载的消息中的最小ID(
new_min_id) - 当后端查询不到更多消息时,返回空HTML,前端设置
hasMore = false终止请求触发
内容的提问来源于stack exchange,提问作者Super Coder
相关产品推荐
相关产品推荐

