aboutsummaryrefslogtreecommitdiff
path: root/src/routes/+layout.svelte
diff options
context:
space:
mode:
authorFuwn <[email protected]>2026-05-19 02:40:22 +0000
committerFuwn <[email protected]>2026-05-19 02:40:22 +0000
commit7e2495bd7579a16151ef875b571989492d190985 (patch)
treeaa03bf0eacbb55ce9887fc476ac7fb0d5b5fd807 /src/routes/+layout.svelte
parentperf(load): font-display swap and content-visibility on badge wall (diff)
downloaddue.moe-7e2495bd7579a16151ef875b571989492d190985.tar.xz
due.moe-7e2495bd7579a16151ef875b571989492d190985.zip
feat(nav): direction-aware view transitions, header excluded
Wires SvelteKit's onNavigate hook into document.startViewTransition, with a callback that bypasses cleanly when the API is unavailable or the user prefers reduced motion. Direction is computed from navigation.from/to.pathname using the same navigationOrder logic as the existing fly slide (forward through the ordered routes, backward otherwise; entering /user is +1, leaving -1). Sign is written to --vt-direction on :root before the transition fires. ::view-transition-old(root) and -new(root) get explicit slide keyframes that read --vt-direction as a sign multiplier on translateX(200px), replacing the browser default crossfade with a direction-aware page-flip that matches the prior feel. .header carries view-transition-name: app-header so it is pulled out of the root snapshot and treated as a shared element. Since the header lives outside the {#key data.url} block and is the same DOM element on both sides, its morph is a visual no-op: only the body slides past it instead of the whole viewport. Svelte's existing fly transition in Root.svelte still runs hidden beneath the snapshot for browsers without View Transitions support, acting as a graceful fallback.
Diffstat (limited to 'src/routes/+layout.svelte')
-rw-r--r--src/routes/+layout.svelte30
1 files changed, 30 insertions, 0 deletions
diff --git a/src/routes/+layout.svelte b/src/routes/+layout.svelte
index 8c36bb0f..e2cb4132 100644
--- a/src/routes/+layout.svelte
+++ b/src/routes/+layout.svelte
@@ -11,6 +11,7 @@ import HeadTitle from "$lib/Home/HeadTitle.svelte";
import "../app.css";
import { readable, type Readable } from "svelte/store";
import { navigating } from "$app/stores";
+import { onNavigate } from "$app/navigation";
import NotificationsProvider from "$lib/Notification/NotificationsProvider.svelte";
import Root from "$lib/Home/Root.svelte";
import root from "$lib/Utility/root";
@@ -90,6 +91,34 @@ $: way = data.url.includes("/user")
$: if ($navigating) isMenuOpen = false;
+onNavigate((navigation) => {
+ if (!("startViewTransition" in document)) return;
+ if (window.matchMedia("(prefers-reduced-motion: reduce)").matches) return;
+
+ const fromPath = navigation.from?.url.pathname ?? "/";
+ const toPath = navigation.to?.url.pathname ?? "/";
+ const direction = toPath.includes("/user")
+ ? 1
+ : fromPath.includes("/user")
+ ? -1
+ : navigationOrder.indexOf(toPath) > navigationOrder.indexOf(fromPath)
+ ? 1
+ : -1;
+
+ document.documentElement.style.setProperty("--vt-direction", String(direction));
+
+ return new Promise((resolve) => {
+ (
+ document as Document & {
+ startViewTransition: (cb: () => Promise<void>) => unknown;
+ }
+ ).startViewTransition(async () => {
+ resolve();
+ await navigation.complete;
+ });
+ });
+});
+
const handleScroll = () => {
const currentScrollPosition = window.scrollY;
@@ -437,6 +466,7 @@ $: {
position: sticky;
top: 1.25rem;
background-color: var(--base0011-strong);
+ view-transition-name: app-header;
transition: transform var(--duration-slow) var(--ease-out-quart);
}