mirror of
https://github.com/Xevion/xevion.dev.git
synced 2026-01-31 02:26:38 -06:00
feat: add View Transitions API with persistent backgrounds
- Add fade in/out page transitions using View Transitions API - Move background/dots to root layout for persistence across routes - Hide native scrollbar immediately to prevent FOUC - Set body background in theme script to prevent flash - Increase inline style threshold for better initial render
This commit is contained in:
@@ -204,3 +204,30 @@ body {
|
||||
.os-scrollbar-handle {
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
/* View Transitions API - page transition animations */
|
||||
@keyframes page-fade-in {
|
||||
from {
|
||||
opacity: 0;
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes page-fade-out {
|
||||
from {
|
||||
opacity: 1;
|
||||
}
|
||||
to {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
::view-transition-old(root) {
|
||||
animation: page-fade-out 120ms ease-out;
|
||||
}
|
||||
|
||||
::view-transition-new(root) {
|
||||
animation: page-fade-in 150ms ease-in 50ms;
|
||||
}
|
||||
|
||||
@@ -3,6 +3,17 @@
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<style>
|
||||
/* Hide native scrollbar immediately to prevent layout shift */
|
||||
html,
|
||||
body {
|
||||
scrollbar-width: none; /* Firefox */
|
||||
-ms-overflow-style: none; /* IE/Edge */
|
||||
&::-webkit-scrollbar {
|
||||
display: none; /* Chrome, Safari, Opera */
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<script>
|
||||
(function () {
|
||||
const stored = localStorage.getItem("theme");
|
||||
@@ -13,6 +24,8 @@
|
||||
if (isDark) {
|
||||
document.documentElement.classList.add("dark");
|
||||
}
|
||||
// Set body background immediately to prevent flash
|
||||
document.body.style.backgroundColor = isDark ? "#000000" : "#ffffff";
|
||||
})();
|
||||
</script>
|
||||
%sveltekit.head%
|
||||
|
||||
@@ -1,31 +1,34 @@
|
||||
<script lang="ts">
|
||||
import { cn } from "$lib/utils";
|
||||
import type { Snippet } from "svelte";
|
||||
import Dots from "./Dots.svelte";
|
||||
import ThemeToggle from "./ThemeToggle.svelte";
|
||||
|
||||
let {
|
||||
class: className = "",
|
||||
backgroundClass = "",
|
||||
bgColor = "",
|
||||
showThemeToggle = true,
|
||||
children,
|
||||
}: {
|
||||
class?: string;
|
||||
backgroundClass?: string;
|
||||
bgColor?: string;
|
||||
showThemeToggle?: boolean;
|
||||
children?: Snippet;
|
||||
} = $props();
|
||||
</script>
|
||||
|
||||
<div
|
||||
class={cn(
|
||||
"pointer-events-none fixed inset-0 -z-20 bg-white dark:bg-black transition-colors duration-300",
|
||||
bgColor,
|
||||
)}
|
||||
></div>
|
||||
<Dots class={[backgroundClass]} />
|
||||
<!--
|
||||
Background: Public pages get their background from root +layout.svelte for persistence.
|
||||
Admin/internal pages can use bgColor prop to set their own background.
|
||||
-->
|
||||
{#if bgColor}
|
||||
<div
|
||||
class={cn(
|
||||
"pointer-events-none fixed inset-0 -z-20 transition-colors duration-300",
|
||||
bgColor,
|
||||
)}
|
||||
></div>
|
||||
{/if}
|
||||
|
||||
<main
|
||||
class={cn(
|
||||
"relative min-h-screen text-zinc-900 dark:text-zinc-50 transition-colors duration-300",
|
||||
|
||||
@@ -7,6 +7,9 @@
|
||||
import { OverlayScrollbars } from "overlayscrollbars";
|
||||
import { onMount } from "svelte";
|
||||
import { themeStore } from "$lib/stores/theme.svelte";
|
||||
import { page } from "$app/stores";
|
||||
import { onNavigate } from "$app/navigation";
|
||||
import Dots from "$lib/components/Dots.svelte";
|
||||
|
||||
let { children, data } = $props();
|
||||
|
||||
@@ -20,6 +23,31 @@
|
||||
|
||||
const metadata = $derived(data?.metadata ?? defaultMetadata);
|
||||
|
||||
// Check if current route is admin (admin has its own layout/background)
|
||||
const isAdminRoute = $derived($page.url.pathname.startsWith("/admin"));
|
||||
// Check if current route is internal (OG preview, etc.)
|
||||
const isInternalRoute = $derived($page.url.pathname.startsWith("/internal"));
|
||||
// Show global background for public pages only
|
||||
const showGlobalBackground = $derived(!isAdminRoute && !isInternalRoute);
|
||||
|
||||
// Use View Transitions API for smooth page transitions (Chrome 111+, Safari 18+)
|
||||
onNavigate((navigation) => {
|
||||
// Skip transitions for same-page navigations or if API not supported
|
||||
if (
|
||||
!document.startViewTransition ||
|
||||
navigation.from?.url.pathname === navigation.to?.url.pathname
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
return new Promise((resolve) => {
|
||||
document.startViewTransition(async () => {
|
||||
resolve();
|
||||
await navigation.complete;
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
onMount(() => {
|
||||
// Initialize theme store
|
||||
themeStore.init();
|
||||
@@ -63,4 +91,13 @@
|
||||
<meta name="twitter:image" content={metadata.ogImage} />
|
||||
</svelte:head>
|
||||
|
||||
<!-- Persistent background layer - only for public routes -->
|
||||
{#if showGlobalBackground}
|
||||
<div
|
||||
class="pointer-events-none fixed inset-0 -z-20 bg-white dark:bg-black transition-colors duration-300"
|
||||
></div>
|
||||
<Dots />
|
||||
{/if}
|
||||
|
||||
<!-- Page content - transitions handled by View Transitions API -->
|
||||
{@render children()}
|
||||
|
||||
@@ -4,7 +4,7 @@ import { vitePreprocess } from "@sveltejs/vite-plugin-svelte";
|
||||
/** @type {import('@sveltejs/kit').Config} */
|
||||
const config = {
|
||||
preprocess: vitePreprocess(),
|
||||
inlineStyleThreshold: 1000,
|
||||
inlineStyleThreshold: 2000,
|
||||
kit: {
|
||||
adapter: adapter({
|
||||
out: "build",
|
||||
|
||||
Reference in New Issue
Block a user