@ssgoi/core
Version:
Core animation engine for SSGOI - Native app-like page transitions with spring physics
241 lines • 8.36 kB
TypeScript
export type TransitionKey = string | symbol;
export type SpringConfig = {
stiffness?: number;
damping?: number;
};
/**
* Schedule strategy for multi-spring animations
* - overlap: All springs start immediately (parallel execution)
* - wait: Each spring waits for previous to complete (sequential)
* - chain: Springs start with offset delays
*/
export type ScheduleType = "overlap" | "wait" | "chain";
/**
* Base configuration shared by both single and multi-spring transitions
*/
export type BaseTransitionConfig = {
prepare?: (element: HTMLElement) => void;
wait?: () => Promise<void>;
onStart?: () => void;
onEnd?: () => void;
};
/**
* Single spring configuration (existing - backward compatible)
* Used for simple transitions with a single animation value
*/
export type SingleSpringConfig<TAnimationValue = number> = BaseTransitionConfig & {
spring?: SpringConfig;
from?: TAnimationValue;
to?: TAnimationValue;
tick?: (progress: TAnimationValue) => void;
};
/**
* Individual spring item in a multi-spring animation
*/
export type SpringItem<TAnimationValue = number> = {
from?: TAnimationValue;
to?: TAnimationValue;
spring?: SpringConfig;
/**
* Frame callback - called on each animation frame with progress value
*
* **Performance Warning:** Use WRITE-ONLY operations for best performance.
* Avoid reading layout properties inside tick to prevent layout thrashing.
*
* Layout-triggering properties (causes forced synchronous layout):
* - element.offsetWidth, offsetHeight, offsetTop, offsetLeft
* - element.clientWidth, clientHeight
* - element.getBoundingClientRect()
* - element.scrollWidth, scrollHeight, scrollTop, scrollLeft
* - window.getComputedStyle(element)
*
* @example
* // ❌ Bad: Layout read in tick (causes thrashing)
* tick: (progress) => {
* const height = element.offsetHeight // READ - forces layout!
* element.style.height = height * progress
* }
*
* @example
* // ✅ Good: Read in prepare, write in tick
* prepare: (element) => {
* const height = element.offsetHeight // READ once before animation
* element.dataset.height = height.toString()
* },
* tick: (progress) => {
* const height = parseFloat(element.dataset.height)
* element.style.height = height * progress // WRITE only
* }
*/
tick: (progress: TAnimationValue) => void;
onStart?: () => void;
onComplete?: () => void;
/**
* Delay offset in milliseconds before this spring starts
* Only applies to 'chain' schedule mode
*/
offset?: number;
};
/**
* Multi-spring configuration (new feature)
* Used for complex transitions with multiple coordinated animations
*/
export type MultiSpringConfig<TAnimationValue = number> = BaseTransitionConfig & {
springs: SpringItem<TAnimationValue>[];
schedule?: ScheduleType;
onProgress?: (completed: number, total: number) => void;
};
/**
* Transition configuration - supports both single and multi-spring animations
* Uses Union Type for type safety and backward compatibility
*/
export type TransitionConfig<TAnimationValue = number> = SingleSpringConfig<TAnimationValue> | MultiSpringConfig<TAnimationValue>;
/**
* Type guard to check if config is single spring
*/
export declare function isSingleSpring<T>(config: TransitionConfig<T>): config is SingleSpringConfig<T>;
/**
* Type guard to check if config is multi-spring
*/
export declare function isMultiSpring<T>(config: TransitionConfig<T>): config is MultiSpringConfig<T>;
export type GetTransitionConfig<TContext = undefined, TAnimationValue = number> = TContext extends undefined ? (node: HTMLElement) => TransitionConfig<TAnimationValue> | Promise<TransitionConfig<TAnimationValue>> : (node: HTMLElement, context: TContext) => TransitionConfig<TAnimationValue> | Promise<TransitionConfig<TAnimationValue>>;
export type Transition<TContext = undefined, TAnimationValue = number> = {
in?: GetTransitionConfig<TContext, TAnimationValue>;
out?: GetTransitionConfig<TContext, TAnimationValue>;
key?: TransitionKey;
};
export type TransitionOptions<TContext = undefined, TAnimationValue = number> = Transition<TContext, TAnimationValue> & {
key?: TransitionKey;
ref?: object;
};
export type TransitionCallback = (element: HTMLElement | null) => void | (() => void);
/**
* Context object passed to view transitions
* Provides essential information for smooth page transitions
*/
export type SggoiTransitionContext = {
/**
* The scroll position difference between 'from' and 'to' pages
* Used in hero and pinterest transitions to maintain natural scroll position
* when transitioning between pages with different scroll states
*/
scrollOffset: {
x: number;
y: number;
};
/**
* The current page's scroll position
* Provides the actual scroll coordinates for precise viewport calculations
* OUT transition receives 'from' page scroll, IN transition receives 'to' page scroll
*/
scroll: {
x: number;
y: number;
};
/**
* The scrollable container element (document.documentElement or custom container)
* Provides viewport dimensions for calculating transition animations
* Lazy-evaluated to handle delayed initialization
*/
scrollingElement: HTMLElement;
/**
* The positioned parent element (containing block for absolute positioning)
* Returns the nearest ancestor with position: relative/absolute/fixed/sticky
* Falls back to document.body if no positioned ancestor exists
* Lazy-evaluated to handle delayed initialization
*/
positionedParent: HTMLElement;
};
export type SggoiTransition = Transition<SggoiTransitionContext>;
export type SsgoiConfig = {
transitions?: {
from: string;
to: string;
transition: SggoiTransition;
symmetric?: boolean;
}[];
defaultTransition?: SggoiTransition;
middleware?: (from: string, to: string) => {
from: string;
to: string;
};
/**
* Skip page transitions when iOS/Safari swipe-back gesture is detected
* This prevents animation conflicts between SSGOI and Safari's native swipe animation
* @default true
*/
skipOnIosSwipe?: boolean;
};
export type SsgoiContext = (path: string) => Transition & {
key: TransitionKey;
};
/**
* Normalized schedule entry for internal processing (discriminated union)
* Used by AnimationScheduler to unify different schedule strategies
* @internal
*/
export type NormalizedScheduleEntry = OffsetScheduleEntry | WaitScheduleEntry;
/**
* Offset-based schedule entry (overlap/chain modes)
* - delay=0: immediate start (overlap)
* - delay>0: delayed start with fixed timing (chain)
* @internal
*/
export type OffsetScheduleEntry = {
type: "offset";
id: string;
delay: number;
};
/**
* Wait-based schedule entry (wait mode)
* Dynamic dependency - starts after previous spring completes
* @internal
*/
export type WaitScheduleEntry = {
type: "wait";
id: string;
};
/**
* Animation state (discriminated union)
* Different return types for single vs multi-spring animations
* @internal
*/
export type AnimationState<T = number> = SingleAnimationState<T> | MultiAnimationState;
/**
* Single spring animation state
* Supports both number and object animations
* @internal
*/
export type SingleAnimationState<T = number> = {
type: "single";
position: T;
velocity: T extends number ? number : Record<string, number>;
from: T;
to: T;
};
/**
* Multi-spring animation state
* Always uses number for progress tracking
* @internal
*/
export type MultiAnimationState = {
type: "multi";
completed: number;
total: number;
direction: "forward" | "backward";
};
/**
* Common interface for animation controllers
* Implemented by both Animator (single spring) and AnimationScheduler (multi-spring)
* @internal
*/
export interface AnimationController<T = number> {
forward(): void;
backward(): void;
stop(): void;
reverse(options?: {
offsetMode?: "immediate" | "mirror" | "reverse";
}): void;
getCurrentState(): AnimationState<T>;
}
//# sourceMappingURL=types.d.ts.map