Lib: Remove previous lazy loading approach and replace with static aspect ratios

The element size must be valid before it is loaded, this is a problem
for cards, as they adapt to their content's size.
Previously I tried to load the first card non-lazily and measure its
dimensions for the next cards, but that was not stable on viewport
changes (could have measured the aspect ratio instead...).
Now, all the aspect ratios are just measured and defined manually,
stupid but simple.
This commit is contained in:
2024-12-16 20:06:56 +01:00
parent d398ab67e0
commit f0c568b982
6 changed files with 83 additions and 78 deletions

View File

@ -6,7 +6,12 @@
import type { Driver } from "$lib/schema";
import Input from "./Input.svelte";
import LazyDropdown, { type LazyDropdownOption } from "./LazyDropdown.svelte";
import { DRIVER_HEADSHOT_HEIGHT, DRIVER_HEADSHOT_WIDTH } from "$lib/config";
import {
DRIVER_CARD_ASPECT_HEIGHT,
DRIVER_CARD_ASPECT_WIDTH,
DRIVER_HEADSHOT_HEIGHT,
DRIVER_HEADSHOT_WIDTH,
} from "$lib/config";
interface DriverCardProps {
/** The [Driver] object used to prefill values. */
@ -43,7 +48,8 @@
</script>
<LazyCard
group="DriverCard"
cardwidth={DRIVER_CARD_ASPECT_WIDTH}
cardheight={DRIVER_CARD_ASPECT_HEIGHT}
imgsrc={driver?.headshot_url ?? headshot_template}
imgwidth={DRIVER_HEADSHOT_WIDTH}
imgheight={DRIVER_HEADSHOT_HEIGHT}

View File

@ -1,12 +1,3 @@
<script lang="ts" module>
// The first element of a group of cards (e.g. driver cards or team cards)
// will register its height here, once its fully loaded.
// This height is then used as the height of following components.
// This is necessary, because for lazy loading depending on viewport intersection,
// the elements must have their actual height from the beginning.
let group_heights: { [key: string]: number } = {};
</script>
<script lang="ts">
import type { Snippet } from "svelte";
import LazyImage from "./LazyImage.svelte";
@ -30,11 +21,11 @@
/** Hide the header image element. It can be shown by removing the "hidden" property using JS and the imgid. */
imghidden?: boolean;
/** Enable to give the card the "w-full" class. */
fullwidth?: boolean;
/** The aspect ratio width used to reserve card space (while its loading) */
cardwidth: number;
/** The group this card belongs to (e.g. "driver" or "race"). All cards that have the same contents (more specifically, height) may be assigned to the same group. */
group: string;
/** The aspect ratio height used to reserve card space (while its loading) */
cardheight: number;
}
let {
@ -44,76 +35,46 @@
imgwidth,
imgheight,
imghidden = false,
fullwidth = false,
group,
cardwidth,
cardheight,
...restProps
}: CardProps = $props();
let load: boolean = $state(false);
const lazy_visible_handler = () => {
console.log("Hi");
load = true;
};
const set_group_height = (node: HTMLElement) => {
if (group_heights[group]) return;
group_heights[group] = node.getBoundingClientRect().height;
// console.log(`Set card group hight: ${group}: ${group_heights[group]}px`);
};
</script>
<!-- TODO: This component needs to know its own height, otherwise the intersection observer doesn't work -->
<!-- (all elements are visible at once, so no lazy loading...) -->
<div use:lazyload onLazyVisible={lazy_visible_handler} style="width: 100%;">
{#if group_heights[group]}
<!-- A card has already loaded and determined the height for cards of this group -->
<div
class="card overflow-hidden bg-white shadow {fullwidth ? 'w-full' : 'w-auto'}"
style="height: {group_heights[group]}px;"
>
<!-- Allow empty strings for images that only appear after user action -->
{#if imgsrc !== undefined}
<LazyImage
id={imgid}
src={imgsrc}
{imgwidth}
{imgheight}
alt="Card header"
draggable="false"
class="select-none shadow"
hidden={imghidden}
/>
{/if}
<!-- Only lazyload children, as the image is already lazy (also the image fade would break) -->
{#if load}
<div class="p-2" {...restProps}>
{@render children()}
</div>
{/if}
</div>
{:else}
<!-- This card is the first one to load in the group, so it is not lazy-loaded to determine the height -->
<div
class="card overflow-hidden bg-white shadow {fullwidth ? 'w-full' : 'w-auto'}"
use:set_group_height
>
<!-- Allow empty strings for images that only appear after user action -->
{#if imgsrc !== undefined}
<LazyImage
id={imgid}
src={imgsrc}
{imgwidth}
{imgheight}
alt="Card header"
draggable="false"
class="select-none shadow"
hidden={imghidden}
/>
{/if}
<div
use:lazyload
onLazyVisible={lazy_visible_handler}
style="width: 100%; aspect-ratio: {cardwidth} / {cardheight};"
>
<div class="card w-full overflow-hidden bg-white shadow">
<!-- Allow empty strings for images that only appear after user action -->
{#if imgsrc !== undefined}
<LazyImage
id={imgid}
src={imgsrc}
{imgwidth}
{imgheight}
alt="Card header"
draggable="false"
class="select-none shadow"
hidden={imghidden}
/>
{/if}
<!-- Only lazyload children, as the image is already lazy (also the image fade would break) -->
{#if load}
<div class="p-2" {...restProps}>
{@render children()}
</div>
</div>
{/if}
{/if}
</div>
</div>

View File

@ -6,7 +6,12 @@
import type { Race } from "$lib/schema";
import Input from "./Input.svelte";
import { format } from "date-fns";
import { RACE_PICTOGRAM_HEIGHT, RACE_PICTOGRAM_WIDTH } from "$lib/config";
import {
RACE_CARD_ASPECT_HEIGHT,
RACE_CARD_ASPECT_WIDTH,
RACE_PICTOGRAM_HEIGHT,
RACE_PICTOGRAM_WIDTH,
} from "$lib/config";
interface RaceCardProps {
/** The [Race] object used to prefill values. */
@ -56,7 +61,8 @@
</script>
<LazyCard
group="RaceCard"
cardwidth={RACE_CARD_ASPECT_WIDTH}
cardheight={RACE_CARD_ASPECT_HEIGHT}
imgsrc={race?.pictogram_url ?? pictogram_template}
imgwidth={RACE_PICTOGRAM_WIDTH}
imgheight={RACE_PICTOGRAM_HEIGHT}

View File

@ -5,7 +5,12 @@
import { get_by_value } from "$lib/database";
import LazyDropdown, { type LazyDropdownOption } from "./LazyDropdown.svelte";
import type { Action } from "svelte/action";
import { DRIVER_HEADSHOT_HEIGHT, DRIVER_HEADSHOT_WIDTH } from "$lib/config";
import {
DRIVER_HEADSHOT_HEIGHT,
DRIVER_HEADSHOT_WIDTH,
SUBSTITUTION_CARD_ASPECT_HEIGHT,
SUBSTITUTION_CARD_ASPECT_WIDTH,
} from "$lib/config";
interface SubstitutionCardProps {
/** The [Substitution] object used to prefill values. */
@ -77,7 +82,8 @@
</script>
<LazyCard
group="SubstitutionCard"
cardwidth={SUBSTITUTION_CARD_ASPECT_WIDTH}
cardheight={SUBSTITUTION_CARD_ASPECT_HEIGHT}
imgsrc={get_by_value(drivers, "id", substitution?.substitute ?? "")?.headshot_url ??
headshot_template}
imgwidth={DRIVER_HEADSHOT_WIDTH}

View File

@ -4,7 +4,12 @@
import Button from "./Button.svelte";
import type { Team } from "$lib/schema";
import Input from "./Input.svelte";
import { TEAM_LOGO_HEIGHT, TEAM_LOGO_WIDTH } from "$lib/config";
import {
TEAM_CARD_ASPECT_HEIGHT,
TEAM_CARD_ASPECT_WIDTH,
TEAM_LOGO_HEIGHT,
TEAM_LOGO_WIDTH,
} from "$lib/config";
import LazyCard from "./LazyCard.svelte";
interface TeamCardProps {
@ -30,7 +35,8 @@
</script>
<LazyCard
group="TeamCard"
cardwidth={TEAM_CARD_ASPECT_WIDTH}
cardheight={TEAM_CARD_ASPECT_HEIGHT}
imgsrc={team?.logo_url ?? logo_template}
imgwidth={TEAM_LOGO_WIDTH}
imgheight={TEAM_LOGO_HEIGHT}

View File

@ -1,3 +1,10 @@
// Many aspect ratios are predefined here.
// This is terrible, since they need to be updated if the HTML changes.
// I tried to determine these dynamically by loading a "sample" element
// and measuring its width/height, but this was not reliable:
// When changing the viewport size, measured heights were no longer accurate.
// Image aspect ratios
export const AVATAR_WIDTH: number = 256;
export const AVATAR_HEIGHT: number = 256;
@ -9,3 +16,16 @@ export const DRIVER_HEADSHOT_HEIGHT: number = 512;
export const RACE_PICTOGRAM_WIDTH: number = 512;
export const RACE_PICTOGRAM_HEIGHT: number = 384;
// Card aspect ratios
export const TEAM_CARD_ASPECT_WIDTH: number = 413;
export const TEAM_CARD_ASPECT_HEIGHT: number = 438;
export const DRIVER_CARD_ASPECT_WIDTH: number = 411;
export const DRIVER_CARD_ASPECT_HEIGHT: number = 769;
export const RACE_CARD_ASPECT_WIDTH: number = 497;
export const RACE_CARD_ASPECT_HEIGHT: number = 879;
export const SUBSTITUTION_CARD_ASPECT_WIDTH: number = 413;
export const SUBSTITUTION_CARD_ASPECT_HEIGHT: number = 625;