前言
今天想优化下用户体验:给小程序商品列表页顶部的标签栏增加个吸顶动画。逻辑是检测用户下滑行为时不吸顶,上滑时吸顶,标签栏未滑离视窗时不处理。并对滑动的处理方法增加节流处理,以优化性能。
一、处理页面滚动
1.1 页面逻辑
页面page_name.js
:
// ...
onPageScroll(e){
this.selectComponent('#goods_list').data._handleScroll(e)
}
// ...
因为业务需要,商品列表goods_list
被我封装成组件形式,所以需要调用组件内的方法来处理。可能有人好奇,为什么组件方法名前有一般用来表示私有方法的下划线_
,却还被外部调用,后面会进行说明。
1.2 商品列表逻辑
商品列表组件goods_list.js
:
import utils from '../../utils/utils'
const HEIGHT_TAB = 70 // 标签栏的高度
// ...
options: {
pureDataPattern: /^_/, // 纯数据字段的正则
},
data:{
_scrollTop:0, // 页面滚动的距离
_handleScroll:_ =>{}, // 防抖处理后的
isSticky:false, // 标签栏是否是吸顶状态
},
lifeTimes(){
attached() {
// 组件挂载时,设置处理滚动的函数
this._setHandleScroll()
},
},
methods(){
_setHandleScroll() {
this.data._handleScroll = utils.throttle(
e => {
const nextScrollTop = e.scrollTop
const prevScrollTop = this.data._scrollTop
if (nextScrollTop > prevScrollTop) {
this.setData({
isSticky: false,
})
} else if (nextScrollTop < prevScrollTop && nextScrollTop > HEIGHT_TAB) {
this.setData({
isSticky: true,
})
}
this.setData({
_scrollTop: nextScrollTop,
})
},
200,
this
)
},
},
// ...
1.3 节流逻辑
utils.js
:
/**
*
* @param fn 原函数
* @param gapTime 间隔时间
* @param context 上下文
*/
throttle(fn, gapTime, context) {
let prev = Date.now()
return function () {
let args = arguments
let now = Date.now()
if (now - prev > gapTime) {
prev = now
fn.apply(context, args)
}
}
}
tips:
- 小程序中,因为页面、组件上下文的关系,需要在方法内部设置防抖函数,如上文所示:
attached
时,赋值_handleScroll
为处理函数(这里可以进一步展开讲,关于小程序的上下文和防抖节流的原理,后面可能会出一篇); - 为了性能考虑,对于不需要与渲染相关的data,用
pureDataPattern
匹配这些data,以减小页面的重绘repaint
与回流reflow
,在我的项目中此类data都是以_
开头的,所以出现了前文中外部调用组件_
开头方法的问题
二、吸顶动画处理
goods_list.wxml
:
<!-- tab栏 -->
<view class="goods-tab {{isSticky ? 'sticky' :''}}">
<view
wx:for="{{tabList}}"
wx:key="sortStyle"
class="item {{sortStyle === item.sortStyle ? 'active':''}}"
bind:tap="clickTabItem"
data-sort-css">style="{{item.sortStyle}}"
>
<view class="title">{{item.title}}</view>
<view class="sub-title">{{item.subTitle}}</view>
</view>
</view>
goods_list.scss
:
.goods-tab {
display: flex;
justify-content: space-between;
align-items: center;
padding: 30rpx 24rpx 20rpx 24rpx;
background: #f8f8f8;
transition: all 0.2s;
z-index: 1003;
position: sticky; // 动作前后position保持一致,有相同属性的变化才能有过渡动画
top: -146rpx; // 标签栏离开视窗时的位置
width: 750rpx;
&.sticky {
position: sticky;
top: 0rpx;
}
// ...
}