Better Scroll 滚动优化实战与常见问题避坑指南
为什么我又在 Better Scroll 里折腾了三天?
上周我接了个需求:一个带下拉刷新、上拉加载、横向滚动 tab 的移动端列表页。UI 给的原型里,交互细节特别多,比如“下拉到 80px 才触发刷新”“滚动到底部自动加载但不能闪动”“横向 tab 滚动后要高亮当前项”。我第一反应是:用原生 scroll 肯定搞不定,得上 Better Scroll。
但问题来了——Better Scroll 现在有三种主流用法:直接 new BScroll()、用 Vue 官方封装的 vue-better-scroll、或者自己写个 hooks 封装。我以前都混着用,这次索性把三个方案都试了一遍,结果踩了不少坑,也摸清了各自的脾气。
谁更灵活?谁更省事?
先说结论:我比较喜欢自己封装 hooks。别急着喷,听我慢慢说。
最省事的肯定是 vue-better-scroll,官方出的,文档齐全,开箱即用。比如实现一个简单的垂直滚动:
<template>
<div class="wrapper" ref="wrapper">
<div class="content">
<div v-for="item in list" :key="item.id">{{ item.text }}</div>
</div>
</div>
</template>
<script>
import { ref, onMounted } from 'vue'
import BScroll from '@better-scroll/core'
import PullDown from '@better-scroll/pull-down'
import PullUp from '@better-scroll/pull-up'
BScroll.use(PullDown).use(PullUp)
export default {
setup() {
const wrapper = ref(null)
let bs = null
onMounted(() => {
bs = new BScroll(wrapper.value, {
pullDownRefresh: true,
pullUpLoad: true
})
bs.on('pullingDown', () => {
// 模拟刷新
setTimeout(() => {
bs.finishPullDown()
}, 1000)
})
})
}
}
</script>
看起来挺清爽,对吧?但问题很快来了:当我想监听“滚动到某个位置高亮 tab”时,发现 vue-better-scroll 的 props 设计太死板,很多底层事件(比如 scrollEnd)根本透不出来。折腾了半天,最后还是得手动调用 bs.on('scrollEnd', ...),那封装的意义在哪?
而直接 new BScroll() 呢?自由度最高,但重复代码多得要命。每个页面都要写 ref、onMounted、onBeforeUnmount 销毁实例……我项目里有七八个滚动页,复制粘贴都快吐了。
所以最后我选了第三条路:自己写个 useBetterScroll hooks。核心代码就这几行:
// composables/useBetterScroll.js
import { onMounted, onBeforeUnmount, ref } from 'vue'
import BScroll from '@better-scroll/core'
import PullDown from '@better-scroll/pull-down'
import PullUp from '@better-scroll/pull-up'
import ObserveDOM from '@better-scroll/observe-dom'
BScroll.use(PullDown).use(PullUp).use(ObserveDOM)
export function useBetterScroll(wrapperRef, options) {
const bs = ref(null)
onMounted(() => {
if (!wrapperRef.value) return
bs.value = new BScroll(wrapperRef.value, {
observeDOM: true,
...options
})
})
onBeforeUnmount(() => {
bs.value?.destroy()
})
return bs
}
在组件里用起来贼简单:
// 在组件中
const wrapper = ref(null)
const bs = useBetterScroll(wrapper, {
pullDownRefresh: true,
pullUpLoad: true
})
// 直接操作 bs.value
bs.value?.on('pullingDown', handlePullDown)
这里注意我踩过好几次坑:一定要加 observeDOM: true 插件!否则动态添加内容后,Better Scroll 不会自动更新高度,导致滚动区域错乱。这个插件虽然有点性能损耗,但比起手动调 refresh(),我宁愿牺牲这点性能。
又踩坑了,touchmove 滚动失效
不管用哪种方案,都会遇到一个经典问题:某些安卓机上 touchmove 被浏览器默认行为拦截,滚动直接卡死。我一开始以为是 Better Scroll 的锅,后来发现是页面根元素没加 CSS。
解决方案很简单,但很容易被忽略:
/* 必须加! */
.wrapper {
height: 100vh;
overflow: hidden;
}
.content {
/* 内容高度由 Better Scroll 控制,这里不用设 */
}
如果 .wrapper 没有明确的高度,或者父级有 overflow: auto,Better Scroll 就无法正确计算滚动区域。我曾经在一个嵌套路由里忘了设高度,折腾了两个小时才定位到问题。
另外,如果你用了 pullDownRefresh,记得在 finishPullDown() 后手动调一次 refresh(),否则新内容可能显示不全。官方文档里提了一句,但藏得特别深。
我的选型逻辑
现在我基本固定了选型规则:
- 简单滚动(比如纯展示列表):直接 new BScroll(),不折腾
- 复杂交互(下拉刷新 + 上拉加载 + 滚动联动):必须用自定义 hooks,方便复用和调试
- 赶工期且交互简单:可以考虑 vue-better-scroll,但要做好后期重写的准备
性能方面,其实三个方案差距不大,因为底层都是同一个 Better Scroll。真正影响性能的是你有没有滥用 refresh() 或者监听太多滚动事件。我建议只在必要时监听 scroll 事件,其他用 scrollEnd 代替,能省不少 CPU。
对了,如果你用的是 React,也有类似方案,比如 use-better-scroll 这个库,但我不熟,就不展开了。反正思路一样:封装 hooks,避免重复代码。
结尾:以上是我踩坑后的总结
折腾完这波,我算是把 Better Scroll 的几种用法都摸透了。虽然自己封装 hooks 多花了一点时间,但后续维护成本低太多了。现在改个滚动逻辑,只需要改 hooks 文件,所有页面自动生效。
当然,这个方案也不是 100% 完美。比如在 SSR 场景下,Better Scroll 会在服务端报错,得加 if (process.client) 判断。不过这种小问题无伤大雅,总比每个页面都写一堆初始化代码强。
以上是我个人对 Better Scroll 几种技术方案的对比总结,有更优的实现方式欢迎评论区交流。这个技巧的拓展用法还有很多(比如结合 Intersection Observer 做懒加载),后续会继续分享这类博客。

暂无评论