自定义 TabBar 吃掉了最后一行点击——微信小程序 scroll-view 底部占位坑
一个 C 端小程序上线后收到反馈:城市选择弹窗里的列表,最后几个城市「点不到」。用手指往下滑,能看到选项,但 tap 事件死活不触发,再往上划一点才能选中。复现稳定,机型无关。
第一反应是 z-index 被什么东西覆盖了——但微信开发者工具 Wxml 面板里看不到任何悬浮层叠在列表上面。
现象
scroll-view内的列表最后若干项:肉眼可见、tap/touchstart 均不响应- 只出现在带自定义 tabbar 的页面(
app.json里tabBar.custom: true) - 页面上没有任何显式
position: fixed的业务层元素压住该区域 - 往上滚一点后,同一个 item 就能被点中
根因
微信小程序原生 tabbar 会压缩 page 可视区,使页面内容区域 = 屏幕高度 - tabbar 高度。自定义 tabbar(custom: true)完全不同:它以独立渲染图层 fixed 在屏幕底部,不参与页面布局流,page 高度仍然是全屏高度。
结果就是:scroll-view 的 content 区域延伸到屏幕最底边,最后几项在视觉上「出来了」,但它们物理上位于 tabbar 正下方——自定义 tabbar 图层捕获了触摸事件,业务列表的 tap 永远到不了。
很多人的第一反应是给 scroll-view 的父容器或 scroll-view 本身加 padding-bottom: 100rpx。这不起作用:
/* ❌ 无效:padding 在 scroll-view 外层,不改变滚动内容高度 */
.city-popup { padding-bottom: 100rpx; }
/* ❌ 无效:scroll-view 自身的 padding 在某些基础库版本也不可靠 */
.city-list { padding-bottom: 120rpx; }scroll-view 的可滚动范围由其 content 高度决定,不是 scroll-view 容器高度。给外层加 padding 只会压缩 scroll-view 的可视框,最后一项仍然滚不出 tabbar 遮挡区。硬编码 rpx 数值更是双重错误——不同机型 safe-area-inset-bottom 不同,iPhone X 系列底部安全区会额外吃掉 34pt。
正解
三步固化为全项目统一模式:
第一步:app.wxss 声明全局 CSS 变量(一处定义,全局复用)
/* app.wxss */
page {
--tabbar-height: 98rpx;
--tabbar-occupy: calc(var(--tabbar-height) + env(safe-area-inset-bottom));
}env(safe-area-inset-bottom) 由微信运行时注入,自动适配 iPhone X+ 的底部安全区,无需按机型硬编码。
第二步:在 scroll-view 内部的 wx:for 之后追加占位 view
<!-- ✅ 正确:占位 view 在 scroll-view 内部,紧跟列表末尾 -->
<scroll-view scroll-y class="city-list">
<view wx:for="{{cities}}" wx:key="id" class="city-item" bind:tap="onPick">
{{item.name}}
</view>
<view class="city-picker-safe-bottom"></view>
</scroll-view>占位 view 必须在 </scroll-view> 之前,这样它会撑高 scroll-view 的 content 区,让最后一项能真正滚出 tabbar 遮挡范围。
第三步:占位 class 引用全局变量
.city-picker-safe-bottom {
height: var(--tabbar-occupy);
flex-shrink: 0; /* 父级是 flex 时防止被压扁 */
}核心记忆点:占位必须在 scroll-view 内,用 --tabbar-occupy 变量,不要硬编码 rpx,不要加到外层容器。同一页面有多个 scroll-view 时,按业务语义分别命名(.list-safe-bottom / .search-safe-bottom / .city-picker-safe-bottom),class 内容完全一样,名字区分上下文便于后续按语义单独调整。
什么时候不需要加
- 非自定义 tabbar 页面:原生 tabbar 已压缩 page 可视区,scroll-view 不会超出
- 子页面(navigateTo 进入):没有 tabbar 渲染,无需占位
- scroll-view 已用
bottom: var(--tabbar-height)定位:本身就避开了 tabbar 覆盖区
收到「列表最后一项点不到 / 要多滚一次才选中」反馈时,先看 app.json 是否 tabBar.custom: true,再看 scroll-view 末尾有没有占位 view——两步定位,两分钟搞定。
一句话外卖
自定义 tabbar 是独立渲染层,不压缩页面高度——凡是 tabbar 页面里的 scroll-view,末尾必须有一个高度等于
tabbar + safe-area的内部占位 view,否则最后几项的点击事件永远被 tabbar 吃掉。