一、需求说明
使用微信小程序原生代码,实现如上图的交互效果。具体要求如下:
- 当手指触发(tap / touch) Tab区块时,页面自动向上滚动,滚动至视窗的顶部。
- Tab区块,最多只能滚动到视窗的顶部,不可以继续向上滚动。
- 当用户向下滑动时,Tab区块正常向下滚动。
- 当Tab区块里的内容超出一屏的高度时,只能在 Tab区块的内部进行滚动。
二、封装自定义组件 <my-swiper>
根据以上需求,使用原生小程序组件和代码,封装一个 <my-swiper> 组件,完整代码如下所示。其中的要点(坑)罗列如下:
- 当Tab区块置顶时,<my-swiper>组件的高度必须等于屏幕视窗的高度,因此将使用
wx.getSystemInfo
来动态获取屏幕尺寸(单位是px
),通过计算后,手动地设置自定义组件<my-swiper> 和 原生组件<swiper>
的高度,分别是h1
和h2
。详细代码见下方的myswiper.js
。 - 小程序原生组件
<swiper>
是有默认高度的,必须手动设置其高度h2
。 -
<swiper-item>
默认是position:absolute;overflow:hidden;
的,我们必须更改其默认样式(见下方的myswiper.wxss
文件),否则当<swiper-item>
内部内容较多时会被隐藏掉。另外,为了让<swiper-item>
中的内容滚动更流畅,还要为其加上-webkit-overflow-scrolling:touch;
属性。 - Tab项可能多于3个,或者是 4个,因此建议使用
flex
来动态布局,以确保 Tabs的数量是可变的。 - 使用
slot
插槽来动态渲染 Tab区块中的内容,在组合使用<slot name='item' />
来定义插槽。 - 为<my-swiper> 组件绑定
tap``touch
事件,并判断当用户点击、上滑时,让页面滚动 <my-swiper> 的位置。详细逻辑见下方的myswiper.js
文件。
# myswiper.wxml
<view class='myswiper' style="height: {{h1}}px;" bindtap='tabTouch' bindtouchstart='touchStart' bindtouchend='touchEnd'>
<!-- Tabs -->
<view class="tabs">
<view class="tab {{curIdx == index ? 'on' : ''}}" wx:for="{{tabArr}}" wx:key="{{index}}" wx:for-item="item" data-index="{{index}}" bindtap="tabClick" >
<text class="tab-title">{{item}}</text>
<text class="tab-line"></text>
</view>
</view>
<!-- swiper -->
<swiper class='swiper' current="{{curIdx}}" bindchange="swiperChange" style="height: {{h2}}px;">
<swiper-item wx:for="{{tabArr}}" wx:key="{{index}}">
<slot name='item{{index}}'></slot>
</swiper-item>
</swiper>
</view>
# myswiper.wxss
.myswiper {
position: relative;
/* height: 1200rpx; */
background: white;
}
.tabs{
width: 100%;
height: 100rpx;
background-color: #eeeeee;
display: flex;
}
.tab{
flex: 1;
font-size:28rpx;
font-family:PingFangSC;
font-weight:400;
color:rgba(102,102,102,1);
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
font-weight: bold;
}
.tab>text{
display: block;
}
.on .tab-line{
width:90rpx;
height:4rpx;
background:rgba(255,117,56,1);
border-radius:3rpx;
margin-top: 5rpx;
}
.on .tab-title{
font-size:32rpx;
font-family:PingFangSC;
font-weight:500;
color:rgba(255,117,56,1);
}
.swiper swiper-item {
/* 开启swiper-item滚动,touch流畅滚动 */
overflow: scroll;
-webkit-overflow-scrolling:touch;
}
# myswiper.js
let startY = 0;
Component({
options: {
multipleSlots: true // 启用多slot支持
},
properties: {
tabArr: { /* Tab项的名称列表 */
type: Array,
value: []
}
},
data: {
h1: '', // my-swiper 组件的高度
h2: '', // swiper 组件的高度
curIdx: 0 // tab 索引号
},
attached: function() {
const that = this
// 获取信息,动态设置组件高度
wx.getSystemInfo({
success: function(e) {
const w = e.windowWidth
const h = e.windowHeight
// 100 是Tab的默认高度,单位rpx
// 把 rpx 转化为 px
const tabH = 100 * w / h
that.setData({ h1: h, h2: h - tabH })
}
});
},
methods: {
/* my-swiper 组件的系列事件 */
tabTouch: function(e) {
// 当用户点击my-swiper组件时,将其置顶
const top = e.currentTarget.offsetTop
// 滚动到顶部
wx.pageScrollTo({scrollTop: top});
},
touchStart: function(e) {
startY = e.changedTouches[0].clientY
console.log('start', startY)
},
touchEnd: function(e) {
const endY = e.changedTouches[0].clientY
// 当手势向上滑动时,把my-swiper组件置顶
if (startY - endY > 10) {
const top = e.currentTarget.offsetTop
wx.pageScrollTo({scrollTop: top});
}
console.log('end', endY)
},
// 点击标签事件
tabClick:function(e){
let data = e.currentTarget.dataset;
this.setData({ curIdx: data.index })
},
// swiper切换事件
swiperChange:function(e){
this.setData({ curIdx:e.detail.current })
},
}
})
# myswiper.json
{ "component": true }
三、使用 <my-swiper> 组件,进行测试
<my-swiper>
组件封装好了,测试代码如下。我们只需要传递一个 tabArr 标签列表进去即可。再使用 slot
插槽动态地插入多个区块的内容。
# demo.json
{
"navigationBarTitleText": "自定义Swiper组件及特效",
"usingComponents": {
"my-swiper": "/component/myswiper/myswiper"
}
}
# demo.wxml
<view>
<view class='other'>
<image mode='aspectFit' src='./a.png' />
</view>
<my-swiper tabArr="{{['人物','自然','汽车','美女']}}">
<view slot='item0' class='content'>人物</view>
<view slot='item1' class='content'>自然</view>
<view slot='item2' class='content'>汽车</view>
<view slot='item3' class='content'>美女</view>
</my-swiper>
</view>
# demo.wxss
.other image {
display: block;
width: 100%;
}
.content {
font-size: 80rpx;
text-align: center;
line-height: 200rpx;
}
# demo.js
Page({
data: {},
onLoad: function() {},
})
四、总结
<my-swiper>
组件的封装,填平了一些坑,比如 swiper
默认的样式问题。通过这个组件,我们至少学到了如下前端知识:
- 什么是组件?如何自定义组件?
- 如何使用小程序的原生组件,并修改其默认的样式?
- 学会使用 slot 插槽,实现组件内容的差异化
- 学会使用小程序原生 api 获取手机信息,用 js 改变组件样式
- 学会使用 touch 事件,区分
e.touches
和e.changedTouches
等 - 学会
px
和rpx
单位之间的区别和相互转化 - 学会使用小程序 api 来手动地滚动页面,等。
以上完整代码,复制黏贴即可实现这个效果,相关要点已经在代码中进行了注释。事实上,要总结的内容还有很多,时间有限就不啰嗦了。好了,本篇就总结到这里。
往期 相关的小程序demo笔记:
本篇结束 2019-08-27
网友评论