fix(web): prevent thumbhashes from covering search bar (#20720)

The thumbhash had a z-index setting which meant it would cover the search bar,
and would always cause weird animations when scrolling up in search results.

This is fixable by removing the z-index and moving it in front the other
elements to get a naturally higher higher z-index preference.
This commit is contained in:
Thomas 2025-08-06 22:57:51 +01:00 committed by GitHub
parent f1c494ef97
commit f36efd128b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 25 additions and 57 deletions

View File

@ -1,22 +0,0 @@
import ImageThumbnail from '$lib/components/assets/thumbnail/image-thumbnail.svelte';
import { render } from '@testing-library/svelte';
describe('ImageThumbnail component', () => {
beforeAll(() => {
Element.prototype.animate = vi.fn().mockImplementation(() => ({
cancel: () => {},
}));
});
it('shows thumbhash while image is loading', () => {
const sut = render(ImageThumbnail, {
url: 'http://localhost/img.png',
altText: 'test',
base64ThumbHash: '1QcSHQRnh493V4dIh4eXh1h4kJUI',
widthStyle: '250px',
});
const thumbhash = sut.getByTestId('thumbhash');
expect(thumbhash).not.toBeFalsy();
});
});

View File

@ -45,4 +45,15 @@ describe('Thumbnail component', () => {
const tabbables = getTabbable(container!);
expect(tabbables.length).toBe(0);
});
it('shows thumbhash while image is loading', () => {
const asset = assetFactory.build({ originalPath: 'image.jpg', originalMimeType: 'image/jpeg' });
const sut = render(Thumbnail, {
asset,
selected: true,
});
const thumbhash = sut.getByTestId('thumbhash');
expect(thumbhash).not.toBeFalsy();
});
});

View File

@ -1,13 +1,10 @@
<script lang="ts">
import { thumbhash } from '$lib/actions/thumbhash';
import BrokenAsset from '$lib/components/assets/broken-asset.svelte';
import Icon from '$lib/components/elements/icon.svelte';
import { cancelImageUrl } from '$lib/utils/sw-messaging';
import { TUNABLES } from '$lib/utils/tunables';
import { mdiEyeOffOutline } from '@mdi/js';
import type { ActionReturn } from 'svelte/action';
import type { ClassValue } from 'svelte/elements';
import { fade } from 'svelte/transition';
interface Props {
url: string;
@ -15,7 +12,6 @@
title?: string | null;
heightStyle?: string | undefined;
widthStyle: string;
base64ThumbHash?: string | null;
curve?: boolean;
shadow?: boolean;
circle?: boolean;
@ -33,7 +29,6 @@
title = null,
heightStyle = undefined,
widthStyle,
base64ThumbHash = null,
curve = false,
shadow = false,
circle = false,
@ -45,10 +40,6 @@
brokenAssetClass = '',
}: Props = $props();
let {
IMAGE_THUMBNAIL: { THUMBHASH_FADE_DURATION },
} = TUNABLES;
let loaded = $state(false);
let errored = $state(false);
@ -100,7 +91,6 @@
alt={loaded || errored ? altText : ''}
{title}
class={['object-cover', optionalClasses, imageClass]}
class:opacity-0={!thumbhash && !loaded}
draggable="false"
/>
{/if}
@ -110,19 +100,3 @@
<Icon {title} path={mdiEyeOffOutline} size="2em" class={hiddenIconClass} />
</div>
{/if}
{#if base64ThumbHash && (!loaded || errored)}
<canvas
use:thumbhash={{ base64ThumbHash }}
data-testid="thumbhash"
style:width={widthStyle}
style:height={heightStyle}
{title}
class="absolute top-0 object-cover"
class:rounded-xl={curve}
class:shadow-lg={shadow}
class:rounded-full={circle}
draggable="false"
out:fade={{ duration: THUMBHASH_FADE_DURATION }}
></canvas>
{/if}

View File

@ -230,15 +230,6 @@
]}
data-outline
></div>
{#if (!loaded || thumbError) && asset.thumbhash}
<canvas
use:thumbhash={{ base64ThumbHash: asset.thumbhash }}
class="absolute object-cover z-1"
style:width="{width}px"
style:height="{height}px"
out:fade={{ duration: THUMBHASH_FADE_DURATION }}
></canvas>
{/if}
<div
class={['group absolute -top-[0px] -bottom-[0px]', { 'cursor-not-allowed': disabled, 'cursor-pointer': !disabled }]}
@ -352,7 +343,21 @@
/>
</div>
{/if}
{#if (!loaded || thumbError) && asset.thumbhash}
<canvas
use:thumbhash={{ base64ThumbHash: asset.thumbhash }}
data-testid="thumbhash"
class="absolute top-0 object-cover"
style:width="{width}px"
style:height="{height}px"
class:rounded-xl={selected}
draggable="false"
out:fade={{ duration: THUMBHASH_FADE_DURATION }}
></canvas>
{/if}
</div>
{#if selectionCandidate}
<div
class="absolute top-0 h-full w-full bg-immich-primary opacity-40"