Vue中Safari浏览器flex布局消息列表自动滚动到底部失效问题
Safari下flex反向布局聊天室滚动兼容方案
问题描述
- 基于Vue开发类聊天室网站,初始实现采用CSS
display: flex搭配column-reverse属性翻转消息视图,新消息通过Array.unshift方法插入列表头部 - 表现差异:Chrome、Firefox浏览器中该实现可自动将底部最新消息保持在可视区域内,Safari浏览器无法实现相同效果
- 已尝试无效方案:
- 新消息追加后调用
scrollTo方法滚动视图,无用户交互的自动推送消息场景下方法不生效 - 通过
scrollTop属性检测当前是否滚动至底部,用于触发新消息提醒,该判断逻辑在Safari下同样失效
- 新消息追加后调用
初始复现代码
<template> <div id="app" ref="app"> <p v-for="message in messages" > {{ message }} </p> </div> </template> <script> export default { data() { return { messages: [] }; }, mounted() { setInterval(() => { this.messages.unshift((new Date()).toString()) this.$nextTick(() => { this.$refs.app.scrollTo(0, 0) }) }, 1000) } }; </script> <style> #app { font-family: Avenir, Helvetica, Arial, sans-serif; text-align: center; color: #2c3e50; display: flex; flex-direction: column-reverse; overflow-y: scroll; scroll-behavior: smooth; height: 90vh; } a, button { color: #4fc08d; } button { background: none; border: solid 1px; border-radius: 2em; font: inherit; padding: 0.75em 2em; } </style>
兼容实现方案
核心问题根源是Safari对column-reverse布局下的滚动锚定特性支持不完善,且scroll-behavior: smooth属性会拦截无用户交互触发的JS滚动行为,可根据业务场景选择以下两种方案:
方案一:放弃反向布局,改用通用正向消息流实现(推荐)
该方案全浏览器兼容性最好,维护成本最低:
- 调整布局:将容器CSS改为
flex-direction: column,新消息改用Array.push追加到列表尾部 - 滚动逻辑调整:
- 插入新消息前先判断用户当前是否处于接近底部位置,判断阈值设为距底部10px(规避小数滚动值精度问题),判断逻辑放在
requestAnimationFrame回调中执行,确保拿到Safari正确的布局尺寸:const container = this.$refs.app const isAtBottom = container.scrollHeight - container.scrollTop - container.clientHeight <= 10 - 如果用户处于底部,DOM更新后直接将容器滚动到最底部,滚动时临时关闭平滑滚动避免被Safari拦截:
const originScrollBehavior = container.style.scrollBehavior container.style.scrollBehavior = 'auto' container.scrollTop = container.scrollHeight container.style.scrollBehavior = originScrollBehavior - 如果用户不在底部,不自动滚动,展示新消息提醒按钮即可,用户点击按钮后再用平滑滚动回到底部
- 插入新消息前先判断用户当前是否处于接近底部位置,判断阈值设为距底部10px(规避小数滚动值精度问题),判断逻辑放在
方案二:保留column-reverse布局做针对性兼容
如果业务必须保留反向布局结构,做以下修改即可对齐Chrome/Firefox表现:
- 删除容器CSS中的
scroll-behavior: smooth全局设置,需要平滑滚动的场景单独通过JS控制 - 替换原有
$nextTick中调用scrollTo的逻辑,嵌套两层requestAnimationFrame等待Safari完成布局重绘后,直接给scrollTop赋值完成滚动:this.messages.unshift((new Date()).toString()) requestAnimationFrame(() => { requestAnimationFrame(() => { this.$refs.app.scrollTop = 0 }) }) - 修正底部判断逻辑:
column-reverse布局下,最新消息位置对应scrollTop = 0,用户向上翻阅历史消息时scrollTop值会增大,判断是否在底部的逻辑改为判断scrollTop <= 10即可
注意:该方案在iOS Safari低版本中仍可能出现滚动位置跳变问题,优先选择方案一。
内容的提问来源于stack exchange,提问作者舞う巣
相关产品推荐
相关产品推荐

