feat(ComparisonSlider): integrate loader and add animations for appearance/disappearance

This commit is contained in:
Ilia Mashkov
2026-02-06 12:04:32 +03:00
parent 3ed63562b7
commit b304e841de
3 changed files with 43 additions and 27 deletions

View File

@@ -15,8 +15,10 @@ import {
createTypographyControl, createTypographyControl,
} from '$shared/lib'; } from '$shared/lib';
import type { LineData } from '$shared/lib'; import type { LineData } from '$shared/lib';
import { Loader } from '$shared/ui';
import { comparisonStore } from '$widgets/ComparisonSlider/model'; import { comparisonStore } from '$widgets/ComparisonSlider/model';
import { Spring } from 'svelte/motion'; import { Spring } from 'svelte/motion';
import { fade } from 'svelte/transition';
import CharacterSlot from './components/CharacterSlot.svelte'; import CharacterSlot from './components/CharacterSlot.svelte';
import ControlsWrapper from './components/ControlsWrapper.svelte'; import ControlsWrapper from './components/ControlsWrapper.svelte';
import Labels from './components/Labels.svelte'; import Labels from './components/Labels.svelte';
@@ -26,6 +28,8 @@ import SliderLine from './components/SliderLine.svelte';
const fontA = $derived(comparisonStore.fontA); const fontA = $derived(comparisonStore.fontA);
const fontB = $derived(comparisonStore.fontB); const fontB = $derived(comparisonStore.fontB);
const isLoading = $derived(comparisonStore.isLoading || !comparisonStore.isReady);
let container: HTMLElement | undefined = $state(); let container: HTMLElement | undefined = $state();
let controlsWrapperElement = $state<HTMLDivElement | null>(null); let controlsWrapperElement = $state<HTMLDivElement | null>(null);
let measureCanvas: HTMLCanvasElement | undefined = $state(); let measureCanvas: HTMLCanvasElement | undefined = $state();
@@ -164,31 +168,33 @@ $effect(() => {
</div> </div>
{/snippet} {/snippet}
{#if fontA && fontB} <!-- Hidden canvas used for text measurement by the helper -->
<!-- Hidden canvas used for text measurement by the helper --> <canvas bind:this={measureCanvas} class="hidden" width="1" height="1"></canvas>
<canvas bind:this={measureCanvas} class="hidden" width="1" height="1"></canvas>
<div class="relative"> <div class="relative">
<div <div
bind:this={container} bind:this={container}
role="slider" role="slider"
tabindex="0" tabindex="0"
aria-valuenow={Math.round(sliderPos)} aria-valuenow={Math.round(sliderPos)}
aria-label="Font comparison slider" aria-label="Font comparison slider"
onpointerdown={startDragging} onpointerdown={startDragging}
class=" class="
group relative w-full py-16 px-24 sm:py-24 sm:px-24 overflow-hidden group relative w-full py-16 px-24 sm:py-24 sm:px-24 overflow-hidden
rounded-[2.5rem] rounded-[2.5rem]
select-none touch-none cursor-ew-resize min-h-100 flex flex-col justify-center select-none touch-none cursor-ew-resize min-h-100 flex flex-col justify-center
backdrop-blur-lg bg-gradient-to-br from-gray-100/70 via-white/50 to-gray-100/60 backdrop-blur-lg bg-gradient-to-br from-gray-100/70 via-white/50 to-gray-100/60
border border-gray-300/40 border border-gray-300/40
shadow-[inset_0_4px_12px_0_rgba(0,0,0,0.12),inset_0_2px_4px_0_rgba(0,0,0,0.08),0_1px_2px_0_rgba(255,255,255,0.8)] shadow-[inset_0_4px_12px_0_rgba(0,0,0,0.12),inset_0_2px_4px_0_rgba(0,0,0,0.08),0_1px_2px_0_rgba(255,255,255,0.8)]
before:absolute before:inset-0 before:rounded-[2.5rem] before:p-[1px] before:absolute before:inset-0 before:rounded-[2.5rem] before:p-[1px]
before:bg-gradient-to-br before:from-black/5 before:via-black/2 before:to-transparent before:bg-gradient-to-br before:from-black/5 before:via-black/2 before:to-transparent
before:-z-10 before:blur-sm before:-z-10 before:blur-sm
" "
> >
<!-- Text Rendering Container --> <!-- Text Rendering Container -->
{#if isLoading}
<Loader size={24} />
{:else}
<div <div
class=" class="
relative flex flex-col items-center gap-4 relative flex flex-col items-center gap-4
@@ -197,6 +203,8 @@ $effect(() => {
drop-shadow-[0_3px_6px_rgba(255,255,255,0.9)] drop-shadow-[0_3px_6px_rgba(255,255,255,0.9)]
" "
style:perspective="1000px" style:perspective="1000px"
in:fade={{ duration: 300, delay: 300 }}
out:fade={{ duration: 300 }}
> >
{#each charComparison.lines as line, lineIndex} {#each charComparison.lines as line, lineIndex}
<div <div
@@ -212,8 +220,10 @@ $effect(() => {
</div> </div>
<SliderLine {sliderPos} {isDragging} /> <SliderLine {sliderPos} {isDragging} />
</div> {/if}
</div>
{#if fontA && fontB && !isLoading}
<Labels {fontA} {fontB} {sliderPos} weight={weightControl.value} /> <Labels {fontA} {fontB} {sliderPos} weight={weightControl.value} />
<!-- Since there're slider controls inside we put them outside the main one --> <!-- Since there're slider controls inside we put them outside the main one -->
<ControlsWrapper <ControlsWrapper
@@ -226,5 +236,5 @@ $effect(() => {
{sizeControl} {sizeControl}
{heightControl} {heightControl}
/> />
</div> {/if}
{/if} </div>

View File

@@ -12,6 +12,7 @@ import { ComboControlV2 } from '$shared/ui';
import { ExpandableWrapper } from '$shared/ui'; import { ExpandableWrapper } from '$shared/ui';
import AArrowUP from '@lucide/svelte/icons/a-arrow-up'; import AArrowUP from '@lucide/svelte/icons/a-arrow-up';
import { Spring } from 'svelte/motion'; import { Spring } from 'svelte/motion';
import { fade } from 'svelte/transition';
interface Props { interface Props {
/** /**
@@ -121,6 +122,8 @@ $effect(() => {
translateX({xSpring.current}px) translateX({xSpring.current}px)
rotateZ({rotateSpring.current}deg) rotateZ({rotateSpring.current}deg)
" "
in:fade={{ duration: 300, delay: 300 }}
out:fade={{ duration: 300, delay: 300 }}
> >
<ExpandableWrapper <ExpandableWrapper
bind:element={wrapper} bind:element={wrapper}

View File

@@ -17,6 +17,7 @@ import {
} from '$shared/shadcn/ui/select'; } from '$shared/shadcn/ui/select';
import { cn } from '$shared/shadcn/utils/shadcn-utils'; import { cn } from '$shared/shadcn/utils/shadcn-utils';
import { comparisonStore } from '$widgets/ComparisonSlider/model'; import { comparisonStore } from '$widgets/ComparisonSlider/model';
import { fade } from 'svelte/transition';
interface Props<T> { interface Props<T> {
/** /**
@@ -60,6 +61,8 @@ function selectFontB(font: UnifiedFont) {
<div <div
class="z-50 pointer-events-auto" class="z-50 pointer-events-auto"
onpointerdown={(e => e.stopPropagation())} onpointerdown={(e => e.stopPropagation())}
in:fade={{ duration: 300, delay: 300 }}
out:fade={{ duration: 300, delay: 300 }}
> >
<SelectRoot type="single" disabled={!fontList.length}> <SelectRoot type="single" disabled={!fontList.length}>
<SelectTrigger <SelectTrigger