diff --git a/package.json b/package.json index 4f73ed8..b06b2bb 100644 --- a/package.json +++ b/package.json @@ -36,9 +36,11 @@ }, "dependencies": { "tdesign-mobile-vue": "^1.13.2", - "timi-web": "link:E:/WebProject/timi-web", - "vue": "3.5.26", - "vue-router": "4.5.0" + "timi-web": "link:E:/WebProject/timi-web" + }, + "peerDependencies": { + "vue": "^3.5.0", + "vue-router": "^4.5.0" }, "devDependencies": { "@types/node": "^22.13.5", @@ -63,7 +65,9 @@ "vite-plugin-dts": "^4.5.4", "vite-plugin-vue-setup-extend": "^0.4.0", "@vue/tsconfig": "^0.7.0", + "vue": "3.5.26", + "vue-router": "4.5.0", "vite": "7.1.11", - "vue-tsc": "^2.2.10" + "vue-tsc": "^2.2.12" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 47199a4..82ca51b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -14,12 +14,6 @@ importers: timi-web: specifier: link:E:/WebProject/timi-web version: link:../timi-web - vue: - specifier: 3.5.26 - version: 3.5.26(typescript@5.8.3) - vue-router: - specifier: 4.5.0 - version: 4.5.0(vue@3.5.26(typescript@5.8.3)) devDependencies: '@types/node': specifier: ^22.13.5 @@ -90,9 +84,15 @@ importers: vite-plugin-vue-setup-extend: specifier: ^0.4.0 version: 0.4.0(vite@7.1.11(@types/node@22.13.9)(less@4.3.0)(terser@5.46.0)) + vue: + specifier: 3.5.26 + version: 3.5.26(typescript@5.8.3) + vue-router: + specifier: 4.5.0 + version: 4.5.0(vue@3.5.26(typescript@5.8.3)) vue-tsc: - specifier: ^2.2.10 - version: 2.2.10(typescript@5.8.3) + specifier: ^2.2.12 + version: 2.2.12(typescript@5.8.3) packages: @@ -1309,12 +1309,21 @@ packages: '@volar/language-core@2.4.12': resolution: {integrity: sha512-RLrFdXEaQBWfSnYGVxvR2WrO6Bub0unkdHYIdC31HzIEqATIuuhRRzYu76iGPZ6OtA4Au1SnW0ZwIqPP217YhA==} + '@volar/language-core@2.4.15': + resolution: {integrity: sha512-3VHw+QZU0ZG9IuQmzT68IyN4hZNd9GchGPhbD9+pa8CVv7rnoOZwo7T8weIbrRmihqy3ATpdfXFnqRrfPVK6CA==} + '@volar/source-map@2.4.12': resolution: {integrity: sha512-bUFIKvn2U0AWojOaqf63ER0N/iHIBYZPpNGogfLPQ68F5Eet6FnLlyho7BS0y2HJ1jFhSif7AcuTx1TqsCzRzw==} + '@volar/source-map@2.4.15': + resolution: {integrity: sha512-CPbMWlUN6hVZJYGcU/GSoHu4EnCHiLaXI9n8c9la6RaI9W5JHX+NqG+GSQcB0JdC2FIBLdZJwGsfKyBB71VlTg==} + '@volar/typescript@2.4.12': resolution: {integrity: sha512-HJB73OTJDgPc80K30wxi3if4fSsZZAOScbj2fcicMuOPoOkcf9NNAINb33o+DzhBdF9xTKC1gnPmIRDous5S0g==} + '@volar/typescript@2.4.15': + resolution: {integrity: sha512-2aZ8i0cqPGjXb4BhkMsPYDkkuc2ZQ6yOpqwAuNwUoncELqoy5fRgOQtLR9gB0g902iS0NAkvpIzs27geVyVdPg==} + '@vue/babel-helper-vue-jsx-merge-props@1.4.0': resolution: {integrity: sha512-JkqXfCkUDp4PIlFdDQ0TdXoIejMtTHP67/pvxlgeY+u5k3LEdKuWZ3LK6xkxo52uDoABIVyRwqVkfLQJhk7VBA==} @@ -1512,8 +1521,8 @@ packages: typescript: optional: true - '@vue/language-core@2.2.10': - resolution: {integrity: sha512-+yNoYx6XIKuAO8Mqh1vGytu8jkFEOH5C8iOv3i8Z/65A7x9iAOXA97Q+PqZ3nlm2lxf5rOJuIGI/wDtx/riNYw==} + '@vue/language-core@2.2.12': + resolution: {integrity: sha512-IsGljWbKGU1MZpBPN+BvPAdr55YPkj2nB/TBNGNC32Vy2qLG25DYu/NBN2vNtZqdRbTRjaoYrahLrToim2NanA==} peerDependencies: typescript: '*' peerDependenciesMeta: @@ -4772,8 +4781,8 @@ packages: vue-template-es2015-compiler@1.9.1: resolution: {integrity: sha512-4gDntzrifFnCEvyoO8PqyJDmguXgVPxKiIxrBKjIowvL9l+N66196+72XVYR8BBf1Uv1Fgt3bGevJ+sEmxfZzw==} - vue-tsc@2.2.10: - resolution: {integrity: sha512-jWZ1xSaNbabEV3whpIDMbjVSVawjAyW+x1n3JeGQo7S0uv2n9F/JMgWW90tGWNFRKya4YwKMZgCtr0vRAM7DeQ==} + vue-tsc@2.2.12: + resolution: {integrity: sha512-P7OP77b2h/Pmk+lZdJ0YWs+5tJ6J2+uOQPo7tlBnY44QqQSPYvS0qVT4wqDJgwrZaLe47etJLLQRFia71GYITw==} hasBin: true peerDependencies: typescript: '>=5.0.0' @@ -6272,14 +6281,26 @@ snapshots: dependencies: '@volar/source-map': 2.4.12 + '@volar/language-core@2.4.15': + dependencies: + '@volar/source-map': 2.4.15 + '@volar/source-map@2.4.12': {} + '@volar/source-map@2.4.15': {} + '@volar/typescript@2.4.12': dependencies: '@volar/language-core': 2.4.12 path-browserify: 1.0.1 vscode-uri: 3.1.0 + '@volar/typescript@2.4.15': + dependencies: + '@volar/language-core': 2.4.15 + path-browserify: 1.0.1 + vscode-uri: 3.1.0 + '@vue/babel-helper-vue-jsx-merge-props@1.4.0': {} '@vue/babel-helper-vue-transform-on@1.3.0': {} @@ -6779,9 +6800,9 @@ snapshots: optionalDependencies: typescript: 5.8.3 - '@vue/language-core@2.2.10(typescript@5.8.3)': + '@vue/language-core@2.2.12(typescript@5.8.3)': dependencies: - '@volar/language-core': 2.4.12 + '@volar/language-core': 2.4.15 '@vue/compiler-dom': 3.5.41 '@vue/compiler-vue2': 2.7.16 '@vue/shared': 3.5.41 @@ -10075,10 +10096,10 @@ snapshots: vue-template-es2015-compiler@1.9.1: {} - vue-tsc@2.2.10(typescript@5.8.3): + vue-tsc@2.2.12(typescript@5.8.3): dependencies: - '@volar/typescript': 2.4.12 - '@vue/language-core': 2.2.10(typescript@5.8.3) + '@volar/typescript': 2.4.15 + '@vue/language-core': 2.2.12(typescript@5.8.3) typescript: 5.8.3 vue@3.5.26(typescript@5.8.3): diff --git a/src/assets/style/entrance-transition.less b/src/assets/style/entrance-transition.less index 8b158b3..702b91e 100644 --- a/src/assets/style/entrance-transition.less +++ b/src/assets/style/entrance-transition.less @@ -12,9 +12,3 @@ opacity: 1; transform: translate3d(0, 0, 0); } - -@media (prefers-reduced-motion: reduce) { - .entrance-fade-up-enter-active { - transition: none; - } -} diff --git a/src/directives/entrance-transition.ts b/src/directives/entrance-transition.ts index 0ad54aa..546a554 100644 --- a/src/directives/entrance-transition.ts +++ b/src/directives/entrance-transition.ts @@ -1,18 +1,31 @@ import type { ObjectDirective } from "vue"; const ENTRANCE_DURATION = 400; +const ENTRANCE_DELAY_STEP = 70; const ACTIVE_CLASS = "entrance-fade-up-enter-active"; const FROM_CLASS = "entrance-fade-up-enter-from"; const TO_CLASS = "entrance-fade-up-enter-to"; const STATE_KEY = Symbol("entrance-transition-state"); +const AUTO_ELEMENTS = new WeakSet(); -/** 入场动画指令支持的延迟值。 */ -export type EntranceTransitionValue = number | string | { delay?: number | string } | undefined; +/** 入场动画指令支持的延迟配置;省略时按同级元素顺序自动错开。 */ +export type EntranceTransitionValue = + | number + | string + | { + /** 固定延迟;设置 step 时启用自动步进,step 优先。 */ + delay?: number | string; + /** 自动步进间隔,单位为毫秒。 */ + step?: number | string; + } + | undefined; interface EntranceTransitionState { frameId?: number; timerId?: number; delay: number; + step: number; + auto: boolean; } type EntranceTransitionElement = HTMLElement & { @@ -29,22 +42,51 @@ function setDelay(element: EntranceTransitionElement, delay: number): void { element.style.setProperty("--entrance-delay", `${delay}ms`); } -function cleanup(element: EntranceTransitionElement): void { +function isAuto(value: EntranceTransitionValue): boolean { + return ( + value === undefined || + (typeof value === "object" && value !== null && value.step !== undefined) + ); +} + +function getAutoStep(value: EntranceTransitionValue): number { + const rawStep = + typeof value === "object" && value !== null + ? Number(value.step ?? ENTRANCE_DELAY_STEP) + : ENTRANCE_DELAY_STEP; + return Number.isFinite(rawStep) ? Math.max(0, rawStep) : ENTRANCE_DELAY_STEP; +} + +function getAutoDelay(element: EntranceTransitionElement, step: number): number { + const parent = element.parentElement; + if (!parent) { + return 0; + } + + const siblings = Array.from(parent.children).filter( + (sibling): sibling is HTMLElement => sibling instanceof HTMLElement && AUTO_ELEMENTS.has(sibling) + ); + return Math.max(0, siblings.indexOf(element)) * step; +} + +function cleanup(element: EntranceTransitionElement, isUnmounting = false): void { const state = element[STATE_KEY]; - if (!state) { - return; + if (state) { + if (state.frameId !== undefined) { + window.cancelAnimationFrame(state.frameId); + } + if (state.timerId !== undefined) { + window.clearTimeout(state.timerId); + } + + element.classList.remove(ACTIVE_CLASS, FROM_CLASS, TO_CLASS); + element.style.removeProperty("--entrance-delay"); + delete element[STATE_KEY]; } - if (state.frameId !== undefined) { - window.cancelAnimationFrame(state.frameId); + if (isUnmounting) { + AUTO_ELEMENTS.delete(element); } - if (state.timerId !== undefined) { - window.clearTimeout(state.timerId); - } - - element.classList.remove(ACTIVE_CLASS, FROM_CLASS, TO_CLASS); - element.style.removeProperty("--entrance-delay"); - delete element[STATE_KEY]; } /** 为元素添加一次性的渐入上移动画。 */ @@ -55,8 +97,15 @@ export const VEntranceTransition: ObjectDirective { state.frameId = window.requestAnimationFrame(() => { state.frameId = undefined; + if (state.auto) { + state.delay = getAutoDelay(target, state.step); + setDelay(target, state.delay); + } target.classList.remove(FROM_CLASS); target.classList.add(TO_CLASS); state.timerId = window.setTimeout(() => cleanup(target), ENTRANCE_DURATION + state.delay); @@ -88,18 +141,31 @@ export const VEntranceTransition: ObjectDirective