From 1641f3e75b0cc687aaf230516d71e91d83cec52d Mon Sep 17 00:00:00 2001 From: thislight Date: Sun, 17 Nov 2024 17:37:42 +0800 Subject: [PATCH] StackedRouter: hero animation support --- src/platform/StackedRouter.css | 16 ++++++---- src/platform/StackedRouter.tsx | 56 ++++++++++++++++++++++++++-------- 2 files changed, 54 insertions(+), 18 deletions(-) diff --git a/src/platform/StackedRouter.css b/src/platform/StackedRouter.css index 5dae005..6b912c1 100644 --- a/src/platform/StackedRouter.css +++ b/src/platform/StackedRouter.css @@ -24,12 +24,8 @@ dialog.StackedPage { background: var(--tutu-color-surface); box-shadow: var(--tutu-shadow-e16); - @media (min-width: 560px) { - & { - left: 50%; - transform: translateX(-50%); - } - } + margin-left: auto; + margin-right: auto; @media (max-width: 560px) { & { @@ -50,4 +46,12 @@ dialog.StackedPage { &::backdrop { background: none; } + + &.animating { + overflow: hidden; + + * { + overflow: hidden; + } + } } \ No newline at end of file diff --git a/src/platform/StackedRouter.tsx b/src/platform/StackedRouter.tsx index 7325d7e..792ec97 100644 --- a/src/platform/StackedRouter.tsx +++ b/src/platform/StackedRouter.tsx @@ -6,6 +6,7 @@ import { createRenderEffect, createUniqueId, Index, + onMount, Show, untrack, useContext, @@ -15,9 +16,7 @@ import { createStore, unwrap } from "solid-js/store"; import "./StackedRouter.css"; import { animateSlideInFromRight, animateSlideOutToRight } from "./anim"; import { ANIM_CURVE_DECELERATION, ANIM_CURVE_STD } from "../material/theme"; -import { - makeEventListener, -} from "@solid-primitives/event-listener"; +import { makeEventListener } from "@solid-primitives/event-listener"; export type StackedRouterProps = Omit; @@ -25,6 +24,9 @@ export type StackFrame = { path: string; rootId: string; state: unknown; + + animateOpen?: (element: HTMLElement) => Animation; + animateClose?: (element: HTMLElement) => Animation; }; export type NewFrameOptions = (T extends undefined @@ -33,6 +35,8 @@ export type NewFrameOptions = (T extends undefined } : { state: T }) & { replace?: boolean; + animateOpen?: StackFrame["animateOpen"]; + animateClose?: StackFrame["animateClose"]; }; export type FramePusher = T[K] extends @@ -117,9 +121,11 @@ function animateClose(element: HTMLElement) { function animateOpen(element: HTMLElement) { if (window.innerWidth <= 560) { - animateSlideInFromRight(element, { easing: ANIM_CURVE_DECELERATION }); + return animateSlideInFromRight(element, { + easing: ANIM_CURVE_DECELERATION, + }); } else { - element.animate( + return element.animate( { opacity: [0.5, 1], }, @@ -128,6 +134,17 @@ function animateOpen(element: HTMLElement) { } } +function serializableStack(stack: readonly StackFrame[]) { + const frames = unwrap(stack); + return frames.map((fr) => { + return { + path: fr.path, + rootId: fr.rootId, + state: fr.state, + }; + }); +} + /** * The router that stacks the pages. */ @@ -140,13 +157,15 @@ const StackedRouter: Component = (oprops) => { path, state: opts?.state, rootId: createUniqueId(), + animateOpen: opts?.animateOpen, + animateClose: opts?.animateClose, }; mutStack(opts?.replace ? stack.length - 1 : stack.length, frame); if (opts?.replace) { - window.history.replaceState(unwrap(stack), "", path); + window.history.replaceState(serializableStack(stack), "", path); } else { - window.history.pushState(unwrap(stack), "", path); + window.history.pushState(serializableStack(stack), "", path); } return frame; }); @@ -165,10 +184,17 @@ const StackedRouter: Component = (oprops) => { } if (stack.length > 1) { const lastFrame = stack[stack.length - 1]; - const element = document.getElementById(lastFrame.rootId)!; + const element = document.getElementById( + lastFrame.rootId, + )! as HTMLDialogElement; + const createAnimation = lastFrame.animateClose ?? animateClose; requestAnimationFrame(() => { - const animation = animateClose(element); - animation.addEventListener("finish", () => onlyPopFrame(depth)); + element.classList.add("animating") + const animation = createAnimation(element); + animation.addEventListener("finish", () => { + element.classList.remove("animating") + onlyPopFrame(depth); + }); }); } else { onlyPopFrame(depth); @@ -195,10 +221,16 @@ const StackedRouter: Component = (oprops) => { }); const onBeforeDialogMount = (element: HTMLDialogElement) => { - createEffect(() => { + onMount(() => { + const lastFr = untrack(() => stack[stack.length - 1]); + const createAnimation = lastFr.animateOpen ?? animateOpen; requestAnimationFrame(() => { element.showModal(); - animateOpen(element); + element.classList.add("animating"); + const animation = createAnimation(element); + animation.addEventListener("finish", () => + element.classList.remove("animating"), + ); }); }); };