add entrance-transition.ts
This commit is contained in:
@@ -0,0 +1,20 @@
|
|||||||
|
.entrance-fade-up-enter-active {
|
||||||
|
transition: opacity .4s ease, transform .4s ease;
|
||||||
|
transition-delay: var(--entrance-delay, 0ms);
|
||||||
|
}
|
||||||
|
|
||||||
|
.entrance-fade-up-enter-from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translate3d(0, 1rem, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
.entrance-fade-up-enter-to {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translate3d(0, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (prefers-reduced-motion: reduce) {
|
||||||
|
.entrance-fade-up-enter-active {
|
||||||
|
transition: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,107 @@
|
|||||||
|
import type { ObjectDirective } from "vue";
|
||||||
|
|
||||||
|
const ENTRANCE_DURATION = 400;
|
||||||
|
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");
|
||||||
|
|
||||||
|
/** 入场动画指令支持的延迟值。 */
|
||||||
|
export type EntranceTransitionValue = number | string | { delay?: number | string } | undefined;
|
||||||
|
|
||||||
|
interface EntranceTransitionState {
|
||||||
|
frameId?: number;
|
||||||
|
timerId?: number;
|
||||||
|
delay: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
type EntranceTransitionElement = HTMLElement & {
|
||||||
|
[STATE_KEY]?: EntranceTransitionState;
|
||||||
|
};
|
||||||
|
|
||||||
|
function getDelay(value: EntranceTransitionValue): number {
|
||||||
|
const rawDelay = typeof value === "object" && value !== null ? value.delay : value;
|
||||||
|
const delay = Number(rawDelay ?? 0);
|
||||||
|
return Number.isFinite(delay) ? Math.max(0, delay) : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
function setDelay(element: EntranceTransitionElement, delay: number): void {
|
||||||
|
element.style.setProperty("--entrance-delay", `${delay}ms`);
|
||||||
|
}
|
||||||
|
|
||||||
|
function cleanup(element: EntranceTransitionElement): void {
|
||||||
|
const state = element[STATE_KEY];
|
||||||
|
if (!state) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
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];
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 为元素添加一次性的渐入上移动画。 */
|
||||||
|
export const VEntranceTransition: ObjectDirective<HTMLElement, EntranceTransitionValue> = {
|
||||||
|
beforeMount(element, binding) {
|
||||||
|
if (!(element instanceof HTMLElement)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const target = element as EntranceTransitionElement;
|
||||||
|
const delay = getDelay(binding.value);
|
||||||
|
target[STATE_KEY] = { delay };
|
||||||
|
setDelay(target, delay);
|
||||||
|
target.classList.add(ACTIVE_CLASS, FROM_CLASS);
|
||||||
|
},
|
||||||
|
|
||||||
|
mounted(element) {
|
||||||
|
if (!(element instanceof HTMLElement)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const target = element as EntranceTransitionElement;
|
||||||
|
const state = target[STATE_KEY];
|
||||||
|
if (!state) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
state.frameId = window.requestAnimationFrame(() => {
|
||||||
|
state.frameId = window.requestAnimationFrame(() => {
|
||||||
|
state.frameId = undefined;
|
||||||
|
target.classList.remove(FROM_CLASS);
|
||||||
|
target.classList.add(TO_CLASS);
|
||||||
|
state.timerId = window.setTimeout(() => cleanup(target), ENTRANCE_DURATION + state.delay);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
updated(element, binding) {
|
||||||
|
if (!(element instanceof HTMLElement) || binding.value === binding.oldValue) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const target = element as EntranceTransitionElement;
|
||||||
|
const state = target[STATE_KEY];
|
||||||
|
if (!state) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
state.delay = getDelay(binding.value);
|
||||||
|
setDelay(target, state.delay);
|
||||||
|
},
|
||||||
|
|
||||||
|
beforeUnmount(element) {
|
||||||
|
if (element instanceof HTMLElement) {
|
||||||
|
cleanup(element as EntranceTransitionElement);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default VEntranceTransition;
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
export {
|
||||||
|
default as VEntranceTransition
|
||||||
|
} from "./entrance-transition";
|
||||||
|
|
||||||
|
export type {
|
||||||
|
EntranceTransitionValue
|
||||||
|
} from "./entrance-transition";
|
||||||
@@ -1,9 +1,11 @@
|
|||||||
import "./assets/style/tdesign-custom.less";
|
import "./assets/style/tdesign-custom.less";
|
||||||
|
import "./assets/style/entrance-transition.less";
|
||||||
import "./components/tiptap-content.less";
|
import "./components/tiptap-content.less";
|
||||||
import "timi-web/style.css";
|
import "timi-web/style.css";
|
||||||
import "../../timi-web/src/components/prism-code-block.less";
|
import "../../timi-web/src/components/prism-code-block.less";
|
||||||
|
|
||||||
export * from "./components";
|
export * from "./components";
|
||||||
|
export * from "./directives";
|
||||||
|
|
||||||
export {
|
export {
|
||||||
};
|
};
|
||||||
|
|||||||
Vendored
+10
@@ -0,0 +1,10 @@
|
|||||||
|
import type { Directive } from "vue";
|
||||||
|
import type { EntranceTransitionValue } from "../directives/entrance-transition";
|
||||||
|
|
||||||
|
declare module "vue" {
|
||||||
|
export interface GlobalDirectives {
|
||||||
|
entranceTransition: Directive<HTMLElement, EntranceTransitionValue>;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export {};
|
||||||
Reference in New Issue
Block a user