fix: prevent flickering if no background image is set on login page

This commit is contained in:
Elias Schneider
2026-04-12 19:48:40 +02:00
parent 33cceeafa8
commit 027e6f078d

View File

@@ -1,7 +1,5 @@
<script module lang="ts"> <script module lang="ts">
// Persist the last failing background image URL across route remounts so let backgroundImageExists = $state<boolean | undefined>(undefined);
// login pages without a background do not briefly retry the image and stutter.
let persistedMissingBackgroundImageUrl: string | undefined;
</script> </script>
<script lang="ts"> <script lang="ts">
@@ -11,8 +9,9 @@
import appConfigStore from '$lib/stores/application-configuration-store'; import appConfigStore from '$lib/stores/application-configuration-store';
import { cachedBackgroundImage } from '$lib/utils/cached-image-util'; import { cachedBackgroundImage } from '$lib/utils/cached-image-util';
import { cn } from '$lib/utils/style'; import { cn } from '$lib/utils/style';
import { type Snippet } from 'svelte'; import { onMount, type Snippet } from 'svelte';
import { MediaQuery } from 'svelte/reactivity'; import { MediaQuery } from 'svelte/reactivity';
import { fade } from 'svelte/transition';
import * as Card from './ui/card'; import * as Card from './ui/card';
let { let {
@@ -23,32 +22,21 @@
showAlternativeSignInMethodButton?: boolean; showAlternativeSignInMethodButton?: boolean;
} = $props(); } = $props();
let missingBackgroundImageUrl = $state<string | undefined>(persistedMissingBackgroundImageUrl);
let loadedBackgroundImageUrl = $state<string | undefined>();
let isInitialLoad = $state(false); let isInitialLoad = $state(false);
let backgroundImageUrl = $derived(cachedBackgroundImage.getUrl()); let animate = $derived(isInitialLoad && !$appConfigStore.disableAnimations);
let imageError = $derived(missingBackgroundImageUrl === backgroundImageUrl);
let imageLoaded = $derived(loadedBackgroundImageUrl === backgroundImageUrl); onMount(async () => {
let animate = $derived(isInitialLoad && imageLoaded && !$appConfigStore.disableAnimations); fetch(cachedBackgroundImage.getUrl(), {
method: 'HEAD'
})
.then(async (res) => (backgroundImageExists = res.ok))
.catch(() => (backgroundImageExists = false));
});
afterNavigate((e) => { afterNavigate((e) => {
isInitialLoad = !e?.from?.url; isInitialLoad = !e?.from?.url;
}); });
function onBackgroundImageLoad() {
loadedBackgroundImageUrl = backgroundImageUrl;
if (persistedMissingBackgroundImageUrl === backgroundImageUrl) {
persistedMissingBackgroundImageUrl = undefined;
missingBackgroundImageUrl = undefined;
}
}
function onBackgroundImageError() {
loadedBackgroundImageUrl = undefined;
persistedMissingBackgroundImageUrl = backgroundImageUrl;
missingBackgroundImageUrl = backgroundImageUrl;
}
const isDesktop = new MediaQuery('min-width: 1024px'); const isDesktop = new MediaQuery('min-width: 1024px');
let alternativeSignInButton = $state({ let alternativeSignInButton = $state({
href: '/login/alternative', href: '/login/alternative',
@@ -70,11 +58,14 @@
}); });
</script> </script>
{#if isDesktop.current} {#if backgroundImageExists === undefined}
<div class="h-screen items-center overflow-hidden text-center flex justify-center"> <div class="bg-background h-screen"></div>
{:else if isDesktop.current}
<div in:fade={{ duration: 150 }} class="h-screen items-center overflow-hidden text-center">
<div <div
class="flex h-full w-[650px] 2xl:w-[800px] p-16 {cn( class="relative z-10 flex h-full p-16 {cn(
showAlternativeSignInMethodButton && 'pb-0' showAlternativeSignInMethodButton && 'pb-0',
backgroundImageExists && 'w-[650px] 2xl:w-[800px]'
)}" )}"
> >
<div class="flex h-full w-full flex-col overflow-hidden"> <div class="flex h-full w-full flex-col overflow-hidden">
@@ -94,15 +85,17 @@
</div> </div>
</div> </div>
{#if !imageError} {#if backgroundImageExists}
<!-- Background image --> <!-- Background image -->
<div class="m-6 flex h-[calc(100vh-3rem)] overflow-hidden rounded-[40px]"> <div
class="absolute top-0 right-0 left-500px bottom-0 z-0 overflow-hidden rounded-[40px] m-6"
>
<img <img
src={backgroundImageUrl} src={cachedBackgroundImage.getUrl()}
class="h-full object-cover {cn(animate && 'animate-bg-zoom')}" class="{cn(
animate && 'animate-bg-zoom'
)} h-screen object-cover w-[calc(100vw-650px)] 2xl:w-[calc(100vw-800px)]"
alt={m.login_background()} alt={m.login_background()}
onload={onBackgroundImageLoad}
onerror={onBackgroundImageError}
/> />
</div> </div>
{/if} {/if}
@@ -112,9 +105,14 @@
class="flex h-screen items-center justify-center bg-cover bg-center text-center" class="flex h-screen items-center justify-center bg-cover bg-center text-center"
style="background-image: url({cachedBackgroundImage.getUrl()});" style="background-image: url({cachedBackgroundImage.getUrl()});"
> >
<Card.Root class="mx-3 w-full max-w-md"> <Card.Root
class={{
'mx-3 w-full max-w-md': true,
'bg-transparent border-0': !backgroundImageExists
}}
>
<Card.CardContent <Card.CardContent
class="px-4 py-10 sm:p-10 {showAlternativeSignInMethodButton ? 'pb-3 sm:pb-3' : ''}" class="px-4 py-10 sm:p-10 {showAlternativeSignInMethodButton ? 'pb-3 sm:pb-3' : ''} "
> >
{@render children()} {@render children()}
{#if showAlternativeSignInMethodButton} {#if showAlternativeSignInMethodButton}