[new feature] Tabs: support switch tabs with swipe gestrue in the content (#694)

This commit is contained in:
张敏 2018-03-15 10:17:51 +08:00 committed by neverland
parent f13a0779fd
commit 5cdf3b1d79
8 changed files with 137 additions and 8 deletions

View File

@ -58,6 +58,14 @@
</van-tab> </van-tab>
</van-tabs> </van-tabs>
</demo-block> </demo-block>
<demo-block :title="$t('title8')">
<van-tabs :active="active" swipeable>
<van-tab :title="$t('tab') + index" v-for="index in tabs" :key="index">
{{ $t('content') }} {{ index }}
</van-tab>
</van-tabs>
</demo-block>
</demo-section> </demo-section>
</template> </template>
@ -71,7 +79,8 @@ export default {
title4: '样式风格', title4: '样式风格',
title5: '点击事件', title5: '点击事件',
title6: '粘性布局', title6: '粘性布局',
title7: '自定义标签' title7: '自定义标签',
title8: '滑动切换'
}, },
'en-US': { 'en-US': {
tab: 'Tab ', tab: 'Tab ',
@ -81,7 +90,8 @@ export default {
title4: 'Card Style', title4: 'Card Style',
title5: 'Click Event', title5: 'Click Event',
title6: 'Sticky', title6: 'Sticky',
title7: 'Custom Tab' title7: 'Custom Tab',
title8: 'Swipeable'
} }
}, },

View File

@ -124,6 +124,18 @@ Use title slot to custom tab title
</van-tabs> </van-tabs>
``` ```
#### Swipeable
In swipeable mode, you can switch tabs with swipe gestrue in the content
```html
<van-tabs :active="active" swipeable>
<van-tab v-for="index in 4" :title="'tab ' + index">
content {{ index }}
</van-tab>
</van-tabs>
```
### Tabs API ### Tabs API
| Attribute | Description | Type | Default | Accepted Values | | Attribute | Description | Type | Default | Accepted Values |
@ -132,6 +144,8 @@ Use title slot to custom tab title
| active | Index of active tab | `String` `Number` | `0` | - | | active | Index of active tab | `String` `Number` | `0` | - |
| duration | Toggle tab's animation time | `Number` | `0.2` | - | - | | duration | Toggle tab's animation time | `Number` | `0.2` | - | - |
| swipe-threshold | Set swipe tabs threshold | `Number` | `4` | - | - | | swipe-threshold | Set swipe tabs threshold | `Number` | `4` | - | - |
| sticky | Whether to use sticky mode | `Boolean` | `false` | - |
| swipeable | Whether to switch tabs with swipe gestrue in the content | `Boolean` | `false` | - |
### Tab API ### Tab API

View File

