手风琴组件展开后「内容像被滑到屏外」:一次滚动对齐的排查记录

3 阅读4分钟

一、业务背景

项目是一个 uni-app 多端应用中的支付流程子页面,展示「支付提示」类富文本列表。接口返回多条时,列表采用手风琴:同一时间只展开一条详情,展开某条时其余条收起。

交互上还有自定义导航栏(含状态栏高度),列表在导航占位下方滚动,整页是 页面级滚动(非 scroll-view 包一层的那种)。

二、现象(产品经理视角)

当用户 展开第二条(或从第一条切换到第二条)时:

  • 第一条会收起,第二条会展开,逻辑正确;
  • 但视觉上 第二条的开头(标题/内容起点)常常不在舒适可视区,像是用户手动把页面上滑了一大截,第二条「从半截」才开始出现。

期望:展开哪一条的时候,视口就要对齐到那一条内容的可见起点(至少标题区域不要被「卡在屏幕上方外」)。

三、原因分析(为什么不是简单的 bug)

这里并不是「数据错了」或「v-if 写反了」,而是 布局变化与滚动位置 的经典组合问题:

  1. 手风琴导致文档高度突变
    上一条从「高」变「矮」,下面各块在文档流里 整体向上移动。浏览器/小程序内核不会自动帮你改 scrollTop,所以用户眼睛看到的视口还停留在「旧的滚动偏移量」上。
  2. 固定顶栏占用可视区域
    自定义导航是 fixed,真正留给内容的「安全区」要从 导航总高度(状态栏 + 44px 等)算起。若只做 scrollTop = 0,有时是 整页顶,不一定等于「当前卡片标题刚好贴在导航下面」。
  3. CSS 过渡让量测时机很关键
    若用 max-height + transition 做展开动画,布局在 300ms 内仍在变化。若在 activeIndex 一赋值就立刻量节点位置,rect 可能还是过渡中间态,算出来的滚动目标会偏。

所以:需要在布局稳定后,用「当前滚动 + 节点相对视口位置」算出新的 scrollTop,并减去导航高度,再 pageScrollTo

四、方案对比(简要)

思路优点注意点
仅 pageScrollTo(0)实现简单容易忽略固定导航;且不一定是「当前条起点」
scroll-into-view(若用 scroll-view)声明式当前页是页面滚动时需改结构或不用这条
SelectorQuery + scrollOffset + pageScrollTo不改页面结构,对齐灵活要处理 导航偏移 和 过渡延迟

最终采用 锚点 id + boundingClientRect + selectViewport().scrollOffset() + uni.pageScrollTo,与现有自定义导航高度字段对齐。

五、实现要点(可复用的套路)

  1. 给每一条可展开卡片包一层带唯一 id 的节点(如 tip-card-0tip-card-1),量测「这一条内容的起点」——我们用的是 整张卡片顶部(含标题),与产品说的「从头展示」一致。

  2. 仅在「从展开 A 切换到展开 B」时滚动;用户点击「收起当前条」时不必滚动,避免多余跳动。

  3. 时机:this.$nextTick 后 setTimeout 略大于 max-height 动画时长(例如样式是 0.3s,则用 ~320ms),再执行查询与滚动,减少过渡中途量测的误差。

  4. 计算公式(核心)

    • 设当前页面 scrollTop 为 s,卡片相对视口顶部的 top 为 rect.top(可为负,表示已在屏外上方)。
    • 希望卡片顶出现在 导航栏下沿 对应的位置,则:
    • targetScrollTop = s + rect.top - navbarHeight
    • 再 Math.max(0, targetScrollTop) 避免负数。
  5. uni.pageScrollTo 的 duration 设一个较短值(如 200ms),过渡柔和;若仍觉得「滚早了/滚晚了」,优先调延迟毫秒数,而不是改公式。

六、可调参数与扩展

  • 延迟:不同机型、不同富文本渲染耗时不同,可在 280~400ms 间微调。
  • 对齐点:若产品希望对齐「正文」而非「标题」,把 id 挂到标题下方的容器即可,公式不变。
  • 快速连点:若极端场景下连续切换多条,可考虑用递增 token 取消过期的 setTimeout,避免旧回调覆盖新状态(当时页面未做,可视需要加)。

七、小结

  • 本质:折叠/展开改变了文档流高度,滚动偏移未随内容重排自动修正,造成「展开项像被滑到屏外」。
  • 做法:在 动画结束后的下一帧附近 量测目标卡片位置,用 scrollTop + rect.top - 导航高度 得到目标滚动值,再 pageScrollTo
  • 体验:展开哪条,视口就跟到哪条的起点,和固定导航对齐,产品反馈会明显好于单纯滚到页面顶部。