前言
最近一段时间在研究微信小程序的开发,相比于原生app来说,确实上手要容易不少,也能够提升项目的开发速度。但与此同时,小程序开发中也存在一些“坑”需要我们注意,因此我想记录一下自己在微信小程序开发中遇到的一些问题,方便日后查阅。
在微信小程序开发中实现下拉刷新最简单的方式就是在页面对应的json文件中添加enablePullDownRefresh:true,然后在Page.onPullDownRefresh 方法中执行刷新逻辑。这种方式有一些局限性,一是无法自定义下拉刷新效果,只能使用默认的“三个圆点”样式;二是下拉刷新的视图位置是固定在顶部的,如果要刷新的视图元素不是位于页面顶部,那么使用这种方式就不太好了,最常见的场景就是顶部Tab标签栏,下面是要刷新的列表。在小程序基础库版本2.10.1之后,scroll-view支持自定义下拉刷新了,很好地解决了上述的两个问题,缺点当然就是兼容性了。于是想着自己实现一个下拉刷新组件,由于我本身是做Android开发的,所以尝试实现了一个仿Material Design风格的下拉刷新效果(Android中的SwipeRefeshLayout),先附上一张效果图:
虽然没能做到百分之百的还原,不过基本功能还是实现了的。相关代码我已经上传到了github。
使用方式
将组件拷贝到项目中,在页面的json文件中引入,这里就不展示了。组件可设置的属性如下:
属性 | 类型 | 默认值 | 说明 |
---|---|---|---|
color | string | black | 下拉刷新进度条颜色,默认值为黑色 |
backgroundColor | string | white | 下拉刷新圆圈背景颜色,默认值为白色 |
threshold | number | 20 | 下拉刷新阈值(单位px),下拉距离超过该值时触发刷新 |
loadMoreEnable | boolean | false | 是否开启上拉加载 |
bindrefresh | eventhandle | 下拉刷新回调函数 | |
bindloadmore | eventhandle | 上拉加载回调函数 |
此外,组件还定义了一个setRefresh()
函数,用于设置刷新状态,参数传入一个布尔值,true表示开始刷新,显示下拉刷新圆圈;false表示结束刷新,隐藏下拉刷新圆圈。
示例代码如下:
index.wxml
<swipe-refresh-layout id='refresh' style='width:100vw;height:200rpx;' loadMoreEnable bindrefresh='refresh' bindloadmore='loadMore'>
<!-- 要刷新的内容 -->
</swipe-refresh-layout>
index.js
Page({
/**
* 页面的初始数据
*/
data: {},
/**
* 生命周期函数--监听页面加载
*/
onLoad: function(options) {
this.swipeRefresh = this.selectComponent('#refresh');
},
/**
* 下拉刷新
*/
refresh: function() {
// 模拟获取数据
setTimeout(() => {
// 结束下拉刷新
this.swipeRefresh.setRefresh(false);
}, 3000);
},
/**
* 上拉加载
*/
loadMore: function() {
// ...
}
})
需要注意,使用时必须给swipe-refresh-layout组件一个固定的宽度和高度。
实现原理
实现原理还是比较简单的,大体上就是通过监听触摸事件,在touchstart
事件回调中记录手指按下的坐标;在touchmove
事件回调中计算手指在竖直方向上的滑动距离,通过css的transform属性实现下拉刷新圆圈的移动;在touchend
事件回调中计算手指抬起时的滑动距离,如果向下的滑动距离超过阈值就触发下拉刷新事件,否则将下拉刷新圆圈归位。由于在滑动的过程中需要频繁地进行用户交互,出于性能方面的考虑,我使用了WXS函数用来响应触摸事件,将事件定义在视图层,解决视图层和逻辑层间通信耗时的问题。
虽然整体的实现逻辑不难理解,但是在开发过程中我还是遇到了几个问题,这里简单介绍一下。
- touchmove导致的卡顿问题
一开始我使用的是bindtouchmove
属性监听手指滑动事件,在开发工具上测试时没有什么问题,后来运行在安卓真机上才发现手指触摸屏幕下拉时刷新圆圈的移动比较卡顿,不流畅。在微信开放社区查了一下这个问题,发现也有人遇到过,目前的解决方案就是将bindtouchmove
改为catchtouchmove
。具体原因还不清楚,可能是官方的bug吧,又或者小程序本身就是这样设计的。改为catchtouchmove
后确实是解决了下拉卡顿的问题,但是同时会导致页面内容无法滑动,如何解决这个问题呢,我们不妨考虑一下具体的使用场景,正常情况下只有在页面内容处于顶部时才可以下拉刷新,可以利用这个条件来判断是否需要我们自己处理touchmove事件。具体的做法是这样的,首先在组件最外层使用scroll-view,通过bindscroll
监听滑动事件:
_scroll: function(e) {
if (e.detail.scrollTop <= 50) {
// 滚动到顶部
this.setData({
scrollTop: true
});
} else {
this.setData({
scrollTop: false
});
}
}
使用一个变量scrollTop来记录是否滑动到了顶部,这里的判断条件为什么是e.detail.scrollTop <= 50
而不是e.detail.scrollTop <= 0
呢,因为通过测试我发现有时页面滑动到顶部时e.detail.scrollTop的值并不是0,而是一个接近0的整数,为了保证每次页面滑动到顶部都能改变scrollTop的状态,这里就给了一个默认值,取50是因为官方文档上给出的默认阈值就是50。
之后在catchtouchmove
回调函数中根据scrollTop的值判断是否需要处理滑动事件,如果scrollTop的值为false(页面内容不处位于顶部)就直接return。
touchmove: function(e) {
if (!this.data.scrollTop) {
return;
}
// 处理滑动事件
}
这样就解决了页面内容始终无法滚动的问题,当然上面的代码只是简单地说明解决方法,详细内容可以参考组件的源码。
- 下拉刷新圆圈的显示层级问题
正常情况下下拉刷新圆圈是位于要刷新的内容之上的,并且不会随着内容的滑动而移动,我们很容易就想到使用position:fixed属性,通过z-index属性来设置元素的层叠顺序。但是如果下拉刷新圆圈的z-index指定一个大于0的数,而刷新组件又不是位于页面的顶部,就会导致下拉刷新圆圈始终会显示出来,如下图所示:
我这里采取的解决方案就是将下拉刷新圆圈的z-index指定为-1,而刷新内容的z-index指定为-2(取值不是固定的,只要比下拉刷新圆圈的小就可以),这样就可以解决下拉刷新圆圈覆盖在页面普通视图之上的问题。当然我的解决方案可能不是最好的,或者存在一些问题,如果大家有自己的想法欢迎提出,一起交流。
总结
目前我对于微信小程序的研究还比较浅显,对于CSS属性的运用也不是很熟练,可能组件本身还存在一些问题,或者大家还有更好的实现方案,欢迎提出。
最后附上Demo地址,大家可以进行参考。