feature/comparison-slider #19
@@ -9,38 +9,26 @@
|
||||
- Responsive layout with Tailwind breakpoints for font sizing.
|
||||
- Performance optimized using offscreen canvas for measurements and transform-based animations.
|
||||
-->
|
||||
<script lang="ts" generics="T extends { name: string; id: string }">
|
||||
<script lang="ts">
|
||||
import { displayedFontsStore } from '$features/DisplayFont';
|
||||
import {
|
||||
createCharacterComparison,
|
||||
createTypographyControl,
|
||||
} from '$shared/lib';
|
||||
import type { LineData } from '$shared/lib';
|
||||
import { cubicOut } from 'svelte/easing';
|
||||
import { Spring } from 'svelte/motion';
|
||||
import { fly } from 'svelte/transition';
|
||||
import CharacterSlot from './components/CharacterSlot.svelte';
|
||||
import ControlsWrapper from './components/ControlsWrapper.svelte';
|
||||
import Labels from './components/Labels.svelte';
|
||||
import SliderLine from './components/SliderLine.svelte';
|
||||
|
||||
interface Props<T extends { name: string; id: string }> {
|
||||
/**
|
||||
* First font definition ({name, id})
|
||||
*/
|
||||
fontA: T;
|
||||
/**
|
||||
* Second font definition ({name, id})
|
||||
*/
|
||||
fontB: T;
|
||||
/**
|
||||
* Text to display and compare
|
||||
*/
|
||||
text?: string;
|
||||
}
|
||||
|
||||
let {
|
||||
fontA,
|
||||
fontB,
|
||||
text = $bindable('The quick brown fox jumps over the lazy dog'),
|
||||
}: Props<T> = $props();
|
||||
// Displayed text
|
||||
let text = $state('The quick brown fox jumps over the lazy dog...');
|
||||
// Pair of fonts to compare
|
||||
const fontA = $derived(displayedFontsStore.fontA);
|
||||
const fontB = $derived(displayedFontsStore.fontB);
|
||||
|
||||
let container: HTMLElement | undefined = $state();
|
||||
let controlsWrapperElement = $state<HTMLDivElement | null>(null);
|
||||
@@ -98,7 +86,6 @@ function handleMove(e: PointerEvent) {
|
||||
|
||||
function startDragging(e: PointerEvent) {
|
||||
if (e.target === controlsWrapperElement || controlsWrapperElement?.contains(e.target as Node)) {
|
||||
console.log('Pointer down on controls wrapper');
|
||||
e.stopPropagation();
|
||||
return;
|
||||
}
|
||||
@@ -162,82 +149,87 @@ $effect(() => {
|
||||
- Font Family switches based on `isPast`
|
||||
- Transitions/Transforms provide the "morph" feel
|
||||
-->
|
||||
<CharacterSlot
|
||||
{char}
|
||||
{proximity}
|
||||
{isPast}
|
||||
weight={weightControl.value}
|
||||
size={sizeControl.value}
|
||||
fontAName={fontA.name}
|
||||
fontBName={fontB.name}
|
||||
/>
|
||||
{#if fontA && fontB}
|
||||
<CharacterSlot
|
||||
{char}
|
||||
{proximity}
|
||||
{isPast}
|
||||
weight={weightControl.value}
|
||||
size={sizeControl.value}
|
||||
fontAName={fontA.name}
|
||||
fontBName={fontB.name}
|
||||
/>
|
||||
{/if}
|
||||
{/each}
|
||||
</div>
|
||||
{/snippet}
|
||||
|
||||
<!-- Hidden canvas used for text measurement by the helper -->
|
||||
<canvas bind:this={measureCanvas} class="hidden" width="1" height="1"></canvas>
|
||||
{#if fontA && fontB}
|
||||
<!-- Hidden canvas used for text measurement by the helper -->
|
||||
<canvas bind:this={measureCanvas} class="hidden" width="1" height="1"></canvas>
|
||||
|
||||
<div class="relative">
|
||||
<div
|
||||
bind:this={container}
|
||||
role="slider"
|
||||
tabindex="0"
|
||||
aria-valuenow={Math.round(sliderPos)}
|
||||
aria-label="Font comparison slider"
|
||||
onpointerdown={startDragging}
|
||||
class="
|
||||
group relative w-full py-16 px-6 sm:py-24 sm:px-12 overflow-hidden
|
||||
bg-indigo-50 rounded-[2.5rem] border border-slate-100 shadow-2xl
|
||||
select-none touch-none cursor-ew-resize min-h-100 flex flex-col justify-center
|
||||
"
|
||||
class:box-shadow={'-20px 20px 60px #bebebe, 20px -20px 60px #ffffff;'}
|
||||
>
|
||||
<!-- Background Gradient Accent -->
|
||||
<div class="relative">
|
||||
<div
|
||||
bind:this={container}
|
||||
role="slider"
|
||||
tabindex="0"
|
||||
aria-valuenow={Math.round(sliderPos)}
|
||||
aria-label="Font comparison slider"
|
||||
onpointerdown={startDragging}
|
||||
class="
|
||||
absolute inset-0 bg-linear-to-br
|
||||
from-slate-50/50 via-white to-slate-100/50
|
||||
opacity-50 pointer-events-none
|
||||
group relative w-full py-16 px-6 sm:py-24 sm:px-12 overflow-hidden
|
||||
bg-indigo-50 rounded-[2.5rem] border border-slate-100 shadow-2xl
|
||||
select-none touch-none cursor-ew-resize min-h-100 flex flex-col justify-center
|
||||
"
|
||||
class:box-shadow={'-20px 20px 60px #bebebe, 20px -20px 60px #ffffff;'}
|
||||
in:fly={{ y: 0, x: -50, duration: 300, easing: cubicOut, opacity: 0.2 }}
|
||||
>
|
||||
<!-- Background Gradient Accent -->
|
||||
<div
|
||||
class="
|
||||
absolute inset-0 bg-linear-to-br
|
||||
from-slate-50/50 via-white to-slate-100/50
|
||||
opacity-50 pointer-events-none
|
||||
"
|
||||
>
|
||||
</div>
|
||||
|
||||
<!-- Text Rendering Container -->
|
||||
<div
|
||||
class="
|
||||
relative flex flex-col items-center gap-4
|
||||
text-4xl sm:text-5xl md:text-6xl lg:text-7xl font-bold leading-[1.15]
|
||||
z-10 pointer-events-none text-center
|
||||
"
|
||||
style:perspective="1000px"
|
||||
>
|
||||
{#each charComparison.lines as line, lineIndex}
|
||||
<div
|
||||
class="relative w-full whitespace-nowrap"
|
||||
style:height={`${heightControl.value}em`}
|
||||
style:display="flex"
|
||||
style:align-items="center"
|
||||
style:justify-content="center"
|
||||
>
|
||||
{@render renderLine(line, lineIndex)}
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
<SliderLine {sliderPos} {isDragging} />
|
||||
</div>
|
||||
|
||||
<!-- Text Rendering Container -->
|
||||
<div
|
||||
class="
|
||||
relative flex flex-col items-center gap-4
|
||||
text-4xl sm:text-5xl md:text-6xl lg:text-7xl font-bold leading-[1.15]
|
||||
z-10 pointer-events-none text-center
|
||||
"
|
||||
style:perspective="1000px"
|
||||
>
|
||||
{#each charComparison.lines as line, lineIndex}
|
||||
<div
|
||||
class="relative w-full whitespace-nowrap"
|
||||
style:height={`${heightControl.value}em`}
|
||||
style:display="flex"
|
||||
style:align-items="center"
|
||||
style:justify-content="center"
|
||||
>
|
||||
{@render renderLine(line, lineIndex)}
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
<!-- Visual Components -->
|
||||
<SliderLine {sliderPos} {isDragging} />
|
||||
<Labels {fontA} {fontB} {sliderPos} />
|
||||
<Labels fontA={fontA} fontB={fontB} {sliderPos} />
|
||||
<!-- Since there're slider controls inside we put them outside the main one -->
|
||||
<ControlsWrapper
|
||||
bind:wrapper={controlsWrapperElement}
|
||||
{sliderPos}
|
||||
{isDragging}
|
||||
bind:text={text}
|
||||
containerWidth={container?.clientWidth}
|
||||
weightControl={weightControl}
|
||||
sizeControl={sizeControl}
|
||||
heightControl={heightControl}
|
||||
/>
|
||||
</div>
|
||||
<!-- Since there're slider controls inside we put them outside the main one -->
|
||||
<ControlsWrapper
|
||||
bind:wrapper={controlsWrapperElement}
|
||||
{sliderPos}
|
||||
{isDragging}
|
||||
bind:text={text}
|
||||
containerWidth={container?.clientWidth}
|
||||
weightControl={weightControl}
|
||||
sizeControl={sizeControl}
|
||||
heightControl={heightControl}
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
Reference in New Issue
Block a user