[improvement] Tabs:support swipeable and sticky property (#1019)

This commit is contained in:
张敏 2018-12-06 15:27:24 +08:00 committed by neverland
parent 696ae3c661
commit e1b68de7fc
5 changed files with 192 additions and 7 deletions

View File

@ -3,7 +3,8 @@ import Page from '../../common/page';
Page({
data: {
tabs: [1, 2, 3, 4],
tabsMore: [1, 2, 3, 4, 5, 6, 7, 8]
tabsMore: [1, 2, 3, 4, 5, 6, 7, 8],
scrollTop: 0
},
onClickDisabled(event) {
@ -25,5 +26,11 @@ Page({
title: `点击标签 ${event.detail.index + 1}`,
icon: 'none'
});
},
onPageScroll(event) {
this.setData({
scrollTop: event.scrollTop
});
}
});

View File

@ -69,6 +69,20 @@
</van-tabs>
</demo-block>
<demo-block title="粘性布局">
<van-tabs sticky scroll-top="{{ scrollTop }}">
<van-tab
wx:for="1234"
wx:key="index"
title="{{ '标签' + item }}"
>
<view class="content">
{{ '内容' + item }}
</view>
</van-tab>
</van-tabs>
</demo-block>
<demo-block title="切换动画">
<van-tabs animated>
<van-tab
@ -82,3 +96,17 @@
</van-tab>
</van-tabs>
</demo-block>
<demo-block title="滑动切换">
<van-tabs swipeable>
<van-tab
wx:for="1234"
wx:key="index"
title="{{ '标签' + item }}"
>
<view class="content" style="height: 1000px;">
{{ '内容' + item }}
</view>
</van-tab>
</van-tabs>
</demo-block>

View File

@ -111,6 +111,19 @@ Page({
});
```
#### 粘性布局
通过`sticky`属性可以开启粘性布局,粘性布局下,当 Tab 滚动到顶部时会自动吸顶
```html
<van-tabs sticky>
<van-tab title="标签 1">内容 1</van-tab>
<van-tab title="标签 2">内容 2</van-tab>
<van-tab title="标签 3">内容 3</van-tab>
<van-tab title="标签 4">内容 4</van-tab>
</van-tabs>
```
#### 切换动画
可以通过`animated`来设置是否启用切换tab时的动画。
@ -124,6 +137,19 @@ Page({
</van-tabs>
```
#### 滑动切换
通过`swipeable`属性可以开启滑动切换标签页
```html
<van-tabs swipeable>
<van-tab title="标签 1">内容 1</van-tab>
<van-tab title="标签 2">内容 2</van-tab>
<van-tab title="标签 3">内容 3</van-tab>
<van-tab title="标签 4">内容 4</van-tab>
</van-tabs>
```
### Tabs API
| 参数 | 说明 | 类型 | 默认值 |
@ -137,6 +163,10 @@ Page({
| line-width | 底部条宽度 (px) | `Number` | 与当前标签等宽 |
| swipe-threshold | 滚动阈值,设置标签数量超过多少个可滚动 | `Number` | `4` |
| animated | 是否使用动画切换 Tabs | `Boolean` | `false` |
| swipeable | 是否开启手势滑动切换 | `Boolean` | `false` |
| sticky | 是否使用粘性定位布局 | `Boolean` | `false` |
| offset-top | 粘性定位布局下与顶部的最小距离,单位 px | `Number` | `0` |
| scroll-top | 页面的`scrollTop`,粘性布局下必须要传入,单位 px | `Number` | `0` |
### Tab API
@ -158,6 +188,7 @@ Page({
| bind:click | 点击标签时触发 | index标签索引title标题 |
| bind:change | 当前激活的标签改变时触发 | index标签索引title标题 |
| bind:disabled | 点击被禁用的标签时触发 | index标签索引title标题 |
| bind:scroll | 滚动时触发 | { scrollTop: 距离顶部位置, isFixed: 是否吸顶 } |
### 外部样式类

View File

@ -1,4 +1,5 @@
import { VantComponent } from '../common/component';
import { touch } from '../mixins/touch';
type TabItemData = {
active: boolean;
@ -8,6 +9,8 @@ type TabItemData = {
};
VantComponent({
mixins: [touch],
relation: {
name: 'tab',
type: 'descendant',
@ -54,7 +57,17 @@ VantComponent({
type: Number,
value: 4
},
animated: Boolean
animated: Boolean,
sticky: Boolean,
offsetTop: {
type: Number,
value: 0
},
swipeable: Boolean,
scrollTop: {
type: Number,
value: 0
}
},
data: {
@ -62,7 +75,9 @@ VantComponent({
lineStyle: '',
scrollLeft: 0,
scrollable: false,
trackStyle: ''
trackStyle: '',
wrapStyle: '',
position: ''
},
watch: {
@ -74,7 +89,9 @@ VantComponent({
color: 'setLine',
lineWidth: 'setLine',
active: 'setActiveTab',
animated: 'setTrack'
animated: 'setTrack',
scrollTop: 'onScroll',
offsetTop: 'setWrapStyle'
},
beforeCreate() {
@ -231,6 +248,100 @@ VantComponent({
});
});
});
},
onTouchStart(event: Weapp.TouchEvent) {
if (!this.data.swipeable) return;
this.touchStart(event);
},
onTouchMove(event: Weapp.TouchEvent) {
if (!this.data.swipeable) return;
this.touchMove(event);
},
// watch swipe touch end
onTouchEnd() {
if (!this.data.swipeable) return;
const { active, tabs } = this.data;
const { direction, deltaX, offsetX } = this;
const minSwipeDistance = 50;
if (direction === 'horizontal' && offsetX >= minSwipeDistance) {
if (deltaX > 0 && active !== 0) {
this.setActive(active - 1);
} else if (deltaX < 0 && active !== tabs.length - 1) {
this.setActive(active + 1);
}
}
},
setWrapStyle() {
const { offsetTop, position } = this.data;
let wrapStyle;
switch (position) {
case 'top':
wrapStyle = `
top: ${offsetTop}px;
position: fixed;
`;
break;
case 'bottom':
wrapStyle = `
top: auto;
bottom: 0;
`;
break;
default:
wrapStyle = '';
}
// cut down `setData`
if (wrapStyle === this.data.wrapStyle) return;
this.setData({
wrapStyle
});
},
// adjust tab position
onScroll(scrollTop) {
if (!this.data.sticky) return;
const { offsetTop } = this.data;
this.getRect('.van-tabs').then(rect => {
const { top, height } = rect;
this.getRect('.van-tabs__wrap').then(rect => {
const { height: wrapHeight } = rect;
let position = '';
if (offsetTop > top + height - wrapHeight) {
position = 'bottom';
} else if (offsetTop > top) {
position = 'top';
}
this.$emit('scroll', {
scrollTop: scrollTop + offsetTop,
isFixed: position === 'top'
});
if (position !== this.data.position) {
this.setData({
position
}, () => {
this.setWrapStyle();
});
}
});
});
}
}
});

View File

@ -1,5 +1,5 @@
<view class="custom-class van-tabs van-tabs--{{ type }}">
<view style="z-index: {{ zIndex }}" class="van-tabs__wrap {{ scrollable ? 'van-tabs__wrap--scrollable' : '' }} {{ type === 'line' && border ? 'van-hairline--top-bottom' : '' }}">
<view style="z-index: {{ zIndex }}; {{ wrapStyle }}" class="van-tabs__wrap {{ scrollable ? 'van-tabs__wrap--scrollable' : '' }} {{ type === 'line' && border ? 'van-hairline--top-bottom' : '' }}">
<scroll-view
scroll-x="{{ scrollable }}"
scroll-with-animation
@ -17,12 +17,20 @@
style="{{ color && index !== active && type === 'card' && !item.disabled ? 'color: ' + color : '' }} {{ color && index === active && type === 'card' ? ';background-color:' + color : '' }} {{ color ? ';border-color: ' + color : '' }} {{ scrollable ? ';flex-basis:' + (88 / swipeThreshold) + '%' : '' }}"
bind:tap="onTap"
>
{{ item.title }}
<view class="van-ellipsis van-tab__title">
{{ item.title }}
</view>
</view>
</view>
</scroll-view>
</view>
<view class="van-tabs__content">
<view
class="van-tabs__content"
bind:touchstart="onTouchStart"
bind:touchmove="onTouchMove"
bind:touchend="onTouchEnd"
bind:touchcancel="onTouchEnd"
>
<view class="van-tabs__track" style="{{ trackStyle }}">
<slot />
</view>