feat(FontApplicator): remove IntersectionObserver to ease the product, font applying logic is entirely in the VirtualList

This commit is contained in:
Ilia Mashkov
2026-02-12 11:14:22 +03:00
parent d3297d519f
commit 5e3929575d

View File

@@ -2,11 +2,10 @@
Component: FontApplicator Component: FontApplicator
Loads fonts from fontshare with link tag Loads fonts from fontshare with link tag
- Loads font only if it's not already applied - Loads font only if it's not already applied
- Uses IntersectionObserver to detect when font is visible - Reacts to font load status to show/hide content
- Adds smooth transition when font appears - Adds smooth transition when font appears
--> -->
<script lang="ts"> <script lang="ts">
import { getFontUrl } from '$entities/Font/lib';
import { cn } from '$shared/shadcn/utils/shadcn-utils'; import { cn } from '$shared/shadcn/utils/shadcn-utils';
import type { Snippet } from 'svelte'; import type { Snippet } from 'svelte';
import { prefersReducedMotion } from 'svelte/motion'; import { prefersReducedMotion } from 'svelte/motion';
@@ -34,47 +33,27 @@ interface Props {
children?: Snippet; children?: Snippet;
} }
let { font, weight = 400, className, children }: Props = $props(); let {
let element: Element; font,
weight = 400,
className,
children,
}: Props = $props();
// Track if the user has actually scrolled this into view const status = $derived(
let hasEnteredViewport = $state(false); appliedFontsManager.getFontStatus(
const status = $derived(appliedFontsManager.getFontStatus(font.id, weight, font.features.isVariable)); font.id,
$effect(() => {
if (status === 'loaded' || status === 'error') {
hasEnteredViewport = true;
return;
}
const observer = new IntersectionObserver(entries => {
if (entries[0].isIntersecting) {
hasEnteredViewport = true;
const url = getFontUrl(font, weight);
// Touch ensures it's in the queue.
// It's safe to call this even if VirtualList called it
// (Manager dedupes based on key)
if (url) {
appliedFontsManager.touch([{
id: font.id,
weight, weight,
name: font.name, font.features.isVariable,
url, ),
isVariable: font.features.isVariable, );
}]);
}
observer.unobserve(element); // The "Show" condition: Font is loaded OR it errored out OR it's a noTouch preview (like in search)
} const shouldReveal = $derived.by(() => {
}); if (noTouch) return true;
return status === 'loaded' || status === 'error';
if (element) observer.observe(element);
return () => observer.disconnect();
}); });
// The "Show" condition: Element is in view AND (Font is ready OR it errored out)
const shouldReveal = $derived(hasEnteredViewport && (status === 'loaded'));
const transitionClasses = $derived( const transitionClasses = $derived(
prefersReducedMotion.current prefersReducedMotion.current
? 'transition-none' // Disable CSS transitions if motion is reduced ? 'transition-none' // Disable CSS transitions if motion is reduced
@@ -83,12 +62,14 @@ const transitionClasses = $derived(
</script> </script>
<div <div
bind:this={element} style:font-family={shouldReveal
style:font-family={shouldReveal ? `'${font.name}'` : 'system-ui, -apple-system, sans-serif'} ? `'${font.name}'`
: 'system-ui, -apple-system, sans-serif'}
class={cn( class={cn(
transitionClasses, transitionClasses,
// If reduced motion is on, we skip the transform/blur entirely // If reduced motion is on, we skip the transform/blur entirely
!shouldReveal && !prefersReducedMotion.current !shouldReveal
&& !prefersReducedMotion.current
&& 'opacity-50 scale-[0.95] blur-sm', && 'opacity-50 scale-[0.95] blur-sm',
!shouldReveal && prefersReducedMotion.current && 'opacity-0', // Still hide until font is ready, but no movement !shouldReveal && prefersReducedMotion.current && 'opacity-0', // Still hide until font is ready, but no movement
shouldReveal && 'opacity-100 scale-100 blur-0', shouldReveal && 'opacity-100 scale-100 blur-0',