Files
2026-01-20 08:04:15 +08:00

207 lines
5.1 KiB
TypeScript

// @ts-nocheck
import { raf } from '@/uni_modules/lime-shared/raf';
// #ifndef UNI-APP-X
import { watchEffect, ref, nextTick } from '@/uni_modules/lime-shared/vue'
// #endif
export type TransitionEmitStatus = "before-enter" | "enter" | "after-enter" | "before-leave" | "leave" | "after-leave"
export type TransitionStatus = '' | 'enter' | 'leave';
export type UseTransitionOptions = {
element ?: Ref<UniElement | null>,
enterClass ?: string,
enterActiveClass ?: string,
enterToClass ?: string,
leaveClass ?: string,
leaveActiveClass ?: string,
leaveToClass ?: string,
appear ?: boolean,
defaultName ?: string,
name ?: () => string,
visible ?: () => boolean,
emits ?: (name : TransitionEmitStatus) => void,
onNextTick ?: (name : TransitionEmitStatus) => Promise<void>,
duration ?: number
}
type ClassNameMap = Map<string, string>;
export type UseTransitionReturn = {
state : Ref<boolean>,
display : Ref<boolean>,
inited : Ref<boolean>,
classes : Ref<string>,
name : Ref<string>,
finished : () => void,
toggle : (v : boolean) => void,
}
export function useTransition(options : UseTransitionOptions) : UseTransitionReturn {
const state = ref(false);
const display = ref(false);
const inited = ref(false);
const classes = ref('');
const name = ref(options.defaultName ?? 'fade');
const enterClass = options.enterClass ?? '';
const enterActiveClass = options.enterActiveClass ?? '';
const enterToClass = options.enterToClass ?? '';
const leaveActiveClass = options.leaveActiveClass ?? '';
const leaveToClass = options.leaveToClass ?? '';
const leaveClass = options.leaveClass ?? '';
const appear = options.appear ?? false
const duration = options.duration ?? 300;
let status : TransitionStatus = '';
let isTransitionEnd = false;
let isTransitioning = false;
let timeoutId = -1
const emitEvent = (event : TransitionEmitStatus) => {
options.emits?.(event);
};
// 结束
const finished = () => {
if (isTransitionEnd) return;
isTransitionEnd = true;
emitEvent(`after-${status}`)
if (display.value && !state.value) {
display.value = false
}
}
const sleep = () : Promise<void> => {
return new Promise((resolve) => {
nextTick(() => {
raf(() => {
// #ifdef APP-ANDROID || APP-IOS || APP-HARMONY
if (options.element?.value != null) {
options.element?.value?.getBoundingClientRectAsync()?.then(res => {
resolve()
})
} else {
resolve()
}
// #endif
// #ifndef APP-ANDROID || APP-IOS || APP-HARMONY
resolve()
// #endif
})
})
})
}
const getClassNames = (name : string) : ClassNameMap => {
return new Map<string, string>([
['enter', `l-${name}-enter l-${name}-enter-active ${enterClass} ${enterActiveClass}`],
['enter-to', `l-${name}-enter-to l-${name}-enter-active ${enterToClass} ${enterActiveClass}`],
['leave', `l-${name}-leave l-${name}-leave-active ${leaveClass} ${leaveActiveClass}`],
['leave-to', `l-${name}-leave-to l-${name}-leave-active ${leaveToClass} ${leaveActiveClass}`]
])
};
const transitionQueue = ref<TransitionStatus[]>([]);
const performTransition = async (newStatus : TransitionStatus, eventName : TransitionStatus) => {
if (status == newStatus) return
transitionQueue.value.push(newStatus);
if (isTransitioning) return;
isTransitioning = true;
// 防止因结束 又切换为开始导致闪烁
isTransitionEnd = true;
while (transitionQueue.value.length > 0) {
const currentStatus = transitionQueue.value.shift()!;
status = currentStatus;
emitEvent(`before-${eventName}`);
await sleep();
await sleep();
if (status != currentStatus) continue;
const classNames = getClassNames(name.value);
inited.value = true;
display.value = true;
classes.value = classNames.get(eventName)!;
emitEvent(eventName);
const executeAfterTick = options.onNextTick?.(eventName);
if (executeAfterTick != null) {
await executeAfterTick;
}
await sleep();
// #ifdef MP
await sleep();
await sleep();
// #endif
if (status != currentStatus) continue;
classes.value = classNames.get(`${eventName}-to`)!;
// isTransitionEnd = false;
// 防止最后无法关闭
if (status == 'leave') {
setTimeout(() => {
finished()
}, duration)
}
}
// 不延迟 会导致快速切换时 因display none 闪烁
clearTimeout(timeoutId)
timeoutId = setTimeout(() => {
if (transitionQueue.value.length == 0 && status == newStatus) {
isTransitionEnd = false;
}
}, duration * 0.8)
isTransitioning = false;
}
// 定义进入过渡的函数
const enter = () => {
performTransition('enter', 'enter');
}
const leave = () => {
performTransition('leave', 'leave');
}
let init = false;
watchEffect(() => {
if (options.visible == null) return
state.value = options.visible!();
if (!appear && !init) {
init = true
return
}
if (state.value) {
enter()
} else {
leave()
}
})
watchEffect(() => {
if (options.name == null) return
name.value = options.name!()
})
const toggle = (v : boolean) => {
state.value = v
if (v) {
enter()
} else {
leave()
}
}
return {
state,
inited,
display,
classes,
name,
finished,
toggle
} as UseTransitionReturn
}