diff --git a/components/segmented/src/MotionThumb.tsx b/components/segmented/src/MotionThumb.tsx index 2f59f87a0f..6f92d3ccdb 100644 --- a/components/segmented/src/MotionThumb.tsx +++ b/components/segmented/src/MotionThumb.tsx @@ -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,27 +60,6 @@ const MotionThumb = defineComponent({ const prevStyle = ref(null); const nextStyle = ref(null); - watch( - () => props.value, - (value, prevValue) => { - const prev = findValueElement(prevValue); - const next = findValueElement(value); - - const calcPrevStyle = calcThumbStyle(prev); - const calcNextStyle = calcThumbStyle(next); - - prevStyle.value = calcPrevStyle; - nextStyle.value = calcNextStyle; - - if (prev && next) { - emit('motionStart'); - } else { - emit('motionEnd'); - } - }, - { flush: 'post' }, - ); - const thumbStart = computed(() => props.direction === 'rtl' ? toPX(-(prevStyle.value?.right as number)) @@ -93,46 +71,68 @@ const MotionThumb = defineComponent({ : 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(() => ({ '--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) => { + const prev = findValueElement(prevValue); + const next = findValueElement(value); + + const calcPrevStyle = calcThumbStyle(prev); + const calcNextStyle = calcThumbStyle(next); + + prevStyle.value = calcPrevStyle; + nextStyle.value = calcNextStyle; + + 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' }, + ); + + // 清理事件监听 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 ( - - {!prevStyle.value || !nextStyle.value ? null :
} -
- ); + return !prevStyle.value || !nextStyle.value ? null :
; }; }, }); diff --git a/components/segmented/style/index.ts b/components/segmented/style/index.ts index 9235959cd8..db266e7654 100644 --- a/components/segmented/style/index.ts +++ b/components/segmented/style/index.ts @@ -144,6 +144,8 @@ const genSegmentedStyle: GenerateStyle = (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 = (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', - }, }, }; };