refactor: optimize segmented motion thumb rendering and transition
parent
b80bd37f6e
commit
26446f988e
|
@ -1,6 +1,5 @@
|
|||
import { addClass, removeClass } from '../../vc-util/Dom/class';
|
||||
import type { CSSProperties, Ref, TransitionProps } from 'vue';
|
||||
import { onBeforeUnmount, nextTick, Transition, watch, defineComponent, computed, ref } from 'vue';
|
||||
import type { CSSProperties, Ref } from 'vue';
|
||||
import { onBeforeUnmount, nextTick, watch, defineComponent, computed, ref } from 'vue';
|
||||
import { anyType } from '../../_util/type';
|
||||
import type { SegmentedValue } from './segmented';
|
||||
|
||||
|
@ -61,6 +60,37 @@ const MotionThumb = defineComponent({
|
|||
const prevStyle = ref<ThumbRect>(null);
|
||||
const nextStyle = ref<ThumbRect>(null);
|
||||
|
||||
const thumbStart = computed(() =>
|
||||
props.direction === 'rtl'
|
||||
? toPX(-(prevStyle.value?.right as number))
|
||||
: toPX(prevStyle.value?.left as number),
|
||||
);
|
||||
const thumbActive = computed(() =>
|
||||
props.direction === 'rtl'
|
||||
? toPX(-(nextStyle.value?.right as number))
|
||||
: toPX(nextStyle.value?.left as number),
|
||||
);
|
||||
|
||||
const mergedStyle = computed<CSSProperties>(() => ({
|
||||
'--thumb-start-left': thumbStart.value,
|
||||
'--thumb-start-width': toPX(prevStyle.value?.width),
|
||||
'--thumb-active-left': thumbActive.value,
|
||||
'--thumb-active-width': toPX(nextStyle.value?.width),
|
||||
}));
|
||||
|
||||
// 监听过渡结束
|
||||
const onTransitionEnd = (e: TransitionEvent) => {
|
||||
if (e.propertyName !== 'transform' && e.propertyName !== 'width') return;
|
||||
|
||||
if (thumbRef.value) {
|
||||
thumbRef.value.removeEventListener('transitionend', onTransitionEnd);
|
||||
}
|
||||
|
||||
prevStyle.value = null;
|
||||
nextStyle.value = null;
|
||||
emit('motionEnd');
|
||||
};
|
||||
|
||||
watch(
|
||||
() => props.value,
|
||||
(value, prevValue) => {
|
||||
|
@ -73,66 +103,36 @@ const MotionThumb = defineComponent({
|
|||
prevStyle.value = calcPrevStyle;
|
||||
nextStyle.value = calcNextStyle;
|
||||
|
||||
if (prev && next) {
|
||||
emit('motionStart');
|
||||
} else {
|
||||
emit('motionEnd');
|
||||
}
|
||||
nextTick(() => {
|
||||
if (prev && next) {
|
||||
if (thumbRef.value) {
|
||||
// 使用 CSS 变量设置初始位置
|
||||
thumbRef.value.style.transform = `translateX(var(--thumb-start-left))`;
|
||||
thumbRef.value.style.width = `var(--thumb-start-width)`;
|
||||
|
||||
// 强制重排
|
||||
thumbRef.value.offsetHeight;
|
||||
|
||||
thumbRef.value.style.transform = `translateX(var(--thumb-active-left))`;
|
||||
thumbRef.value.style.width = `var(--thumb-active-width)`;
|
||||
|
||||
emit('motionStart');
|
||||
|
||||
thumbRef.value.addEventListener('transitionend', onTransitionEnd);
|
||||
}
|
||||
} else {
|
||||
emit('motionEnd');
|
||||
}
|
||||
});
|
||||
},
|
||||
{ flush: 'post' },
|
||||
);
|
||||
|
||||
const thumbStart = computed(() =>
|
||||
props.direction === 'rtl'
|
||||
? toPX(-(prevStyle.value?.right as number))
|
||||
: toPX(prevStyle.value?.left as number),
|
||||
);
|
||||
const thumbActive = computed(() =>
|
||||
props.direction === 'rtl'
|
||||
? toPX(-(nextStyle.value?.right as number))
|
||||
: toPX(nextStyle.value?.left as number),
|
||||
);
|
||||
|
||||
// =========================== Motion ===========================
|
||||
let timeid: any;
|
||||
const onAppearStart: TransitionProps['onBeforeEnter'] = (el: HTMLDivElement) => {
|
||||
clearTimeout(timeid);
|
||||
nextTick(() => {
|
||||
if (el) {
|
||||
el.style.transform = `translateX(var(--thumb-start-left))`;
|
||||
el.style.width = `var(--thumb-start-width)`;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const onAppearActive: TransitionProps['onEnter'] = (el: HTMLDivElement) => {
|
||||
timeid = setTimeout(() => {
|
||||
if (el) {
|
||||
addClass(el, `${props.motionName}-appear-active`);
|
||||
el.style.transform = `translateX(var(--thumb-active-left))`;
|
||||
el.style.width = `var(--thumb-active-width)`;
|
||||
}
|
||||
});
|
||||
};
|
||||
const onAppearEnd: TransitionProps['onAfterEnter'] = (el: HTMLDivElement) => {
|
||||
prevStyle.value = null;
|
||||
nextStyle.value = null;
|
||||
if (el) {
|
||||
el.style.transform = null;
|
||||
el.style.width = null;
|
||||
removeClass(el, `${props.motionName}-appear-active`);
|
||||
}
|
||||
emit('motionEnd');
|
||||
};
|
||||
const mergedStyle = computed<CSSProperties>(() => ({
|
||||
'--thumb-start-left': thumbStart.value,
|
||||
'--thumb-start-width': toPX(prevStyle.value?.width),
|
||||
'--thumb-active-left': thumbActive.value,
|
||||
'--thumb-active-width': toPX(nextStyle.value?.width),
|
||||
}));
|
||||
// 清理事件监听
|
||||
onBeforeUnmount(() => {
|
||||
clearTimeout(timeid);
|
||||
thumbRef.value?.removeEventListener('transitionend', onTransitionEnd);
|
||||
});
|
||||
|
||||
return () => {
|
||||
// It's little ugly which should be refactor when @umi/test update to latest jsdom
|
||||
const motionProps = {
|
||||
|
@ -145,16 +145,7 @@ const MotionThumb = defineComponent({
|
|||
(motionProps as any)['data-test-style'] = JSON.stringify(mergedStyle.value);
|
||||
}
|
||||
|
||||
return (
|
||||
<Transition
|
||||
appear
|
||||
onBeforeEnter={onAppearStart}
|
||||
onEnter={onAppearActive}
|
||||
onAfterEnter={onAppearEnd}
|
||||
>
|
||||
{!prevStyle.value || !nextStyle.value ? null : <div {...motionProps}></div>}
|
||||
</Transition>
|
||||
);
|
||||
return !prevStyle.value || !nextStyle.value ? null : <div {...motionProps}></div>;
|
||||
};
|
||||
},
|
||||
});
|
||||
|
|
|
@ -144,6 +144,8 @@ const genSegmentedStyle: GenerateStyle<SegmentedToken> = (token: SegmentedToken)
|
|||
height: '100%',
|
||||
padding: `${token.paddingXXS}px 0`,
|
||||
borderRadius: token.borderRadiusSM,
|
||||
transition: `transform ${token.motionDurationSlow} ${token.motionEaseInOut}, width ${token.motionDurationSlow} ${token.motionEaseInOut}`,
|
||||
willChange: 'transform, width',
|
||||
|
||||
[`& ~ ${componentCls}-item:not(${componentCls}-item-selected):not(${componentCls}-item-disabled)::after`]:
|
||||
{
|
||||
|
@ -180,12 +182,6 @@ const genSegmentedStyle: GenerateStyle<SegmentedToken> = (token: SegmentedToken)
|
|||
// disabled styles
|
||||
...getItemDisabledStyle(`&-disabled ${componentCls}-item`, token),
|
||||
...getItemDisabledStyle(`${componentCls}-item-disabled`, token),
|
||||
|
||||
// transition effect when `appear-active`
|
||||
[`${componentCls}-thumb-motion-appear-active`]: {
|
||||
transition: `transform ${token.motionDurationSlow} ${token.motionEaseInOut}, width ${token.motionDurationSlow} ${token.motionEaseInOut}`,
|
||||
willChange: 'transform, width',
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
||||
|
|
Loading…
Reference in New Issue