feat: directional slide-x page transitions in router
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
+22
-5
@@ -85,6 +85,16 @@ let currentPath = null;
|
|||||||
// Router
|
// Router
|
||||||
// --------------------------------------------------------
|
// --------------------------------------------------------
|
||||||
|
|
||||||
|
const ROUTE_ORDER = ['/', '/tasks', '/shopping', '/meals', '/calendar',
|
||||||
|
'/notes', '/contacts', '/budget', '/settings'];
|
||||||
|
|
||||||
|
function getDirection(fromPath, toPath) {
|
||||||
|
const fromIdx = ROUTE_ORDER.indexOf(fromPath ?? '/');
|
||||||
|
const toIdx = ROUTE_ORDER.indexOf(toPath);
|
||||||
|
if (fromIdx === -1 || toIdx === -1 || fromPath === toPath) return 'right';
|
||||||
|
return toIdx > fromIdx ? 'right' : 'left';
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Navigiert zu einem Pfad und rendert die entsprechende Seite.
|
* Navigiert zu einem Pfad und rendert die entsprechende Seite.
|
||||||
* @param {string} path
|
* @param {string} path
|
||||||
@@ -100,6 +110,8 @@ async function navigate(path, userOrPushState = true, pushState = true) {
|
|||||||
pushState = userOrPushState;
|
pushState = userOrPushState;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Alten Pfad merken, bevor currentPath aktualisiert wird — für Richtungsberechnung
|
||||||
|
const previousPath = currentPath;
|
||||||
currentPath = path;
|
currentPath = path;
|
||||||
|
|
||||||
const route = ROUTES.find((r) => r.path === path) ?? ROUTES.find((r) => r.path === '/');
|
const route = ROUTES.find((r) => r.path === path) ?? ROUTES.find((r) => r.path === '/');
|
||||||
@@ -126,7 +138,7 @@ async function navigate(path, userOrPushState = true, pushState = true) {
|
|||||||
history.pushState({ path }, '', path);
|
history.pushState({ path }, '', path);
|
||||||
}
|
}
|
||||||
|
|
||||||
await renderPage(route);
|
await renderPage(route, previousPath);
|
||||||
updateNav(path);
|
updateNav(path);
|
||||||
updateThemeColorForRoute(route);
|
updateThemeColorForRoute(route);
|
||||||
}
|
}
|
||||||
@@ -134,8 +146,9 @@ async function navigate(path, userOrPushState = true, pushState = true) {
|
|||||||
/**
|
/**
|
||||||
* Lädt und rendert eine Seite dynamisch.
|
* Lädt und rendert eine Seite dynamisch.
|
||||||
* @param {{ path: string, page: string }} route
|
* @param {{ path: string, page: string }} route
|
||||||
|
* @param {string|null} previousPath - Pfad vor der Navigation (für Richtungsberechnung)
|
||||||
*/
|
*/
|
||||||
async function renderPage(route) {
|
async function renderPage(route, previousPath = null) {
|
||||||
const app = document.getElementById('app');
|
const app = document.getElementById('app');
|
||||||
const loading = document.getElementById('app-loading');
|
const loading = document.getElementById('app-loading');
|
||||||
|
|
||||||
@@ -158,18 +171,22 @@ async function renderPage(route) {
|
|||||||
|
|
||||||
const content = document.getElementById('page-content') || app;
|
const content = document.getElementById('page-content') || app;
|
||||||
|
|
||||||
|
// Richtung bestimmen (previousPath ist der alte Pfad vor der Navigation)
|
||||||
|
const direction = getDirection(previousPath, route.path);
|
||||||
|
const outClass = direction === 'right' ? 'page-transition--out-left' : 'page-transition--out-right';
|
||||||
|
const inClass = direction === 'right' ? 'page-transition--in-right' : 'page-transition--in-left';
|
||||||
|
|
||||||
// Alte Seite kurz ausfaden, falls vorhanden
|
// Alte Seite kurz ausfaden, falls vorhanden
|
||||||
const oldPage = content.querySelector('.page-transition');
|
const oldPage = content.querySelector('.page-transition');
|
||||||
if (oldPage) {
|
if (oldPage) {
|
||||||
oldPage.classList.add('page-transition--out');
|
oldPage.classList.add(outClass);
|
||||||
await new Promise(r => setTimeout(r, 120));
|
await new Promise(r => setTimeout(r, 120));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Seiten-Wrapper bereits jetzt in den DOM einfügen, damit
|
// Seiten-Wrapper bereits jetzt in den DOM einfügen, damit
|
||||||
// document.getElementById() in render() die richtigen Elemente findet.
|
// document.getElementById() in render() die richtigen Elemente findet.
|
||||||
const pageWrapper = document.createElement('div');
|
const pageWrapper = document.createElement('div');
|
||||||
pageWrapper.className = 'page-transition';
|
pageWrapper.className = `page-transition ${inClass}`;
|
||||||
pageWrapper.style.animation = 'page-in 0.2s ease forwards';
|
|
||||||
content.replaceChildren(pageWrapper);
|
content.replaceChildren(pageWrapper);
|
||||||
|
|
||||||
await module.render(pageWrapper, { user: currentUser });
|
await module.render(pageWrapper, { user: currentUser });
|
||||||
|
|||||||
+39
-10
@@ -44,22 +44,51 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* --------------------------------------------------------
|
/* --------------------------------------------------------
|
||||||
* Seiten-Übergangs-Animation
|
* Seiten-Übergangs-Animation (direktional)
|
||||||
* -------------------------------------------------------- */
|
* -------------------------------------------------------- */
|
||||||
@keyframes page-in {
|
@keyframes page-slide-in-right {
|
||||||
from { opacity: 0; transform: translateY(4px); }
|
from { opacity: 0; transform: translateX(20px); }
|
||||||
to { opacity: 1; transform: translateY(0); }
|
to { opacity: 1; transform: translateX(0); }
|
||||||
|
}
|
||||||
|
@keyframes page-slide-in-left {
|
||||||
|
from { opacity: 0; transform: translateX(-20px); }
|
||||||
|
to { opacity: 1; transform: translateX(0); }
|
||||||
|
}
|
||||||
|
@keyframes page-out-left {
|
||||||
|
from { opacity: 1; transform: translateX(0); }
|
||||||
|
to { opacity: 0; transform: translateX(-20px); }
|
||||||
|
}
|
||||||
|
@keyframes page-out-right {
|
||||||
|
from { opacity: 1; transform: translateX(0); }
|
||||||
|
to { opacity: 0; transform: translateX(20px); }
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes page-out {
|
.page-transition--in-right {
|
||||||
from { opacity: 1; }
|
animation: page-slide-in-right 0.2s var(--ease-out) forwards;
|
||||||
to { opacity: 0; }
|
|
||||||
}
|
}
|
||||||
|
.page-transition--in-left {
|
||||||
.page-transition--out {
|
animation: page-slide-in-left 0.2s var(--ease-out) forwards;
|
||||||
animation: page-out 0.12s ease forwards;
|
}
|
||||||
|
.page-transition--out-left {
|
||||||
|
animation: page-out-left 0.12s ease forwards;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
|
.page-transition--out-right {
|
||||||
|
animation: page-out-right 0.12s ease forwards;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (prefers-reduced-motion: reduce) {
|
||||||
|
.page-transition--in-right,
|
||||||
|
.page-transition--in-left {
|
||||||
|
animation: none;
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
.page-transition--out-left,
|
||||||
|
.page-transition--out-right {
|
||||||
|
animation: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* --------------------------------------------------------
|
/* --------------------------------------------------------
|
||||||
* Layout: Mobile (Standard, < 1024px)
|
* Layout: Mobile (Standard, < 1024px)
|
||||||
|
|||||||
Reference in New Issue
Block a user