@veltra/compositions 1.3.4 → 1.3.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -5,11 +5,11 @@ interface TransitionBase {
|
|
|
5
5
|
target: ShallowRef<HTMLElement | undefined> | HTMLElement;
|
|
6
6
|
/** 进入动画结束回调 */
|
|
7
7
|
afterEnter?: () => void;
|
|
8
|
-
/**
|
|
8
|
+
/** 进入动画被取消回调(css 实现中阶段被打断时由新阶段直接接管,不会触发) */
|
|
9
9
|
enterCanceled?: () => void;
|
|
10
10
|
/** 离开动画结束回调 */
|
|
11
11
|
afterLeave?: () => void;
|
|
12
|
-
/**
|
|
12
|
+
/** 离开动画被取消回调(css 实现中阶段被打断时由新阶段直接接管,不会触发) */
|
|
13
13
|
leaveCanceled?: () => void;
|
|
14
14
|
}
|
|
15
15
|
interface CssTransitionOptions extends TransitionBase {
|
|
@@ -1,19 +1,23 @@
|
|
|
1
1
|
import { createToggle } from "@veltra/utils";
|
|
2
|
-
import { computed, isRef, onBeforeUnmount
|
|
2
|
+
import { computed, isRef, onBeforeUnmount } from "vue";
|
|
3
3
|
//#region src/use-transition/use-css-transition.ts
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
if (el._count <= 0) delete el._count;
|
|
4
|
+
/** 解析 CSS 时间值(s / ms)为毫秒 */
|
|
5
|
+
const parseTime = (time) => {
|
|
6
|
+
const t = time.trim();
|
|
7
|
+
if (t.endsWith("ms")) return parseFloat(t);
|
|
8
|
+
return parseFloat(t) * 1e3;
|
|
10
9
|
};
|
|
11
10
|
/**
|
|
12
11
|
* 使用css过渡
|
|
12
|
+
*
|
|
13
|
+
* 阶段结束不依赖 transitionend 事件:进入/离开相互打断时类共存会导致
|
|
14
|
+
* 过渡不触发、end 事件丢失从而卡死。这里每个阶段开始时清理相反阶段的类,
|
|
15
|
+
* 并以「计算过渡耗时 + 定时器」兜底,保证阶段必然终结。
|
|
16
|
+
*
|
|
13
17
|
* @param options 过渡选项
|
|
14
18
|
*/
|
|
15
19
|
function useCssTransition(options) {
|
|
16
|
-
const { name, target, afterEnter, afterLeave,
|
|
20
|
+
const { name, target, afterEnter, afterLeave, keepEnterTo = false } = options;
|
|
17
21
|
const getDom = () => isRef(target) ? target.value : target;
|
|
18
22
|
const classes = computed(() => {
|
|
19
23
|
const _name = typeof name === "string" ? name : name.value;
|
|
@@ -32,93 +36,84 @@ function useCssTransition(options) {
|
|
|
32
36
|
leaveActive: `${_name}-leave-active`
|
|
33
37
|
};
|
|
34
38
|
});
|
|
39
|
+
/** 阶段序号,使过期的 rAF / 定时器回调失效 */
|
|
40
|
+
let phaseSeq = 0;
|
|
41
|
+
let finalizeTimer;
|
|
42
|
+
const clearFinalizeTimer = () => {
|
|
43
|
+
if (finalizeTimer === void 0) return;
|
|
44
|
+
clearTimeout(finalizeTimer);
|
|
45
|
+
finalizeTimer = void 0;
|
|
46
|
+
};
|
|
47
|
+
/** 目标元素当前应用的最长过渡耗时(时长 + 延迟),无过渡则为 0 */
|
|
48
|
+
const getTransitionDuration = (dom) => {
|
|
49
|
+
const { transitionDuration, transitionDelay } = getComputedStyle(dom);
|
|
50
|
+
const durations = transitionDuration.split(",").map(parseTime);
|
|
51
|
+
const delays = transitionDelay.split(",").map(parseTime);
|
|
52
|
+
return durations.reduce((max, d, i) => {
|
|
53
|
+
return Math.max(max, d + (delays[i] ?? delays[delays.length - 1] ?? 0));
|
|
54
|
+
}, 0);
|
|
55
|
+
};
|
|
56
|
+
/** 阶段完成:清理类并触发回调,仅当前阶段的定时器生效 */
|
|
57
|
+
const finalize = (seq, entering) => {
|
|
58
|
+
if (seq !== phaseSeq) return;
|
|
59
|
+
const dom = getDom();
|
|
60
|
+
const { enterActive, enterTo, leaveActive, leaveTo } = classes.value;
|
|
61
|
+
if (entering) {
|
|
62
|
+
if (keepEnterTo) dom?.classList.remove(enterActive);
|
|
63
|
+
else dom?.classList.remove(enterActive, enterTo);
|
|
64
|
+
afterEnter?.();
|
|
65
|
+
} else {
|
|
66
|
+
dom?.classList.remove(leaveActive, leaveTo);
|
|
67
|
+
afterLeave?.();
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
/** 过渡开始后调度兜底完成(晚 50ms 容错) */
|
|
71
|
+
const scheduleFinalize = (seq, entering) => {
|
|
72
|
+
clearFinalizeTimer();
|
|
73
|
+
const dom = getDom();
|
|
74
|
+
const duration = dom ? getTransitionDuration(dom) : 0;
|
|
75
|
+
finalizeTimer = setTimeout(() => finalize(seq, entering), duration + 50);
|
|
76
|
+
};
|
|
35
77
|
/** 开始进入动画 */
|
|
36
78
|
const startTransitionIn = () => {
|
|
37
|
-
const
|
|
79
|
+
const seq = ++phaseSeq;
|
|
80
|
+
const { enterActive, enterTo, enterFrom, leaveFrom, leaveActive, leaveTo } = classes.value;
|
|
38
81
|
const dom = getDom();
|
|
82
|
+
dom?.classList.remove(leaveFrom, leaveActive, leaveTo);
|
|
39
83
|
dom?.classList.add(enterFrom);
|
|
40
84
|
requestAnimationFrame(() => {
|
|
85
|
+
if (seq !== phaseSeq) return;
|
|
41
86
|
dom?.classList.add(enterActive);
|
|
42
87
|
requestAnimationFrame(() => {
|
|
88
|
+
if (seq !== phaseSeq) return;
|
|
43
89
|
dom?.classList.remove(enterFrom);
|
|
44
90
|
dom?.classList.add(enterTo);
|
|
91
|
+
scheduleFinalize(seq, true);
|
|
45
92
|
});
|
|
46
93
|
});
|
|
47
94
|
};
|
|
48
95
|
/** 开始离开动画 */
|
|
49
96
|
const startTransitionOut = () => {
|
|
50
|
-
const
|
|
97
|
+
const seq = ++phaseSeq;
|
|
98
|
+
const { leaveTo, leaveActive, leaveFrom, enterFrom, enterActive, enterTo } = classes.value;
|
|
51
99
|
const dom = getDom();
|
|
52
|
-
|
|
100
|
+
dom?.classList.remove(enterFrom, enterActive, enterTo);
|
|
53
101
|
dom?.classList.add(leaveFrom, leaveActive);
|
|
54
102
|
requestAnimationFrame(() => {
|
|
55
|
-
|
|
103
|
+
if (seq !== phaseSeq) return;
|
|
56
104
|
requestAnimationFrame(() => {
|
|
105
|
+
if (seq !== phaseSeq) return;
|
|
57
106
|
dom?.classList.remove(leaveFrom);
|
|
58
107
|
dom?.classList.add(leaveTo);
|
|
108
|
+
scheduleFinalize(seq, false);
|
|
59
109
|
});
|
|
60
110
|
});
|
|
61
111
|
};
|
|
62
|
-
const [
|
|
112
|
+
const [, toggle] = createToggle(false, (_active) => {
|
|
63
113
|
_active ? startTransitionIn() : startTransitionOut();
|
|
64
114
|
});
|
|
65
|
-
const transitionEndHandler = (e) => {
|
|
66
|
-
e.stopPropagation();
|
|
67
|
-
const { leaveActive, enterActive, enterTo, leaveTo } = classes.value;
|
|
68
|
-
const dom = getDom();
|
|
69
|
-
if (dom !== e.target) return;
|
|
70
|
-
decreaseTransitionCount(dom);
|
|
71
|
-
if (dom._count) return;
|
|
72
|
-
if (active.value) {
|
|
73
|
-
if (keepEnterTo) dom?.classList.remove(enterActive);
|
|
74
|
-
else dom?.classList.remove(enterActive, enterTo);
|
|
75
|
-
afterEnter?.();
|
|
76
|
-
} else {
|
|
77
|
-
dom?.classList.remove(leaveActive, leaveTo);
|
|
78
|
-
afterLeave?.();
|
|
79
|
-
}
|
|
80
|
-
};
|
|
81
|
-
const transitionRunHandler = (e) => {
|
|
82
|
-
e.stopPropagation();
|
|
83
|
-
const dom = getDom();
|
|
84
|
-
if (dom !== e.target) return;
|
|
85
|
-
increaseTransitionCount(dom);
|
|
86
|
-
};
|
|
87
|
-
const transitionCancelHandler = (e) => {
|
|
88
|
-
e.stopPropagation();
|
|
89
|
-
const dom = getDom();
|
|
90
|
-
if (dom !== e.target) return;
|
|
91
|
-
decreaseTransitionCount(dom);
|
|
92
|
-
if (dom._count) return;
|
|
93
|
-
const { leaveActive, enterActive } = classes.value;
|
|
94
|
-
if (active.value) {
|
|
95
|
-
dom?.classList.remove(enterActive);
|
|
96
|
-
enterCanceled?.();
|
|
97
|
-
} else {
|
|
98
|
-
dom?.classList.remove(leaveActive);
|
|
99
|
-
leaveCanceled?.();
|
|
100
|
-
}
|
|
101
|
-
};
|
|
102
|
-
/** 添加事件 */
|
|
103
|
-
const addEvent = (el) => {
|
|
104
|
-
el?.addEventListener("transitioncancel", transitionCancelHandler);
|
|
105
|
-
el?.addEventListener("transitionend", transitionEndHandler);
|
|
106
|
-
el?.addEventListener("transitionrun", transitionRunHandler);
|
|
107
|
-
};
|
|
108
|
-
/** 移除事件 */
|
|
109
|
-
const removeEvent = (el) => {
|
|
110
|
-
el?.removeEventListener("transitioncancel", transitionCancelHandler);
|
|
111
|
-
el?.removeEventListener("transitionend", transitionEndHandler);
|
|
112
|
-
el?.removeEventListener("transitionrun", transitionRunHandler);
|
|
113
|
-
};
|
|
114
|
-
if (isRef(target)) watch(target, (_target, oldTarget) => {
|
|
115
|
-
if (oldTarget) removeEvent(oldTarget);
|
|
116
|
-
_target && addEvent(_target);
|
|
117
|
-
});
|
|
118
|
-
else if (target) addEvent(target);
|
|
119
115
|
onBeforeUnmount(() => {
|
|
120
|
-
|
|
121
|
-
removeEvent(dom);
|
|
116
|
+
clearFinalizeTimer();
|
|
122
117
|
});
|
|
123
118
|
return {
|
|
124
119
|
toggle,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-css-transition.js","names":[],"sources":["../../src/use-transition/use-css-transition.ts"],"sourcesContent":["import { createToggle } from '@veltra/utils'\nimport { isRef,
|
|
1
|
+
{"version":3,"file":"use-css-transition.js","names":[],"sources":["../../src/use-transition/use-css-transition.ts"],"sourcesContent":["import { createToggle } from '@veltra/utils'\nimport { isRef, onBeforeUnmount, computed } from 'vue'\n\nimport type { Returned, CssTransitionOptions } from './type'\n\n/** 解析 CSS 时间值(s / ms)为毫秒 */\nconst parseTime = (time: string): number => {\n const t = time.trim()\n if (t.endsWith('ms')) return parseFloat(t)\n return parseFloat(t) * 1000\n}\n\n/**\n * 使用css过渡\n *\n * 阶段结束不依赖 transitionend 事件:进入/离开相互打断时类共存会导致\n * 过渡不触发、end 事件丢失从而卡死。这里每个阶段开始时清理相反阶段的类,\n * 并以「计算过渡耗时 + 定时器」兜底,保证阶段必然终结。\n *\n * @param options 过渡选项\n */\nexport function useCssTransition(options: CssTransitionOptions): Returned {\n const { name, target, afterEnter, afterLeave, keepEnterTo = false } = options\n\n const getDom = (): HTMLElement | undefined => (isRef(target) ? target.value : target)\n\n const classes = computed(() => {\n const _name = typeof name === 'string' ? name : name.value\n return {\n /** 进入前的类 */\n enterFrom: `${_name}-enter-from`,\n /** 进入后最终的类 */\n enterTo: `${_name}-enter-to`,\n /** 【进入动画】持续时的类 */\n enterActive: `${_name}-enter-active`,\n /** 离开前的类 */\n leaveFrom: `${_name}-leave-from`,\n /** 离开类 */\n leaveTo: `${_name}-leave-to`,\n /** 【离开动画】持续时的类 */\n leaveActive: `${_name}-leave-active`\n }\n })\n\n /** 阶段序号,使过期的 rAF / 定时器回调失效 */\n let phaseSeq = 0\n\n let finalizeTimer: ReturnType<typeof setTimeout> | undefined\n\n const clearFinalizeTimer = () => {\n if (finalizeTimer === undefined) return\n clearTimeout(finalizeTimer)\n finalizeTimer = undefined\n }\n\n /** 目标元素当前应用的最长过渡耗时(时长 + 延迟),无过渡则为 0 */\n const getTransitionDuration = (dom: HTMLElement): number => {\n const { transitionDuration, transitionDelay } = getComputedStyle(dom)\n const durations = transitionDuration.split(',').map(parseTime)\n const delays = transitionDelay.split(',').map(parseTime)\n return durations.reduce((max, d, i) => {\n return Math.max(max, d + (delays[i] ?? delays[delays.length - 1] ?? 0))\n }, 0)\n }\n\n /** 阶段完成:清理类并触发回调,仅当前阶段的定时器生效 */\n const finalize = (seq: number, entering: boolean) => {\n if (seq !== phaseSeq) return\n\n const dom = getDom()\n const { enterActive, enterTo, leaveActive, leaveTo } = classes.value\n\n if (entering) {\n if (keepEnterTo) {\n dom?.classList.remove(enterActive)\n } else {\n dom?.classList.remove(enterActive, enterTo)\n }\n afterEnter?.()\n } else {\n dom?.classList.remove(leaveActive, leaveTo)\n afterLeave?.()\n }\n }\n\n /** 过渡开始后调度兜底完成(晚 50ms 容错) */\n const scheduleFinalize = (seq: number, entering: boolean) => {\n clearFinalizeTimer()\n const dom = getDom()\n const duration = dom ? getTransitionDuration(dom) : 0\n finalizeTimer = setTimeout(() => finalize(seq, entering), duration + 50)\n }\n\n /** 开始进入动画 */\n const startTransitionIn = () => {\n const seq = ++phaseSeq\n const { enterActive, enterTo, enterFrom, leaveFrom, leaveActive, leaveTo } = classes.value\n const dom = getDom()\n\n // 清理离开阶段的残留类,避免与进入类共存导致过渡失效\n dom?.classList.remove(leaveFrom, leaveActive, leaveTo)\n dom?.classList.add(enterFrom)\n\n requestAnimationFrame(() => {\n if (seq !== phaseSeq) return\n dom?.classList.add(enterActive)\n requestAnimationFrame(() => {\n if (seq !== phaseSeq) return\n dom?.classList.remove(enterFrom)\n dom?.classList.add(enterTo)\n scheduleFinalize(seq, true)\n })\n })\n }\n\n /** 开始离开动画 */\n const startTransitionOut = () => {\n const seq = ++phaseSeq\n const { leaveTo, leaveActive, leaveFrom, enterFrom, enterActive, enterTo } = classes.value\n const dom = getDom()\n\n // 清理进入阶段的残留类,避免与离开类共存导致过渡失效\n dom?.classList.remove(enterFrom, enterActive, enterTo)\n dom?.classList.add(leaveFrom, leaveActive)\n\n requestAnimationFrame(() => {\n if (seq !== phaseSeq) return\n requestAnimationFrame(() => {\n if (seq !== phaseSeq) return\n dom?.classList.remove(leaveFrom)\n dom?.classList.add(leaveTo)\n scheduleFinalize(seq, false)\n })\n })\n }\n\n const [, toggle] = createToggle(false, (_active) => {\n _active ? startTransitionIn() : startTransitionOut()\n })\n\n onBeforeUnmount(() => {\n clearFinalizeTimer()\n })\n\n return {\n toggle,\n enter() {\n toggle(true)\n },\n leave() {\n toggle(false)\n }\n }\n}\n"],"mappings":";;;;AAMA,MAAM,aAAa,SAAyB;CAC1C,MAAM,IAAI,KAAK,KAAK;CACpB,IAAI,EAAE,SAAS,IAAI,GAAG,OAAO,WAAW,CAAC;CACzC,OAAO,WAAW,CAAC,IAAI;AACzB;;;;;;;;;;AAWA,SAAgB,iBAAiB,SAAyC;CACxE,MAAM,EAAE,MAAM,QAAQ,YAAY,YAAY,cAAc,UAAU;CAEtE,MAAM,eAAyC,MAAM,MAAM,IAAI,OAAO,QAAQ;CAE9E,MAAM,UAAU,eAAe;EAC7B,MAAM,QAAQ,OAAO,SAAS,WAAW,OAAO,KAAK;EACrD,OAAO;;GAEL,WAAW,GAAG,MAAM;;GAEpB,SAAS,GAAG,MAAM;;GAElB,aAAa,GAAG,MAAM;;GAEtB,WAAW,GAAG,MAAM;;GAEpB,SAAS,GAAG,MAAM;;GAElB,aAAa,GAAG,MAAM;EACxB;CACF,CAAC;;CAGD,IAAI,WAAW;CAEf,IAAI;CAEJ,MAAM,2BAA2B;EAC/B,IAAI,kBAAkB,KAAA,GAAW;EACjC,aAAa,aAAa;EAC1B,gBAAgB,KAAA;CAClB;;CAGA,MAAM,yBAAyB,QAA6B;EAC1D,MAAM,EAAE,oBAAoB,oBAAoB,iBAAiB,GAAG;EACpE,MAAM,YAAY,mBAAmB,MAAM,GAAG,CAAC,CAAC,IAAI,SAAS;EAC7D,MAAM,SAAS,gBAAgB,MAAM,GAAG,CAAC,CAAC,IAAI,SAAS;EACvD,OAAO,UAAU,QAAQ,KAAK,GAAG,MAAM;GACrC,OAAO,KAAK,IAAI,KAAK,KAAK,OAAO,MAAM,OAAO,OAAO,SAAS,MAAM,EAAE;EACxE,GAAG,CAAC;CACN;;CAGA,MAAM,YAAY,KAAa,aAAsB;EACnD,IAAI,QAAQ,UAAU;EAEtB,MAAM,MAAM,OAAO;EACnB,MAAM,EAAE,aAAa,SAAS,aAAa,YAAY,QAAQ;EAE/D,IAAI,UAAU;GACZ,IAAI,aACF,KAAK,UAAU,OAAO,WAAW;QAEjC,KAAK,UAAU,OAAO,aAAa,OAAO;GAE5C,aAAa;EACf,OAAO;GACL,KAAK,UAAU,OAAO,aAAa,OAAO;GAC1C,aAAa;EACf;CACF;;CAGA,MAAM,oBAAoB,KAAa,aAAsB;EAC3D,mBAAmB;EACnB,MAAM,MAAM,OAAO;EACnB,MAAM,WAAW,MAAM,sBAAsB,GAAG,IAAI;EACpD,gBAAgB,iBAAiB,SAAS,KAAK,QAAQ,GAAG,WAAW,EAAE;CACzE;;CAGA,MAAM,0BAA0B;EAC9B,MAAM,MAAM,EAAE;EACd,MAAM,EAAE,aAAa,SAAS,WAAW,WAAW,aAAa,YAAY,QAAQ;EACrF,MAAM,MAAM,OAAO;EAGnB,KAAK,UAAU,OAAO,WAAW,aAAa,OAAO;EACrD,KAAK,UAAU,IAAI,SAAS;EAE5B,4BAA4B;GAC1B,IAAI,QAAQ,UAAU;GACtB,KAAK,UAAU,IAAI,WAAW;GAC9B,4BAA4B;IAC1B,IAAI,QAAQ,UAAU;IACtB,KAAK,UAAU,OAAO,SAAS;IAC/B,KAAK,UAAU,IAAI,OAAO;IAC1B,iBAAiB,KAAK,IAAI;GAC5B,CAAC;EACH,CAAC;CACH;;CAGA,MAAM,2BAA2B;EAC/B,MAAM,MAAM,EAAE;EACd,MAAM,EAAE,SAAS,aAAa,WAAW,WAAW,aAAa,YAAY,QAAQ;EACrF,MAAM,MAAM,OAAO;EAGnB,KAAK,UAAU,OAAO,WAAW,aAAa,OAAO;EACrD,KAAK,UAAU,IAAI,WAAW,WAAW;EAEzC,4BAA4B;GAC1B,IAAI,QAAQ,UAAU;GACtB,4BAA4B;IAC1B,IAAI,QAAQ,UAAU;IACtB,KAAK,UAAU,OAAO,SAAS;IAC/B,KAAK,UAAU,IAAI,OAAO;IAC1B,iBAAiB,KAAK,KAAK;GAC7B,CAAC;EACH,CAAC;CACH;CAEA,MAAM,GAAG,UAAU,aAAa,QAAQ,YAAY;EAClD,UAAU,kBAAkB,IAAI,mBAAmB;CACrD,CAAC;CAED,sBAAsB;EACpB,mBAAmB;CACrB,CAAC;CAED,OAAO;EACL;EACA,QAAQ;GACN,OAAO,IAAI;EACb;EACA,QAAQ;GACN,OAAO,KAAK;EACd;CACF;AACF"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@veltra/compositions",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.6",
|
|
4
4
|
"files": [
|
|
5
5
|
"dist",
|
|
6
6
|
"src"
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
"peerDependencies": {
|
|
23
23
|
"@cat-kit/core": ">=1.1.8",
|
|
24
24
|
"@cat-kit/fe": ">=1.1.8",
|
|
25
|
-
"@veltra/utils": "1.3.
|
|
25
|
+
"@veltra/utils": "1.3.6",
|
|
26
26
|
"vue": ">=3.5.40"
|
|
27
27
|
}
|
|
28
28
|
}
|
|
@@ -5,11 +5,11 @@ export interface TransitionBase {
|
|
|
5
5
|
target: ShallowRef<HTMLElement | undefined> | HTMLElement
|
|
6
6
|
/** 进入动画结束回调 */
|
|
7
7
|
afterEnter?: () => void
|
|
8
|
-
/**
|
|
8
|
+
/** 进入动画被取消回调(css 实现中阶段被打断时由新阶段直接接管,不会触发) */
|
|
9
9
|
enterCanceled?: () => void
|
|
10
10
|
/** 离开动画结束回调 */
|
|
11
11
|
afterLeave?: () => void
|
|
12
|
-
/**
|
|
12
|
+
/** 离开动画被取消回调(css 实现中阶段被打断时由新阶段直接接管,不会触发) */
|
|
13
13
|
leaveCanceled?: () => void
|
|
14
14
|
}
|
|
15
15
|
|
|
@@ -1,36 +1,28 @@
|
|
|
1
1
|
import { createToggle } from '@veltra/utils'
|
|
2
|
-
import { isRef,
|
|
2
|
+
import { isRef, onBeforeUnmount, computed } from 'vue'
|
|
3
3
|
|
|
4
4
|
import type { Returned, CssTransitionOptions } from './type'
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
el._count = (el._count ?? 0) - 1
|
|
12
|
-
if (el._count <= 0) {
|
|
13
|
-
delete el._count
|
|
14
|
-
}
|
|
6
|
+
/** 解析 CSS 时间值(s / ms)为毫秒 */
|
|
7
|
+
const parseTime = (time: string): number => {
|
|
8
|
+
const t = time.trim()
|
|
9
|
+
if (t.endsWith('ms')) return parseFloat(t)
|
|
10
|
+
return parseFloat(t) * 1000
|
|
15
11
|
}
|
|
16
12
|
|
|
17
13
|
/**
|
|
18
14
|
* 使用css过渡
|
|
15
|
+
*
|
|
16
|
+
* 阶段结束不依赖 transitionend 事件:进入/离开相互打断时类共存会导致
|
|
17
|
+
* 过渡不触发、end 事件丢失从而卡死。这里每个阶段开始时清理相反阶段的类,
|
|
18
|
+
* 并以「计算过渡耗时 + 定时器」兜底,保证阶段必然终结。
|
|
19
|
+
*
|
|
19
20
|
* @param options 过渡选项
|
|
20
21
|
*/
|
|
21
22
|
export function useCssTransition(options: CssTransitionOptions): Returned {
|
|
22
|
-
const {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
afterEnter,
|
|
26
|
-
afterLeave,
|
|
27
|
-
enterCanceled,
|
|
28
|
-
leaveCanceled,
|
|
29
|
-
keepEnterTo = false
|
|
30
|
-
} = options
|
|
31
|
-
|
|
32
|
-
const getDom = (): (HTMLElement & { _count?: number }) | undefined =>
|
|
33
|
-
isRef(target) ? target.value : target
|
|
23
|
+
const { name, target, afterEnter, afterLeave, keepEnterTo = false } = options
|
|
24
|
+
|
|
25
|
+
const getDom = (): HTMLElement | undefined => (isRef(target) ? target.value : target)
|
|
34
26
|
|
|
35
27
|
const classes = computed(() => {
|
|
36
28
|
const _name = typeof name === 'string' ? name : name.value
|
|
@@ -50,60 +42,35 @@ export function useCssTransition(options: CssTransitionOptions): Returned {
|
|
|
50
42
|
}
|
|
51
43
|
})
|
|
52
44
|
|
|
53
|
-
/**
|
|
54
|
-
|
|
55
|
-
const { enterActive, enterTo, enterFrom } = classes.value
|
|
56
|
-
const dom = getDom()
|
|
45
|
+
/** 阶段序号,使过期的 rAF / 定时器回调失效 */
|
|
46
|
+
let phaseSeq = 0
|
|
57
47
|
|
|
58
|
-
|
|
48
|
+
let finalizeTimer: ReturnType<typeof setTimeout> | undefined
|
|
59
49
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
dom?.classList.add(enterTo)
|
|
65
|
-
})
|
|
66
|
-
})
|
|
50
|
+
const clearFinalizeTimer = () => {
|
|
51
|
+
if (finalizeTimer === undefined) return
|
|
52
|
+
clearTimeout(finalizeTimer)
|
|
53
|
+
finalizeTimer = undefined
|
|
67
54
|
}
|
|
68
55
|
|
|
69
|
-
/**
|
|
70
|
-
const
|
|
71
|
-
const {
|
|
72
|
-
const
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
}
|
|
78
|
-
dom?.classList.add(leaveFrom, leaveActive)
|
|
79
|
-
|
|
80
|
-
requestAnimationFrame(() => {
|
|
81
|
-
dom?.classList.add(leaveActive)
|
|
82
|
-
requestAnimationFrame(() => {
|
|
83
|
-
dom?.classList.remove(leaveFrom)
|
|
84
|
-
dom?.classList.add(leaveTo)
|
|
85
|
-
})
|
|
86
|
-
})
|
|
56
|
+
/** 目标元素当前应用的最长过渡耗时(时长 + 延迟),无过渡则为 0 */
|
|
57
|
+
const getTransitionDuration = (dom: HTMLElement): number => {
|
|
58
|
+
const { transitionDuration, transitionDelay } = getComputedStyle(dom)
|
|
59
|
+
const durations = transitionDuration.split(',').map(parseTime)
|
|
60
|
+
const delays = transitionDelay.split(',').map(parseTime)
|
|
61
|
+
return durations.reduce((max, d, i) => {
|
|
62
|
+
return Math.max(max, d + (delays[i] ?? delays[delays.length - 1] ?? 0))
|
|
63
|
+
}, 0)
|
|
87
64
|
}
|
|
88
65
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
66
|
+
/** 阶段完成:清理类并触发回调,仅当前阶段的定时器生效 */
|
|
67
|
+
const finalize = (seq: number, entering: boolean) => {
|
|
68
|
+
if (seq !== phaseSeq) return
|
|
92
69
|
|
|
93
|
-
const transitionEndHandler = (e: TransitionEvent) => {
|
|
94
|
-
e.stopPropagation()
|
|
95
|
-
|
|
96
|
-
const { leaveActive, enterActive, enterTo, leaveTo } = classes.value
|
|
97
70
|
const dom = getDom()
|
|
71
|
+
const { enterActive, enterTo, leaveActive, leaveTo } = classes.value
|
|
98
72
|
|
|
99
|
-
if (
|
|
100
|
-
|
|
101
|
-
decreaseTransitionCount(dom)
|
|
102
|
-
|
|
103
|
-
if (dom._count) return
|
|
104
|
-
|
|
105
|
-
// 激活状态,移除enter-active类
|
|
106
|
-
if (active.value) {
|
|
73
|
+
if (entering) {
|
|
107
74
|
if (keepEnterTo) {
|
|
108
75
|
dom?.classList.remove(enterActive)
|
|
109
76
|
} else {
|
|
@@ -116,62 +83,63 @@ export function useCssTransition(options: CssTransitionOptions): Returned {
|
|
|
116
83
|
}
|
|
117
84
|
}
|
|
118
85
|
|
|
119
|
-
|
|
120
|
-
|
|
86
|
+
/** 过渡开始后调度兜底完成(晚 50ms 容错) */
|
|
87
|
+
const scheduleFinalize = (seq: number, entering: boolean) => {
|
|
88
|
+
clearFinalizeTimer()
|
|
121
89
|
const dom = getDom()
|
|
122
|
-
|
|
123
|
-
|
|
90
|
+
const duration = dom ? getTransitionDuration(dom) : 0
|
|
91
|
+
finalizeTimer = setTimeout(() => finalize(seq, entering), duration + 50)
|
|
124
92
|
}
|
|
125
93
|
|
|
126
|
-
|
|
127
|
-
|
|
94
|
+
/** 开始进入动画 */
|
|
95
|
+
const startTransitionIn = () => {
|
|
96
|
+
const seq = ++phaseSeq
|
|
97
|
+
const { enterActive, enterTo, enterFrom, leaveFrom, leaveActive, leaveTo } = classes.value
|
|
128
98
|
const dom = getDom()
|
|
129
99
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
if (dom._count) return
|
|
134
|
-
|
|
135
|
-
const { leaveActive, enterActive } = classes.value
|
|
100
|
+
// 清理离开阶段的残留类,避免与进入类共存导致过渡失效
|
|
101
|
+
dom?.classList.remove(leaveFrom, leaveActive, leaveTo)
|
|
102
|
+
dom?.classList.add(enterFrom)
|
|
136
103
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
dom?.classList.
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
104
|
+
requestAnimationFrame(() => {
|
|
105
|
+
if (seq !== phaseSeq) return
|
|
106
|
+
dom?.classList.add(enterActive)
|
|
107
|
+
requestAnimationFrame(() => {
|
|
108
|
+
if (seq !== phaseSeq) return
|
|
109
|
+
dom?.classList.remove(enterFrom)
|
|
110
|
+
dom?.classList.add(enterTo)
|
|
111
|
+
scheduleFinalize(seq, true)
|
|
112
|
+
})
|
|
113
|
+
})
|
|
145
114
|
}
|
|
146
115
|
|
|
147
|
-
/**
|
|
148
|
-
const
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
}
|
|
116
|
+
/** 开始离开动画 */
|
|
117
|
+
const startTransitionOut = () => {
|
|
118
|
+
const seq = ++phaseSeq
|
|
119
|
+
const { leaveTo, leaveActive, leaveFrom, enterFrom, enterActive, enterTo } = classes.value
|
|
120
|
+
const dom = getDom()
|
|
153
121
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
el?.removeEventListener('transitionend', transitionEndHandler)
|
|
158
|
-
el?.removeEventListener('transitionrun', transitionRunHandler)
|
|
159
|
-
}
|
|
122
|
+
// 清理进入阶段的残留类,避免与离开类共存导致过渡失效
|
|
123
|
+
dom?.classList.remove(enterFrom, enterActive, enterTo)
|
|
124
|
+
dom?.classList.add(leaveFrom, leaveActive)
|
|
160
125
|
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
126
|
+
requestAnimationFrame(() => {
|
|
127
|
+
if (seq !== phaseSeq) return
|
|
128
|
+
requestAnimationFrame(() => {
|
|
129
|
+
if (seq !== phaseSeq) return
|
|
130
|
+
dom?.classList.remove(leaveFrom)
|
|
131
|
+
dom?.classList.add(leaveTo)
|
|
132
|
+
scheduleFinalize(seq, false)
|
|
133
|
+
})
|
|
167
134
|
})
|
|
168
|
-
} else if (target) {
|
|
169
|
-
addEvent(target)
|
|
170
135
|
}
|
|
171
136
|
|
|
137
|
+
const [, toggle] = createToggle(false, (_active) => {
|
|
138
|
+
_active ? startTransitionIn() : startTransitionOut()
|
|
139
|
+
})
|
|
140
|
+
|
|
172
141
|
onBeforeUnmount(() => {
|
|
173
|
-
|
|
174
|
-
removeEvent(dom)
|
|
142
|
+
clearFinalizeTimer()
|
|
175
143
|
})
|
|
176
144
|
|
|
177
145
|
return {
|