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({ useExpose({
prev, prev,
next, next,
state,
resize, resize,
swipeTo, swipeTo,
}); });

View File

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

View File

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

View File

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