diff --git a/src/app/ui/Layout.svelte b/src/app/ui/Layout.svelte index a934407..021c334 100644 --- a/src/app/ui/Layout.svelte +++ b/src/app/ui/Layout.svelte @@ -49,8 +49,6 @@ onMount(async () => { } fontsReady = true; }); - -$inspect(fontsReady); diff --git a/src/entities/Font/model/store/appliedFontsStore/appliedFontsStore.svelte.ts b/src/entities/Font/model/store/appliedFontsStore/appliedFontsStore.svelte.ts index 9cb92b7..6a6c61a 100644 --- a/src/entities/Font/model/store/appliedFontsStore/appliedFontsStore.svelte.ts +++ b/src/entities/Font/model/store/appliedFontsStore/appliedFontsStore.svelte.ts @@ -112,7 +112,7 @@ export class AppliedFontsManager { const internalName = `f_${config.id}`; const weightRange = config.isVariable ? '100 900' : `${config.weight}`; - const font = new FontFace(config.name, `url(${config.url})`, { + const font = new FontFace(config.name, `url(${config.url}) format('woff2')`, { weight: weightRange, style: 'normal', display: 'swap', diff --git a/src/entities/Font/ui/FontApplicator/FontApplicator.svelte b/src/entities/Font/ui/FontApplicator/FontApplicator.svelte index 1ec6114..a6076ec 100644 --- a/src/entities/Font/ui/FontApplicator/FontApplicator.svelte +++ b/src/entities/Font/ui/FontApplicator/FontApplicator.svelte @@ -6,30 +6,24 @@ - Adds smooth transition when font appears -->
{ role="button" tabindex={0} class={cn( - 'will-change-transform duration-300', + 'will-change-[transform, width, height] duration-300', disabled ? 'pointer-events-none' : 'pointer-events-auto', className, )} diff --git a/src/widgets/ComparisonSlider/model/stores/comparisonStore.svelte.ts b/src/widgets/ComparisonSlider/model/stores/comparisonStore.svelte.ts index 787cca9..d277e5f 100644 --- a/src/widgets/ComparisonSlider/model/stores/comparisonStore.svelte.ts +++ b/src/widgets/ComparisonSlider/model/stores/comparisonStore.svelte.ts @@ -34,6 +34,7 @@ class ComparisonStore { #fontB = $state(); #sampleText = $state('The quick brown fox jumps over the lazy dog'); #isRestoring = $state(true); + #fontsReady = $state(false); #typography = createTypographyControlManager(DEFAULT_TYPOGRAPHY_CONTROLS_DATA, 'glyphdiff:comparison:typography'); constructor() { @@ -49,6 +50,7 @@ class ComparisonStore { // If we already have a selection, do nothing if (this.#fontA && this.#fontB) { + this.#checkFontsLoaded(); return; } @@ -66,6 +68,48 @@ class ComparisonStore { }); } + /** + * Checks if fonts are actually loaded in the browser at current weight. + * Uses CSS Font Loading API to prevent FOUT. + */ + async #checkFontsLoaded() { + if (!('fonts' in document)) { + this.#fontsReady = true; + return; + } + + this.#fontsReady = false; + + const weight = this.#typography.weight; + const size = this.#typography.renderedSize; + const fontAName = this.#fontA?.name; + const fontBName = this.#fontB?.name; + + if (!fontAName || !fontBName) return; + + try { + // Step 1: Load fonts into memory + await Promise.all([ + document.fonts.load(`${weight} ${size}px "${fontAName}"`), + document.fonts.load(`${weight} ${size}px "${fontBName}"`), + ]); + + // Step 2: Wait for browser to be ready to render + await document.fonts.ready; + + // Step 3: Force a layout/paint cycle (critical!) + await new Promise(resolve => { + requestAnimationFrame(() => { + requestAnimationFrame(resolve); // Double rAF ensures paint completes + }); + }); + + this.#fontsReady = true; + } catch (error) { + console.warn('[ComparisonStore] Font loading failed:', error); + setTimeout(() => this.#fontsReady = true, 1000); + } + } /** * Restore state from persistent storage */ @@ -141,13 +185,12 @@ class ComparisonStore { * Check if both fonts are selected */ get isReady() { - return !!this.#fontA && !!this.#fontB; + return !!this.#fontA && !!this.#fontB && this.#fontsReady; } get isLoading() { - return this.#isRestoring; + return this.#isRestoring || !this.#fontsReady; } - /** * Public initializer (optional, as constructor starts it) * Kept for compatibility if manual re-init is needed diff --git a/src/widgets/ComparisonSlider/ui/ComparisonSlider/ComparisonSlider.svelte b/src/widgets/ComparisonSlider/ui/ComparisonSlider/ComparisonSlider.svelte index e34157d..8e22e3a 100644 --- a/src/widgets/ComparisonSlider/ui/ComparisonSlider/ComparisonSlider.svelte +++ b/src/widgets/ComparisonSlider/ui/ComparisonSlider/ComparisonSlider.svelte @@ -167,10 +167,6 @@ $effect(() => { {char} {proximity} {isPast} - weight={typography.weight} - size={typography.renderedSize} - fontAName={fontA.name} - fontBName={fontB.name} /> {/if} {/each} @@ -202,7 +198,9 @@ $effect(() => { > {#if isLoading} - +
+ +
{:else}
+import { appliedFontsManager } from '$entities/Font'; +import { getFontUrl } from '$entities/Font/lib'; import { cn } from '$shared/shadcn/utils/shadcn-utils'; +import { comparisonStore } from '../../../model'; interface Props { /** @@ -18,46 +21,63 @@ interface Props { * Flag indicating whether character needed to be changed */ isPast: boolean; - /** - * Font weight of the character - */ - weight: number; - /** - * Font size of the character - */ - size: number; - /** - * Name of the font for the character after the change - */ - fontAName: string; - /** - * Name of the font for the character before the change - */ - fontBName: string; } -let { char, proximity, isPast, weight, size, fontAName, fontBName }: Props = $props(); +let { char, proximity, isPast }: Props = $props(); + +const fontA = $derived(comparisonStore.fontA); +const fontB = $derived(comparisonStore.fontB); +const typography = $derived(comparisonStore.typography); + +$effect(() => { + if (!fontA || !fontB) { + return; + } + + const urlA = getFontUrl(fontA, typography.weight); + const urlB = getFontUrl(fontB, typography.weight); + + if (!urlA || !urlB) { + return; + } + + appliedFontsManager.touch([{ + id: fontA.id, + weight: typography.weight, + name: fontA.name, + url: urlA, + isVariable: fontA.features.isVariable, + }, { + id: fontB.id, + weight: typography.weight, + name: fontB.name, + url: urlB, + isVariable: fontB.features.isVariable, + }]); +}); - 0.5 ? '0 0 15px rgba(99,102,241,0.3)' : 'none'} - style:will-change={proximity > 0 ? 'transform, font-family, color' : 'auto'} -> - {char === ' ' ? '\u00A0' : char} - + style:filter="brightness({1 + proximity * 0.2}) contrast({1 + proximity * 0.1})" + style:text-shadow={proximity > 0.5 ? '0 0 15px rgba(99,102,241,0.3)' : 'none'} + style:will-change={proximity > 0 ? 'transform, font-family, color' : 'auto'} + > + {char === ' ' ? '\u00A0' : char} + +{/if}