@ -124,6 +124,18 @@ export default {
</van-tabs> </van-tabs>
``` ```
#### 滑动切换
通过`swipeable`属性可以开启滑动切换tab
```html
<van-tabs :active="active" swipeable>
<van-tab v-for="index in 4" :title="'选项 ' + index">
内容 {{ index }}
</van-tab>
</van-tabs>
```
### Tabs API ### Tabs API
| 参数 | 说明 | 类型 | 默认值 | 可选 | | 参数 | 说明 | 类型 | 默认值 | 可选 |
@ -133,6 +145,7 @@ export default {
| duration | 切换 tab 的动画时间 | `Number` | `0.2` | - | | duration | 切换 tab 的动画时间 | `Number` | `0.2` | - |
| swipe-threshold | 滚动阀值,设置 Tab 超过多少个可滚动 | `Number` | `4` | - | | swipe-threshold | 滚动阀值,设置 Tab 超过多少个可滚动 | `Number` | `4` | - |
| sticky | 是否使用粘性定位布局 | `Boolean` | `false` | - | | sticky | 是否使用粘性定位布局 | `Boolean` | `false` | - |
| swipeable | 是否可以滑动内容切换 | `Boolean` | `false` | - |
### Tab API ### Tab API

View File

@ -103,6 +103,7 @@ export default create({
onInput(event) { onInput(event) {
const { value } = event.target; const { value } = event.target;
this.currentValue = value ? this.correctValue(+value) : value; this.currentValue = value ? this.correctValue(+value) : value;
event.target.value = this.currentValue;
this.emitInput(); this.emitInput();
}, },

View File

@ -26,7 +26,7 @@
</div> </div>
</div> </div>
</div> </div>
<div class="van-tabs__content"> <div class="van-tabs__content" ref="content">
<slot /> <slot />
</div> </div>
</div> </div>
@ -63,7 +63,8 @@ export default create({
swipeThreshold: { swipeThreshold: {
type: Number, type: Number,
default: 4 default: 4
} },
swipeable: Boolean
}, },
data() { data() {
@ -71,7 +72,11 @@ export default create({
tabs: [], tabs: [],
position: 'content-top', position: 'content-top',
curActive: 0, curActive: 0,
navBarStyle: {} navBarStyle: {},
pos: {
x: 0,
y: 0
}
}; };
}, },
@ -115,6 +120,9 @@ export default create({
if (this.sticky) { if (this.sticky) {
this.scrollHandler(true); this.scrollHandler(true);
} }
if (this.swipeable) {
this.swipeableHandler(true);
}
this.scrollIntoView(); this.scrollIntoView();
}); });
}, },
@ -124,6 +132,10 @@ export default create({
if (this.sticky) { if (this.sticky) {
this.scrollHandler(false); this.scrollHandler(false);
} }
/* istanbul ignore next */
if (this.swipeable) {
this.swipeableHandler(false);
}
}, },
methods: { methods: {
@ -136,6 +148,46 @@ export default create({
} }
}, },
// whether to bind content swipe listener
swipeableHandler(init) {
const swipeableEl = this.$refs.content;
this.touchMoveHandler = scrollUtils.debounce(this.watchTouchMove.bind(this), 500);
(init ? on : off)(swipeableEl, 'touchstart', this.recordTouchStartPosition, true);
(init ? on : off)(swipeableEl, 'touchmove', this.touchMoveHandler, true);
},
// record swipe touch start position
recordTouchStartPosition(e) {
this.pos = {
x: e.touches[0].clientX,
y: e.touches[0].clientY
};
},
// watch swipe touch move
watchTouchMove(e) {
const { pos } = this;
const dx = e.touches[0].clientX - pos.x;
const dy = e.touches[0].clientY - pos.y;
const isForward = dx > 0;
const isHorizontal = Math.abs(dy) < Math.abs(dx);
const minSwipeDistance = 50;
/* istanbul ignore else */
if (isHorizontal && Math.abs(dx) >= minSwipeDistance) {
const active = +this.curActive;
/* istanbul ignore else */
if (isForward && active !== 0) {
this.curActive = active - 1;
} else if (!isForward && active !== this.tabs.length - 1) {
this.curActive = active + 1;
}
}
},
// adjust tab position // adjust tab position
onScroll() { onScroll() {
const scrollTop = scrollUtils.getScrollTop(this.scrollEl); const scrollTop = scrollUtils.getScrollTop(this.scrollEl);

Binary file not shown.

View File

@ -1,5 +1,12 @@
<template> <template>
<van-tabs :active="active" :duration="duration" @click="handleTabClick" @disabled="handleTabDisabledClick" :sticky="sticky"> <van-tabs
:active="active"
:duration="duration"
:sticky="sticky"
:swipeable="swipeable"
@click="handleTabClick"
@disabled="handleTabDisabledClick"
>
<van-tab :title="firstTabTitle" :disabled="firstTabDisabled">内容一</van-tab> <van-tab :title="firstTabTitle" :disabled="firstTabDisabled">内容一</van-tab>
<van-tab title="选项二">内容二</van-tab> <van-tab title="选项二">内容二</van-tab>
<van-tab title="选项三" disabled>内容三</van-tab> <van-tab title="选项三" disabled>内容三</van-tab>
@ -17,7 +24,8 @@ export default {
firstTabDisabled: { firstTabDisabled: {
type: Boolean type: Boolean
}, },
sticky: Boolean sticky: Boolean,
swipeable: Boolean
}, },
data() { data() {

View File

@ -2,6 +2,7 @@ import Tabs from 'packages/tabs';
import { mount } from 'avoriaz'; import { mount } from 'avoriaz';
import TabsTestComponent from '../components/tabs'; import TabsTestComponent from '../components/tabs';
import MoreTabsTestComponent from '../components/more-tabs'; import MoreTabsTestComponent from '../components/more-tabs';
import { triggerTouch } from '../utils';
describe('Tabs', () => { describe('Tabs', () => {
let wrapper; let wrapper;
@ -114,7 +115,7 @@ describe('Tabs', () => {
}); });
}); });
it('sticky', (done) => { it('create a sticky tabs', (done) => {
wrapper = mount(TabsTestComponent, { wrapper = mount(TabsTestComponent, {
attachToDocument: true, attachToDocument: true,
propsData: { propsData: {
@ -129,4 +130,34 @@ describe('Tabs', () => {
done(); done();
}, 30); }, 30);
}); });
it('create a swipeable tabs', (done) => {
wrapper = mount(TabsTestComponent, {
attachToDocument: true,
propsData: {
swipeable: true
}
});
const tabsContainer = wrapper.find('.van-tabs')[0];
const tabContent = wrapper.find('.van-tabs__content')[0];
expect(tabsContainer.vNode.child.curActive).to.equal(0);
wrapper.vm.$nextTick(() => {
triggerTouch(tabContent, 'touchstart', 0, 0);
triggerTouch(tabContent, 'touchmove', -100, 0);
setTimeout(() => {
expect(tabsContainer.vNode.child.curActive).to.equal(1);
triggerTouch(tabContent, 'touchstart', 0, 0);
triggerTouch(tabContent, 'touchmove', 100, 0);
setTimeout(() => {
expect(tabsContainer.vNode.child.curActive).to.equal(0);
done();
}, 500);
}, 500);
})
});
}); });