mirror of
https://gitee.com/vant-contrib/vant.git
synced 2025-04-06 03:57:59 +08:00
feat(Tab): add swipe animation #1174
This commit is contained in:
parent
812a35079c
commit
1376d6f3f7
@ -367,6 +367,7 @@ export default createComponent({
|
|||||||
useExpose({
|
useExpose({
|
||||||
prev,
|
prev,
|
||||||
next,
|
next,
|
||||||
|
state,
|
||||||
resize,
|
resize,
|
||||||
swipeTo,
|
swipeTo,
|
||||||
});
|
});
|
||||||
|
@ -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>
|
||||||
);
|
);
|
||||||
|
@ -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>
|
||||||
);
|
);
|
||||||
};
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
@ -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}
|
||||||
>
|
>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user