Better Scroll 实战踩坑与滚动优化技巧分享
核心代码就这几行,但别急着复制
上周重构一个移动端商品详情页,需要实现一个带 sticky 效果的滚动区域。一开始我直接用原生 overflow-y: scroll,结果在 iOS 上卡成 PPT,手指一松就停——体验差到产品经理差点拍桌子。后来换上 Better Scroll,三行 JS 搞定,流畅得像德芙巧克力。但别以为照搬文档就行,我踩过好几个坑,下面说说怎么用才稳。
先上最基础的初始化代码(亲测有效):
import BScroll from 'better-scroll'
const wrapper = document.querySelector('.scroll-wrapper')
const scroll = new BScroll(wrapper, {
scrollY: true,
click: true,
bounce: false
})
对应的 HTML 结构必须是两层嵌套:
<div class="scroll-wrapper" style="height: 300px; overflow: hidden;">
<div class="scroll-content">
<!-- 你的内容放这里 -->
</div>
</div>
注意:外层容器必须有明确高度(或 max-height),且 overflow: hidden;内层内容高度要能撑开(否则滚不动)。这个结构我一开始没注意,内容高度刚好等于容器高度时,Better Scroll 会认为“不需要滚动”,直接禁用滚动——折腾了半天才发现是这个原因。
又踩坑了,touchmove 滚动失效
在 Vue 项目里,我经常遇到 Better Scroll 初始化后无法滚动的问题。查了好久,发现是因为 DOM 还没渲染完就执行了初始化。解决方法很简单:用 $nextTick 或监听数据加载完成后再 init。
// Vue 2 示例
this.$nextTick(() => {
this.scroll = new BScroll(this.$refs.wrapper, {
scrollY: true
})
})
// 或者等 API 数据回来再初始化
fetch('/api/data')
.then(res => res.json())
.then(data => {
this.list = data
this.$nextTick(() => {
this.initScroll()
})
})
另外,如果内容是动态加载的(比如分页下拉),记得调用 refresh() 方法。很多人忘了这一步,导致新内容加进去后滚不动。我的习惯是在数据更新后加个防抖的 refresh:
methods: {
updateList(newItems) {
this.list.push(...newItems)
this.$nextTick(() => {
this.scroll && this.scroll.refresh()
})
}
}
这个场景最好用:横向滚动 + 点击选中
做商品筛选标签栏时,经常需要横向滚动。Better Scroll 对横向支持也很好,但要注意两点:一是设置 scrollX: true 而不是 scrollY;二是如果标签项是通过点击切换选中状态,必须开启 click: true,否则 click 事件会被 prevent 掉。
const tagScroll = new BScroll('.tag-wrapper', {
scrollX: true,
click: true,
momentum: false, // 关闭惯性滚动,避免点完还滑一段
bounce: false
})
这里有个细节:如果你用的是 FastClick 或类似库来消除 300ms 延迟,可能会和 Better Scroll 的 click 逻辑冲突。建议直接用 Better Scroll 内置的 click 支持,别再额外引入 FastClick 了——我之前两个一起用,导致部分 Android 机点击失效。
踩坑提醒:这三点一定注意
- 不要在滚动容器里放 input 或 textarea。Better Scroll 会阻止默认行为,导致输入框无法聚焦。如果非得放,可以用
disable()和enable()动态开关,或者把 input 放到滚动区域外(用 fixed 定位模拟)。 - destroy 要手动调用。在 Vue/React 组件卸载时,务必调用
scroll.destroy(),否则内存泄漏。我线上项目因为没 destroy,低端机跑久了直接卡死。 - observeDOM 默认是开启的,但有时会漏判。如果内容高度变化很频繁(比如图片懒加载),建议手动调用
refresh(),或者关闭observeDOM改用更可控的方式。
高级技巧:监听滚动位置做吸顶效果
商品详情页常见的“信息栏吸顶”效果,用 Better Scroll 的 on('scroll') 事件就能实现。比 Intersection Observer 简单多了,兼容性也好。
const header = document.querySelector('.detail-header')
const scroll = new BScroll(wrapper, {
scrollY: true,
probeType: 3 // 必须设为 3 才能实时派发 scroll 事件
})
scroll.on('scroll', (pos) => {
if (pos.y < -100) { // 滚动超过 100px
header.classList.add('fixed')
} else {
header.classList.remove('fixed')
}
})
注意:probeType 有三个值(1、2、3),只有 3 是实时触发(每帧都触发),性能消耗大但精准。如果只是做吸顶,用 2(手指拖动时触发)也够用,还能省点性能。
别被文档吓到,其实就这么简单
Better Scroll 文档写得挺全,但新手容易被一堆配置项搞晕。其实日常开发就用那几个核心选项:scrollX/Y、click、bounce、pullUpLoad(上拉加载)。其他像 mouseWheel、swipeTime 之类的,除非有特殊需求,否则不用管。
另外,现在官方主推的是 @better-scroll/core + 插件模式,比老版本更轻量。比如要上拉加载,单独装 @better-scroll/pull-up 插件:
import BScroll from '@better-scroll/core'
import PullUp from '@better-scroll/pull-up'
BScroll.use(PullUp)
const scroll = new BScroll(wrapper, {
pullUpLoad: true
})
scroll.on('pullingUp', () => {
// 加载更多数据
fetchData().then(() => {
scroll.finishPullUp() // 告诉 BS 加载完成
})
})
这样打包体积小很多,按需加载。不过如果你项目不大,直接用完整版 better-scroll 也行,省事。
以上是我踩坑后的总结,希望对你有帮助。Better Scroll 的拓展用法还有很多,比如结合 transition 做平滑滚动、自定义滚动条样式、多层嵌套滚动等,后续会继续分享这类博客。有更优的实现方式欢迎评论区交流。

暂无评论