@ssgoi/core 0.0.1
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.
- package/README.md +34 -0
- package/dist/animator.d.ts +41 -0
- package/dist/animator.d.ts.map +1 -0
- package/dist/create-ssgoi-transition-context.d.ts +15 -0
- package/dist/create-ssgoi-transition-context.d.ts.map +1 -0
- package/dist/create-transition-callback.d.ts +36 -0
- package/dist/create-transition-callback.d.ts.map +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/ssgoi.cjs +1 -0
- package/dist/ssgoi.js +1 -0
- package/dist/ssgoi2.cjs +1 -0
- package/dist/ssgoi2.js +305 -0
- package/dist/ssgoi3.cjs +1 -0
- package/dist/ssgoi3.js +173 -0
- package/dist/ssgoi4.cjs +1 -0
- package/dist/ssgoi4.js +764 -0
- package/dist/transition.d.ts +16 -0
- package/dist/transition.d.ts.map +1 -0
- package/dist/transitions/blur.d.ts +27 -0
- package/dist/transitions/blur.d.ts.map +1 -0
- package/dist/transitions/bounce.d.ts +27 -0
- package/dist/transitions/bounce.d.ts.map +1 -0
- package/dist/transitions/fade.d.ts +26 -0
- package/dist/transitions/fade.d.ts.map +1 -0
- package/dist/transitions/index.d.ts +8 -0
- package/dist/transitions/index.d.ts.map +1 -0
- package/dist/transitions/none.d.ts +24 -0
- package/dist/transitions/none.d.ts.map +1 -0
- package/dist/transitions/rotate.d.ts +28 -0
- package/dist/transitions/rotate.d.ts.map +1 -0
- package/dist/transitions/scale.d.ts +27 -0
- package/dist/transitions/scale.d.ts.map +1 -0
- package/dist/transitions/slide.d.ts +27 -0
- package/dist/transitions/slide.d.ts.map +1 -0
- package/dist/transitions.d.ts +2 -0
- package/dist/types.d.ts +38 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/utils-BSgyev81.cjs +1 -0
- package/dist/utils-DR41YsAh.js +16 -0
- package/dist/utils.d.ts +11 -0
- package/dist/utils.d.ts.map +1 -0
- package/dist/view-transitions/fade.d.ts +7 -0
- package/dist/view-transitions/fade.d.ts.map +1 -0
- package/dist/view-transitions/hero.d.ts +8 -0
- package/dist/view-transitions/hero.d.ts.map +1 -0
- package/dist/view-transitions/index.d.ts +5 -0
- package/dist/view-transitions/index.d.ts.map +1 -0
- package/dist/view-transitions/pinterest.d.ts +8 -0
- package/dist/view-transitions/pinterest.d.ts.map +1 -0
- package/dist/view-transitions/ripple.d.ts +7 -0
- package/dist/view-transitions/ripple.d.ts.map +1 -0
- package/dist/view-transitions.d.ts +2 -0
- package/package.json +58 -0
- package/src/lib/animator.ts +172 -0
- package/src/lib/create-ssgoi-transition-context.ts +265 -0
- package/src/lib/create-transition-callback.ts +246 -0
- package/src/lib/index.ts +3 -0
- package/src/lib/transition.ts +76 -0
- package/src/lib/transitions/blur.ts +55 -0
- package/src/lib/transitions/bounce.ts +61 -0
- package/src/lib/transitions/fade.ts +33 -0
- package/src/lib/transitions/index.ts +7 -0
- package/src/lib/transitions/none.ts +27 -0
- package/src/lib/transitions/rotate.ts +57 -0
- package/src/lib/transitions/scale.ts +34 -0
- package/src/lib/transitions/slide.ts +53 -0
- package/src/lib/types.ts +60 -0
- package/src/lib/utils.ts +39 -0
- package/src/lib/view-transitions/fade.ts +29 -0
- package/src/lib/view-transitions/hero.ts +179 -0
- package/src/lib/view-transitions/index.ts +4 -0
- package/src/lib/view-transitions/pinterest.ts +403 -0
- package/src/lib/view-transitions/ripple.ts +45 -0
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
interface FadeOptions {
|
|
2
|
+
from?: number;
|
|
3
|
+
to?: number;
|
|
4
|
+
spring?: {
|
|
5
|
+
stiffness?: number;
|
|
6
|
+
damping?: number;
|
|
7
|
+
};
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export const fade = (options: FadeOptions = {}) => {
|
|
11
|
+
const {
|
|
12
|
+
from = 0,
|
|
13
|
+
to = 1,
|
|
14
|
+
spring = { stiffness: 300, damping: 30 }
|
|
15
|
+
} = options;
|
|
16
|
+
|
|
17
|
+
return {
|
|
18
|
+
in: (element: HTMLElement) => ({
|
|
19
|
+
spring,
|
|
20
|
+
tick: (progress: number) => {
|
|
21
|
+
const opacity = from + (to - from) * progress;
|
|
22
|
+
element.style.opacity = opacity.toString();
|
|
23
|
+
}
|
|
24
|
+
}),
|
|
25
|
+
out: (element: HTMLElement) => ({
|
|
26
|
+
spring,
|
|
27
|
+
tick: (progress: number) => {
|
|
28
|
+
const opacity = from + (to - from) * progress;
|
|
29
|
+
element.style.opacity = opacity.toString();
|
|
30
|
+
}
|
|
31
|
+
})
|
|
32
|
+
};
|
|
33
|
+
};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
interface NoneOptions {
|
|
2
|
+
spring?: {
|
|
3
|
+
stiffness?: number;
|
|
4
|
+
damping?: number;
|
|
5
|
+
};
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export const none = (options: NoneOptions = {}) => {
|
|
9
|
+
const {
|
|
10
|
+
spring = { stiffness: 1000, damping: 100 }
|
|
11
|
+
} = options;
|
|
12
|
+
|
|
13
|
+
return {
|
|
14
|
+
in: (_element: HTMLElement) => ({
|
|
15
|
+
spring,
|
|
16
|
+
tick: (_progress: number) => {
|
|
17
|
+
// No animation, just instantly show
|
|
18
|
+
}
|
|
19
|
+
}),
|
|
20
|
+
out: (_element: HTMLElement) => ({
|
|
21
|
+
spring,
|
|
22
|
+
tick: (_progress: number) => {
|
|
23
|
+
// No animation, just instantly hide
|
|
24
|
+
}
|
|
25
|
+
})
|
|
26
|
+
};
|
|
27
|
+
};
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
interface RotateOptions {
|
|
2
|
+
degrees?: number;
|
|
3
|
+
clockwise?: boolean;
|
|
4
|
+
scale?: boolean;
|
|
5
|
+
fade?: boolean;
|
|
6
|
+
spring?: {
|
|
7
|
+
stiffness?: number;
|
|
8
|
+
damping?: number;
|
|
9
|
+
};
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export const rotate = (options: RotateOptions = {}) => {
|
|
13
|
+
const {
|
|
14
|
+
degrees = 360,
|
|
15
|
+
clockwise = true,
|
|
16
|
+
scale = false,
|
|
17
|
+
fade = false,
|
|
18
|
+
spring = { stiffness: 500, damping: 25 }
|
|
19
|
+
} = options;
|
|
20
|
+
|
|
21
|
+
const rotation = clockwise ? degrees : -degrees;
|
|
22
|
+
|
|
23
|
+
return {
|
|
24
|
+
in: (element: HTMLElement) => ({
|
|
25
|
+
spring,
|
|
26
|
+
tick: (progress: number) => {
|
|
27
|
+
const transforms = [`rotate(${progress * rotation}deg)`];
|
|
28
|
+
|
|
29
|
+
if (scale) {
|
|
30
|
+
transforms.push(`scale(${progress})`);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
element.style.transform = transforms.join(' ');
|
|
34
|
+
|
|
35
|
+
if (fade) {
|
|
36
|
+
element.style.opacity = progress.toString();
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}),
|
|
40
|
+
out: (element: HTMLElement) => ({
|
|
41
|
+
spring,
|
|
42
|
+
tick: (progress: number) => {
|
|
43
|
+
const transforms = [`rotate(${progress * rotation}deg)`];
|
|
44
|
+
|
|
45
|
+
if (scale) {
|
|
46
|
+
transforms.push(`scale(${progress})`);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
element.style.transform = transforms.join(' ');
|
|
50
|
+
|
|
51
|
+
if (fade) {
|
|
52
|
+
element.style.opacity = progress.toString();
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
})
|
|
56
|
+
};
|
|
57
|
+
};
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
interface ScaleOptions {
|
|
2
|
+
duration?: number;
|
|
3
|
+
from?: number;
|
|
4
|
+
to?: number;
|
|
5
|
+
spring?: {
|
|
6
|
+
stiffness?: number;
|
|
7
|
+
damping?: number;
|
|
8
|
+
};
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export const scale = (options: ScaleOptions = {}) => {
|
|
12
|
+
const {
|
|
13
|
+
from = 0,
|
|
14
|
+
to = 1,
|
|
15
|
+
spring = { stiffness: 300, damping: 30 }
|
|
16
|
+
} = options;
|
|
17
|
+
|
|
18
|
+
return {
|
|
19
|
+
in: (element: HTMLElement) => ({
|
|
20
|
+
spring,
|
|
21
|
+
tick: (progress: number) => {
|
|
22
|
+
const value = from + (to - from) * progress;
|
|
23
|
+
element.style.transform = `scale(${value})`;
|
|
24
|
+
}
|
|
25
|
+
}),
|
|
26
|
+
out: (element: HTMLElement) => ({
|
|
27
|
+
spring,
|
|
28
|
+
tick: (progress: number) => {
|
|
29
|
+
const value = from + (to - from) * progress;
|
|
30
|
+
element.style.transform = `scale(${value})`;
|
|
31
|
+
}
|
|
32
|
+
})
|
|
33
|
+
};
|
|
34
|
+
};
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
interface SlideOptions {
|
|
2
|
+
direction?: 'left' | 'right' | 'up' | 'down';
|
|
3
|
+
distance?: number;
|
|
4
|
+
fade?: boolean;
|
|
5
|
+
spring?: {
|
|
6
|
+
stiffness?: number;
|
|
7
|
+
damping?: number;
|
|
8
|
+
};
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export const slide = (options: SlideOptions = {}) => {
|
|
12
|
+
const {
|
|
13
|
+
direction = 'left',
|
|
14
|
+
distance = 100,
|
|
15
|
+
fade = true,
|
|
16
|
+
spring = { stiffness: 400, damping: 35 }
|
|
17
|
+
} = options;
|
|
18
|
+
|
|
19
|
+
const getTransform = (progress: number) => {
|
|
20
|
+
const offset = (1 - progress) * distance;
|
|
21
|
+
switch (direction) {
|
|
22
|
+
case 'left':
|
|
23
|
+
return `translateX(${-offset}px)`;
|
|
24
|
+
case 'right':
|
|
25
|
+
return `translateX(${offset}px)`;
|
|
26
|
+
case 'up':
|
|
27
|
+
return `translateY(${-offset}px)`;
|
|
28
|
+
case 'down':
|
|
29
|
+
return `translateY(${offset}px)`;
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
return {
|
|
34
|
+
in: (element: HTMLElement) => ({
|
|
35
|
+
spring,
|
|
36
|
+
tick: (progress: number) => {
|
|
37
|
+
element.style.transform = getTransform(progress);
|
|
38
|
+
if (fade) {
|
|
39
|
+
element.style.opacity = progress.toString();
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}),
|
|
43
|
+
out: (element: HTMLElement) => ({
|
|
44
|
+
spring,
|
|
45
|
+
tick: (progress: number) => {
|
|
46
|
+
element.style.transform = getTransform(progress);
|
|
47
|
+
if (fade) {
|
|
48
|
+
element.style.opacity = progress.toString();
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
})
|
|
52
|
+
};
|
|
53
|
+
};
|
package/src/lib/types.ts
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
export type TransitionKey = string | symbol;
|
|
2
|
+
|
|
3
|
+
export type SpringConfig = {
|
|
4
|
+
stiffness?: number;
|
|
5
|
+
damping?: number;
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
export type TransitionConfig = {
|
|
9
|
+
// Spring physics configuration
|
|
10
|
+
spring?: SpringConfig; // Default: { stiffness: 300, damping: 30 }
|
|
11
|
+
|
|
12
|
+
// Callback for each frame with progress value (0-1)
|
|
13
|
+
tick?: (progress: number) => void;
|
|
14
|
+
|
|
15
|
+
// Prepare element before animation (typically for out transitions)
|
|
16
|
+
prepare?: (element: HTMLElement) => void;
|
|
17
|
+
|
|
18
|
+
// Called when animation starts
|
|
19
|
+
onStart?: () => void;
|
|
20
|
+
|
|
21
|
+
// Called when animation ends
|
|
22
|
+
onEnd?: () => void;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
export type GetTransitionConfig<TContext = undefined> =
|
|
26
|
+
TContext extends undefined
|
|
27
|
+
? (node: HTMLElement) => TransitionConfig | Promise<TransitionConfig>
|
|
28
|
+
: (
|
|
29
|
+
node: HTMLElement,
|
|
30
|
+
context: TContext
|
|
31
|
+
) => TransitionConfig | Promise<TransitionConfig>;
|
|
32
|
+
|
|
33
|
+
export type Transition<TContext = undefined> = {
|
|
34
|
+
in?: GetTransitionConfig<TContext>;
|
|
35
|
+
out?: GetTransitionConfig<TContext>;
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
export type TransitionCallback = (
|
|
39
|
+
element: HTMLElement | null
|
|
40
|
+
) => void | (() => void);
|
|
41
|
+
|
|
42
|
+
export type SggoiTransitionContext = {
|
|
43
|
+
scrollOffset: { x: number; y: number };
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
export type SggoiTransition = Transition<SggoiTransitionContext>;
|
|
47
|
+
|
|
48
|
+
export type SsgoiConfig = {
|
|
49
|
+
transitions: {
|
|
50
|
+
from: string;
|
|
51
|
+
to: string;
|
|
52
|
+
transition: SggoiTransition;
|
|
53
|
+
symmetric?: boolean;
|
|
54
|
+
}[];
|
|
55
|
+
defaultTransition?: SggoiTransition;
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
export type SsgoiContext = (
|
|
59
|
+
path: string
|
|
60
|
+
) => Transition & { key: TransitionKey };
|
package/src/lib/utils.ts
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Applies common styles for outgoing page elements
|
|
3
|
+
* Makes the element absolute positioned to allow the incoming page to take its place
|
|
4
|
+
*/
|
|
5
|
+
export const prepareOutgoing = (element: HTMLElement): void => {
|
|
6
|
+
element.style.position = "absolute";
|
|
7
|
+
element.style.width = "100%";
|
|
8
|
+
element.style.top = "0";
|
|
9
|
+
element.style.left = "0";
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Gets the scrolling element that contains the given element
|
|
14
|
+
* Returns the first scrollable parent element or document.documentElement
|
|
15
|
+
*/
|
|
16
|
+
export const getScrollingElement = (element: HTMLElement): HTMLElement => {
|
|
17
|
+
let current = element.parentElement;
|
|
18
|
+
|
|
19
|
+
while (current && current !== document.body) {
|
|
20
|
+
const style = window.getComputedStyle(current);
|
|
21
|
+
const overflow = style.overflow + style.overflowY + style.overflowX;
|
|
22
|
+
|
|
23
|
+
// Check if element is scrollable
|
|
24
|
+
if (overflow.includes('auto') || overflow.includes('scroll')) {
|
|
25
|
+
return current;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// Also check if element has scroll even without explicit overflow
|
|
29
|
+
if (current.scrollHeight > current.clientHeight ||
|
|
30
|
+
current.scrollWidth > current.clientWidth) {
|
|
31
|
+
return current;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
current = current.parentElement;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Return document element as fallback (handles body/html scrolling)
|
|
38
|
+
return document.documentElement;
|
|
39
|
+
};
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { SpringConfig, SggoiTransition } from "../types";
|
|
2
|
+
import { prepareOutgoing } from "../utils";
|
|
3
|
+
|
|
4
|
+
interface FadeOptions {
|
|
5
|
+
spring?: Partial<SpringConfig>;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export const fade = (options: FadeOptions = {}): SggoiTransition => {
|
|
9
|
+
const spring: SpringConfig = {
|
|
10
|
+
stiffness: options.spring?.stiffness ?? 300,
|
|
11
|
+
damping: options.spring?.damping ?? 30,
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
return {
|
|
15
|
+
in: (element) => ({
|
|
16
|
+
spring,
|
|
17
|
+
tick: (progress) => {
|
|
18
|
+
element.style.opacity = progress.toString();
|
|
19
|
+
},
|
|
20
|
+
}),
|
|
21
|
+
out: (element) => ({
|
|
22
|
+
spring,
|
|
23
|
+
tick: (progress) => {
|
|
24
|
+
element.style.opacity = progress.toString();
|
|
25
|
+
},
|
|
26
|
+
prepare: prepareOutgoing,
|
|
27
|
+
}),
|
|
28
|
+
};
|
|
29
|
+
};
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
import type { SpringConfig, SggoiTransition } from "../types";
|
|
2
|
+
import { prepareOutgoing } from "../utils";
|
|
3
|
+
|
|
4
|
+
interface HeroOptions {
|
|
5
|
+
spring?: Partial<SpringConfig>;
|
|
6
|
+
timeout?: number;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
function getHeroEl(page: HTMLElement, key: string): HTMLElement | null {
|
|
10
|
+
return page.querySelector(`[data-hero-key="${key}"]`);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function getRect(root: HTMLElement, el: HTMLElement): DOMRect {
|
|
14
|
+
const rootRect = root.getBoundingClientRect();
|
|
15
|
+
const elRect = el.getBoundingClientRect();
|
|
16
|
+
|
|
17
|
+
return new DOMRect(
|
|
18
|
+
elRect.left - rootRect.left,
|
|
19
|
+
elRect.top - rootRect.top,
|
|
20
|
+
elRect.width,
|
|
21
|
+
elRect.height
|
|
22
|
+
);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export const hero = (options: HeroOptions = {}): SggoiTransition => {
|
|
26
|
+
const spring: SpringConfig = {
|
|
27
|
+
stiffness: options.spring?.stiffness ?? 300,
|
|
28
|
+
damping: options.spring?.damping ?? 30,
|
|
29
|
+
};
|
|
30
|
+
const timeout = options.timeout ?? 300;
|
|
31
|
+
|
|
32
|
+
// Closure variables to share state between in/out
|
|
33
|
+
let fromNode: HTMLElement | null = null;
|
|
34
|
+
let resolver: ((value: boolean) => void) | null = null;
|
|
35
|
+
|
|
36
|
+
return {
|
|
37
|
+
in: async (element, context) => {
|
|
38
|
+
const toNode = element;
|
|
39
|
+
|
|
40
|
+
// Find all hero elements in the incoming page
|
|
41
|
+
const heroEls = Array.from(toNode.querySelectorAll("[data-hero-key]"));
|
|
42
|
+
if (heroEls.length === 0) {
|
|
43
|
+
return {
|
|
44
|
+
spring,
|
|
45
|
+
tick: () => {}, // No hero elements, skip animation
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Wait for fromNode to be set by out transition
|
|
50
|
+
const hasFromNode = await new Promise<boolean>((resolve) => {
|
|
51
|
+
if (fromNode) {
|
|
52
|
+
// fromNode already set by out transition
|
|
53
|
+
resolve(true);
|
|
54
|
+
} else {
|
|
55
|
+
// Store resolver for out transition to call
|
|
56
|
+
resolver = resolve;
|
|
57
|
+
// Timeout fallback
|
|
58
|
+
setTimeout(() => {
|
|
59
|
+
resolver = null;
|
|
60
|
+
resolve(false);
|
|
61
|
+
}, timeout);
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
if (!hasFromNode || !fromNode) {
|
|
66
|
+
fromNode = null;
|
|
67
|
+
return {
|
|
68
|
+
spring,
|
|
69
|
+
tick: () => {}, // No fromNode, skip animation
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Calculate animations for matching hero elements
|
|
74
|
+
const heroAnimations = heroEls
|
|
75
|
+
.map((heroEl) => {
|
|
76
|
+
const key = heroEl.getAttribute("data-hero-key");
|
|
77
|
+
if (!key) return null;
|
|
78
|
+
|
|
79
|
+
const fromEl = getHeroEl(fromNode!, key);
|
|
80
|
+
if (!fromEl) return null;
|
|
81
|
+
|
|
82
|
+
const toEl = heroEl as HTMLElement;
|
|
83
|
+
|
|
84
|
+
// Calculate animation parameters
|
|
85
|
+
const fromRect = getRect(fromNode!, fromEl);
|
|
86
|
+
const toRect = getRect(toNode, toEl);
|
|
87
|
+
const dx = fromRect.left - toRect.left - context.scrollOffset.x;
|
|
88
|
+
const dy = fromRect.top - toRect.top - context.scrollOffset.y;
|
|
89
|
+
const dw = fromRect.width / toRect.width;
|
|
90
|
+
const dh = fromRect.height / toRect.height;
|
|
91
|
+
|
|
92
|
+
// Store original styles
|
|
93
|
+
const originalTransform = toEl.style.transform;
|
|
94
|
+
const originalPosition = toEl.style.position;
|
|
95
|
+
const originalTransformOrigin = toEl.style.transformOrigin;
|
|
96
|
+
|
|
97
|
+
return {
|
|
98
|
+
toEl,
|
|
99
|
+
dx,
|
|
100
|
+
dy,
|
|
101
|
+
dw,
|
|
102
|
+
dh,
|
|
103
|
+
originalTransform,
|
|
104
|
+
originalPosition,
|
|
105
|
+
originalTransformOrigin,
|
|
106
|
+
};
|
|
107
|
+
})
|
|
108
|
+
.filter(Boolean) as Array<{
|
|
109
|
+
toEl: HTMLElement;
|
|
110
|
+
dx: number;
|
|
111
|
+
dy: number;
|
|
112
|
+
dw: number;
|
|
113
|
+
dh: number;
|
|
114
|
+
originalTransform: string;
|
|
115
|
+
originalPosition: string;
|
|
116
|
+
originalTransformOrigin: string;
|
|
117
|
+
}>;
|
|
118
|
+
|
|
119
|
+
// Reset fromNode for next transition
|
|
120
|
+
fromNode = null;
|
|
121
|
+
|
|
122
|
+
if (heroAnimations.length === 0) {
|
|
123
|
+
return {
|
|
124
|
+
spring,
|
|
125
|
+
tick: () => {}, // No matching hero elements
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
return {
|
|
130
|
+
spring,
|
|
131
|
+
prepare: () => {
|
|
132
|
+
heroAnimations.forEach(({ toEl }) => {
|
|
133
|
+
toEl.style.position = "relative";
|
|
134
|
+
toEl.style.transformOrigin = "top left";
|
|
135
|
+
});
|
|
136
|
+
},
|
|
137
|
+
tick: (progress) => {
|
|
138
|
+
// Animate all hero elements
|
|
139
|
+
heroAnimations.forEach(({ toEl, dx, dy, dw, dh }) => {
|
|
140
|
+
toEl.style.transform = `translate(${(1 - progress) * dx}px,${(1 - progress) * dy}px) scale(${progress + (1 - progress) * dw}, ${progress + (1 - progress) * dh})`;
|
|
141
|
+
});
|
|
142
|
+
},
|
|
143
|
+
onEnd: () => {
|
|
144
|
+
// Reset all hero elements
|
|
145
|
+
heroAnimations.forEach(
|
|
146
|
+
({
|
|
147
|
+
toEl,
|
|
148
|
+
originalTransform,
|
|
149
|
+
originalPosition,
|
|
150
|
+
originalTransformOrigin,
|
|
151
|
+
}) => {
|
|
152
|
+
toEl.style.transform = originalTransform;
|
|
153
|
+
toEl.style.position = originalPosition;
|
|
154
|
+
toEl.style.transformOrigin = originalTransformOrigin;
|
|
155
|
+
}
|
|
156
|
+
);
|
|
157
|
+
},
|
|
158
|
+
};
|
|
159
|
+
},
|
|
160
|
+
out: async (element) => {
|
|
161
|
+
return {
|
|
162
|
+
onStart: () => {
|
|
163
|
+
// Store fromNode
|
|
164
|
+
fromNode = element;
|
|
165
|
+
|
|
166
|
+
// If there's a waiting resolver, resolve it
|
|
167
|
+
if (resolver) {
|
|
168
|
+
resolver(true);
|
|
169
|
+
resolver = null;
|
|
170
|
+
}
|
|
171
|
+
},
|
|
172
|
+
prepare: (element) => {
|
|
173
|
+
prepareOutgoing(element);
|
|
174
|
+
element.style.opacity = "0";
|
|
175
|
+
},
|
|
176
|
+
};
|
|
177
|
+
},
|
|
178
|
+
};
|
|
179
|
+
};
|