feat(Tab): add swipe animation #1174

This commit is contained in:
chenjiahan 2020-10-03 15:33:21 +08:00
parent 812a35079c
commit 1376d6f3f7
4 changed files with 54 additions and 56 deletions

View File

@ -367,6 +367,7 @@ export default createComponent({
useExpose({
prev,
next,
state,
resize,
swipeTo,
});

View File

@ -6,6 +6,9 @@ import { TABS_KEY } from '../tabs';
import { useParent } from '@vant/use';
import { routeProps } from '../composition/use-route';
// Components
import SwipeItem from '../swipe-item';
const [createComponent, bem] = createNamespace('tab');
export default createComponent({
@ -20,7 +23,6 @@ export default createComponent({
},
setup(props, { slots }) {
const root = ref();
const inited = ref(false);
const { parent, index } = useParent(TABS_KEY);
@ -62,28 +64,27 @@ export default createComponent({
return;
}
const { animated, scrollspy, lazyRender } = parent.props;
const { animated, swipeable, scrollspy, lazyRender } = parent.props;
const active = isActive();
const show = scrollspy || active;
const shouldRender = inited.value || scrollspy || !lazyRender;
const Content = shouldRender ? slots.default?.() : null;
if (animated) {
if (animated || swipeable) {
return (
<div
ref={root}
<SwipeItem
role="tabpanel"
aria-hidden={!active}
class={bem('pane-wrapper', { inactive: !active })}
>
<div class={bem('pane')}>{Content}</div>
</div>
<div class={bem('pane')}>{slots.default?.()}</div>
</SwipeItem>
);
}
const shouldRender = inited.value || scrollspy || !lazyRender;
const Content = shouldRender ? slots.default?.() : null;
return (
<div v-show={show} ref={root} role="tabpanel" class={bem('pane')}>
<div v-show={show} role="tabpanel" class={bem('pane')}>
{Content}
</div>
);

View File

@ -1,14 +1,16 @@
import { ref, watch } from 'vue';
import { createNamespace } from '../utils';
import { useTouch } from '../composition/use-touch';
import Swipe from '../swipe';
const [createComponent, bem] = createNamespace('tabs');
const MIN_SWIPE_DISTANCE = 50;
export default createComponent({
props: {
inited: Boolean,
duration: [Number, String],
animated: Boolean,
swipeable: Boolean,
lazyRender: Boolean,
count: {
type: Number,
required: true,
@ -22,60 +24,52 @@ export default createComponent({
emits: ['change'],
setup(props, { emit, slots }) {
const touch = useTouch();
const swipeRef = ref();
const onTouchEnd = () => {
const { deltaX, offsetX } = touch;
const { currentIndex } = props;
/* istanbul ignore else */
if (touch.isHorizontal() && offsetX.value >= MIN_SWIPE_DISTANCE) {
/* istanbul ignore else */
if (deltaX.value > 0 && currentIndex !== 0) {
emit('change', currentIndex - 1);
} else if (deltaX.value < 0 && currentIndex !== props.count - 1) {
emit('change', currentIndex + 1);
}
}
const onChange = (index: number) => {
emit('change', index);
};
const renderChildren = () => {
const Content = slots.default?.();
if (props.animated) {
const style = {
transform: `translate3d(${-1 * props.currentIndex * 100}%, 0, 0)`,
transitionDuration: `${props.duration}s`,
};
if (props.animated || props.swipeable) {
return (
<div class={bem('track')} style={style}>
<Swipe
ref={swipeRef}
loop={false}
class={bem('track')}
touchable={props.swipeable}
lazyRender={props.lazyRender}
showIndicators={false}
onChange={onChange}
>
{Content}
</div>
</Swipe>
);
}
return Content;
};
return () => {
const listeners = props.swipeable
? {
onTouchstart: touch.start,
onTouchmove: touch.move,
onTouchend: onTouchEnd,
onTouchcancel: onTouchEnd,
}
: null;
watch(
() => props.currentIndex,
(index) => {
const swipe = swipeRef.value;
if (swipe && swipe.state.active !== index) {
swipe.swipeTo(index, { immediate: !props.inited });
}
}
);
return (
<div
class={bem('content', { animated: props.animated })}
{...listeners}
>
{renderChildren()}
</div>
);
};
return () => (
<div
class={bem('content', {
animated: props.animated || props.swipeable,
})}
>
{renderChildren()}
</div>
);
},
});

View File

@ -91,7 +91,6 @@ export default createComponent({
emits: ['click', 'change', 'scroll', 'disabled', 'rendered', 'update:active'],
setup(props, { emit, slots }) {
let inited;
let tabHeight;
let lockScroll;
let stickyFixed;
@ -106,6 +105,7 @@ export default createComponent({
const { children, linkChildren } = useChildren(TABS_KEY);
const state = reactive({
inited: false,
position: '',
currentIndex: -1,
lineStyle: {
@ -159,7 +159,7 @@ export default createComponent({
const init = () => {
nextTick(() => {
inited = true;
state.inited = true;
tabHeight = getVisibleHeight(wrapRef.value);
scrollIntoView(true);
});
@ -167,7 +167,7 @@ export default createComponent({
// update nav bar style
const setLine = () => {
const shouldAnimate = inited;
const shouldAnimate = state.inited;
nextTick(() => {
const titles = titleRefs.value;
@ -436,9 +436,11 @@ export default createComponent({
)}
<TabsContent
count={children.length}
inited={state.inited}
animated={props.animated}
duration={props.duration}
swipeable={props.swipeable}
lazyRender={props.lazyRender}
currentIndex={state.currentIndex}
onChange={setCurrentIndex}
>