mirror of
https://github.com/Sonny93/my-links.git
synced 2025-12-11 16:53:05 +00:00
refactor: page transitioning
This commit is contained in:
@@ -25,34 +25,18 @@
|
||||
--mantine-color-body: var(--ml-bg-dark) !important;
|
||||
}
|
||||
|
||||
.__transition_fadeIn {
|
||||
transform-origin: 50% top;
|
||||
animation: fadeIn 0.15s ease both;
|
||||
}
|
||||
|
||||
.__transition_fadeOut {
|
||||
transform-origin: 50% top;
|
||||
animation: fadeOut 0.15s ease both;
|
||||
}
|
||||
|
||||
@keyframes fadeIn {
|
||||
@keyframes pageFadeIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: scale(0.9);
|
||||
filter: blur(0.44rem);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: none;
|
||||
filter: blur(0);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes fadeOut {
|
||||
from {
|
||||
opacity: 1;
|
||||
transform: none;
|
||||
}
|
||||
to {
|
||||
opacity: 0;
|
||||
transform: scale(0.9);
|
||||
}
|
||||
.page-transition-enter {
|
||||
animation: pageFadeIn 250ms ease forwards;
|
||||
will-change: opacity, filter;
|
||||
}
|
||||
|
||||
54
inertia/hooks/use_page_transition.tsx
Normal file
54
inertia/hooks/use_page_transition.tsx
Normal file
@@ -0,0 +1,54 @@
|
||||
import { useEffect, useRef } from 'react';
|
||||
import {
|
||||
getCurrentPathAndSearch,
|
||||
getPathAndSearchFromRaw,
|
||||
restartCssAnimation,
|
||||
} from '~/lib/navigation';
|
||||
import { InertiaSuccessEvent } from '~/types/inertia';
|
||||
|
||||
const PAGE_TRANSITION_CLASS = 'page-transition-enter';
|
||||
|
||||
interface UsePageTransitionProps {
|
||||
querySelector: string;
|
||||
}
|
||||
|
||||
export const usePageTransition = ({
|
||||
querySelector,
|
||||
}: UsePageTransitionProps): void => {
|
||||
const previousUrlRef = useRef<string>(getCurrentPathAndSearch());
|
||||
|
||||
useEffect(() => {
|
||||
const handleSuccess = (event: Event) => {
|
||||
const element = document.querySelector(
|
||||
querySelector
|
||||
) as HTMLElement | null;
|
||||
if (!element) return;
|
||||
|
||||
const { detail } = event as InertiaSuccessEvent;
|
||||
const nextUrlRaw = detail?.page?.url ?? getCurrentPathAndSearch();
|
||||
|
||||
const next = getPathAndSearchFromRaw(nextUrlRaw);
|
||||
const prev = previousUrlRef.current;
|
||||
if (next === prev) return;
|
||||
|
||||
previousUrlRef.current = next;
|
||||
restartCssAnimation(element, PAGE_TRANSITION_CLASS);
|
||||
|
||||
const onEnd = () => {
|
||||
element.classList.remove(PAGE_TRANSITION_CLASS);
|
||||
element.removeEventListener('animationend', onEnd);
|
||||
};
|
||||
element.addEventListener('animationend', onEnd);
|
||||
};
|
||||
|
||||
document.addEventListener(
|
||||
'inertia:success',
|
||||
handleSuccess as EventListener
|
||||
);
|
||||
return () =>
|
||||
document.removeEventListener(
|
||||
'inertia:success',
|
||||
handleSuccess as EventListener
|
||||
);
|
||||
}, [querySelector]);
|
||||
};
|
||||
@@ -1,6 +1,5 @@
|
||||
import { api } from '#adonis/api';
|
||||
import { PRIMARY_COLOR } from '#config/project';
|
||||
import { router } from '@inertiajs/react';
|
||||
import {
|
||||
ColorSchemeScript,
|
||||
createTheme,
|
||||
@@ -13,14 +12,12 @@ import '@mantine/spotlight/styles.css';
|
||||
import { createTuyau } from '@tuyau/client';
|
||||
import { TuyauProvider } from '@tuyau/inertia/react';
|
||||
import dayjs from 'dayjs';
|
||||
import { ReactNode, useEffect } from 'react';
|
||||
import { ReactNode } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import 'virtual:uno.css';
|
||||
import '~/css/app.css';
|
||||
import { useAppUrl } from '~/hooks/use_app_url';
|
||||
|
||||
const TRANSITION_IN_CLASS = '__transition_fadeIn';
|
||||
const TRANSITION_OUT_CLASS = '__transition_fadeOut';
|
||||
import { usePageTransition } from '~/hooks/use_page_transition';
|
||||
|
||||
const customTheme = createTheme({
|
||||
colors: {
|
||||
@@ -78,46 +75,13 @@ export function BaseLayout({ children }: { children: ReactNode }) {
|
||||
const appUrl = useAppUrl();
|
||||
dayjs.locale(i18n.language);
|
||||
|
||||
usePageTransition({ querySelector: '#app > div:nth-child(5)' });
|
||||
|
||||
const tuyauClient = createTuyau({
|
||||
api,
|
||||
baseUrl: appUrl,
|
||||
});
|
||||
|
||||
const findAppElement = () => document.getElementById('app');
|
||||
|
||||
const flipClass = (addClass: string, removeClass: string) => {
|
||||
const appElement = findAppElement();
|
||||
if (appElement) {
|
||||
appElement.classList.add(addClass);
|
||||
appElement.classList.remove(removeClass);
|
||||
}
|
||||
};
|
||||
|
||||
const canTransition = (currentLocation: URL, newLocation: URL) =>
|
||||
currentLocation.pathname !== newLocation.pathname;
|
||||
|
||||
useEffect(() => {
|
||||
const currentLocation = new URL(window.location.href);
|
||||
|
||||
const removeStartEventListener = router.on(
|
||||
'start',
|
||||
(event) =>
|
||||
canTransition(currentLocation, event.detail.visit.url) &&
|
||||
flipClass(TRANSITION_OUT_CLASS, TRANSITION_IN_CLASS)
|
||||
);
|
||||
const removefinishEventListener = router.on(
|
||||
'finish',
|
||||
(event) =>
|
||||
canTransition(currentLocation, event.detail.visit.url) &&
|
||||
flipClass(TRANSITION_IN_CLASS, TRANSITION_OUT_CLASS)
|
||||
);
|
||||
|
||||
return () => {
|
||||
removeStartEventListener();
|
||||
removefinishEventListener();
|
||||
};
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<TuyauProvider client={tuyauClient}>
|
||||
<ColorSchemeScript />
|
||||
|
||||
@@ -77,3 +77,27 @@ export const buildUrl = (url: string, params: Record<string, string>) => {
|
||||
});
|
||||
return urlObj.toString();
|
||||
};
|
||||
|
||||
export function getCurrentPathAndSearch(): string {
|
||||
if (typeof window === 'undefined') return '';
|
||||
return `${window.location.pathname}${window.location.search}`;
|
||||
}
|
||||
|
||||
export function getPathAndSearchFromRaw(rawUrl: string): string {
|
||||
try {
|
||||
const url = new URL(rawUrl, window.location.origin);
|
||||
return `${url.pathname}${url.search}`;
|
||||
} catch {
|
||||
return rawUrl;
|
||||
}
|
||||
}
|
||||
|
||||
export function restartCssAnimation(
|
||||
element: HTMLElement,
|
||||
className: string
|
||||
): void {
|
||||
element.classList.remove(className);
|
||||
// Force reflow to ensure animation restarts when re-adding the class
|
||||
void element.offsetWidth;
|
||||
element.classList.add(className);
|
||||
}
|
||||
|
||||
@@ -15,3 +15,5 @@ export type InertiaPage<
|
||||
> = T & {
|
||||
auth: Auth;
|
||||
};
|
||||
|
||||
export type InertiaSuccessEvent = CustomEvent<InertiaSuccessDetail>;
|
||||
Reference in New Issue
Block a user