记得最开始做小程序的时候,遇到的第一个坑便是 <scroll-view></scroll-view> 和小程序页面级别的下拉刷新冲突的问题。即:<scroll-view></scroll-view>标签与onPullDownRefresh事件无法同时使用。

当时想做成的效果就是一个可以作用拖动的长列表,同时具有下拉刷新效果,大概是这样:


PS:用的安卓模拟器录屏为MP4,ffmpeg 执行 ffmpeg -ss 0 -t 5 -i 11.mp4 -s 300*500 -r 15 res.gif 得到 gif


虽然这样确实会有很严重的性能问题,但是因为这是个练手项目。当时还在学前端,一心想的就是如何实现想要的效果,没有考虑性能这方面。

如上图所示,实现一个左右拖动的列表并不难,只需要将几个 <scroll-view></scroll-view> 放在 <swiper></swiper> 里就好了。那么这个时候如何实现下拉刷新呢?

好了,不废话了。当时被我找到了一种方法可以模拟实现下拉刷新:用 <swiper></swiper> 实现。将上面可以左右拖动里面放了四个 <scroll-view></scroll-view> 的 <swiper></swiper> 再放进一个只有一个 item 的 <swiper></swiper>里,通过监听外面这个 <swiper></swiper> 的 scroll 事件来触发下拉刷新效果。代码就是这样:



效果大概是这样:



虽然这个录屏效果使用的模拟器,本身会有点卡。但是实际跑在手机上也并没有流畅多少。但是多少也算一种尝试。

后来在工作中,原生小程序语法用的比较少了。更多的用的是 uniapp。不巧的是 uniapp 除了实现了超强的跨平台能力之外,也完美的继承了小程序的各种缺点。就包括这个冲突问题。

好在小程序中遇到的像这种左右拖动的长列表的业务场景比较少(主要是卡)。但是在 app 端和 h5 端,基本上不存在太严重的性能问题。于是当时还对之前的这个想法做了一个封装,放在 uniapp 插件市场了。后来在项目中也大量使用了,效果图:



这个插件就叫:uniapp 左右滑动切换, scrollview 下拉刷新

简介就一句话:利用一个只有一个 swiper-item 纵向 swiper 组件,模拟下拉刷新效果


// 部分源码实现
<swiper class="pulldown" vertical @touchend="touchend" @transition="transition">
    <swiper-item class="pulldown-item" :style="{ top: pulldownRefreshingTop + 'px' }">
        <slot></slot>
    </swiper-item>
</swiper>


插件放在 uniapp 插件市场了,传送门。

照目前这个情况,事情似乎已经解决了。但是实际上,上面的插件在一些情况下还是会卡。以为纯前端实现的无限上拉列表本来在跨平台应用中就有性能问题。而我也一直在找还有没有更好的技术解决方案,最好官方从组件上能支持下拉刷新。直到小程序 2.10.1 发布:



这不就是我苦苦等待的原生支持嘛!话不多说,直接用在项目里。然后下面就开始踩坑了!!!

如果你打开 <scroll-view></scroll-view> 官方文档,第一句话就是:



就是用 <scroll-view></scroll-view> 你必须显式给 <scroll-view></scroll-view> 的高度指定一个固定的 px/rpx。至于什么 height: 100%; 或者 flex: 1; 根本不生效。于是在初期,我只能用了一个比较麻烦的方法,就是页面加载过来后,获取一下 <scroll-view></scroll-view> 父级的高度,然后用状态的形式赋给 style,在 uniapp 中的使用是这样的:


Vue.prototype.$offset = function (selector) { // 获取元素宽高位置信息
	return new Promise((resolve, reject) => {
		uni.createSelectorQuery().in(this).select(selector).boundingClientRect(data => {
			data ? resolve(data) : reject('元素不存在')
		}).exec()
	})
}


在组件中使用:


mounted() {
    this.request()
    this.$offset('.scroll').then(res => {
        this.scrollViewHeight = res.height
    })
},


可以看出,相当繁琐。

但是当 <scroll-view></scroll-view> 开启 refresher-enabled 为 true 时,动态设置 <scroll-view></scroll-view> 的高度会使 refresher-enabled 达不到预期效果。refresher-enabled 参考的 <scroll-view></scroll-view> 高度是页面挂载之后的初始高度,异步赋值的高度不会被 refresher-enabled 感知。

也就是如果 <scroll-view></scroll-view> 一开始 scrollViewHeight 的值为0,当页面挂载后执行 this.$offset('.scroll') 获取 <scroll-view></scroll-view> 的高度赋给 scrollViewHeight 。此时即使开启了 refresher-enabled 也没有下拉刷新效果。因为 异步赋值的高度不会被 refresher-enabled 感知。

如何解决?

两种方法:

第一种:<scroll-view v-if="scrollViewHeight "></scroll-view>

保证 <scroll-view></scroll-view> 挂载时有足够的高度。

第二种(推荐):<scroll-view style="height: calc(100vh - 500rpx);"></scroll-view>

是的 calc 写在行间就可以利用 css 函数的能力,获取 <scroll-view></scroll-view> 的高度,而不用借助 js。很方便有没有?

PS:还有一种情况就是:当开启 refresher-enabled 后,当 <scroll-view></scroll-view> 触顶时,手指下拉一定的距离松开后会触发 refresherrefresh 事件除此之外,在某些微信开发者工具基础库中,上拉触底也会触发 refresherrefresh 。不过在新版的基础库已经没有这种情况了。