fix(createCharacterComparison): improve characters measurment for better magnifying presicion

This commit is contained in:
Ilia Mashkov
2026-01-30 00:54:40 +03:00
parent 1a2c44fb97
commit fbaf596fef
2 changed files with 39 additions and 31 deletions

View File

@@ -204,37 +204,41 @@ export function createCharacterComparison<
/**
* precise calculation of character state based on global slider position.
*
* @param lineIndex - Index of the line
* @param charIndex - Index of the character in the line
* @param lineData - The line data object
* @param sliderPos - Current slider position (0-100)
* @param lineElement - The line element
* @param container - The container element
* @returns Object containing proximity (0-1) and isPast (boolean)
*/
function getCharState(
lineIndex: number,
charIndex: number,
lineData: LineData,
sliderPos: number,
lineElement?: HTMLElement,
container?: HTMLElement,
) {
if (!containerWidth) return { proximity: 0, isPast: false };
if (!containerWidth || !container) {
return {
proximity: 0,
isPast: false,
};
}
const charElement = lineElement?.children[charIndex] as HTMLElement;
// Calculate the pixel position of the character relative to the CONTAINER
// 1. Find the left edge of the centered line
const lineStartOffset = (containerWidth - lineData.width) / 2;
if (!charElement) {
return { proximity: 0, isPast: false };
}
// 2. Find the character's center relative to the line
const charRelativePercent = (charIndex + 0.5) / lineData.text.length;
const charPixelPos = lineStartOffset + (charRelativePercent * lineData.width);
// Get the actual bounding box of the character
const charRect = charElement.getBoundingClientRect();
const containerRect = container.getBoundingClientRect();
// 3. Convert back to global percentage (0-100)
const charGlobalPercent = (charPixelPos / containerWidth) * 100;
// Calculate character center relative to container
const charCenter = charRect.left + (charRect.width / 2) - containerRect.left;
const charGlobalPercent = (charCenter / containerWidth) * 100;
const distance = Math.abs(sliderPos - charGlobalPercent);
// Proximity range: +/- 15% around the slider
const range = 15;
const range = 5;
const proximity = Math.max(0, 1 - distance / range);
const isPast = sliderPos > charGlobalPercent;
return { proximity, isPast };

View File

@@ -68,6 +68,8 @@ const charComparison = createCharacterComparison(
() => sizeControl.value,
);
let lineElements = $state<(HTMLElement | undefined)[]>([]);
/** Physics-based spring for smooth handle movement */
const sliderSpring = new Spring(50, {
stiffness: 0.2, // Balanced for responsiveness
@@ -138,12 +140,18 @@ $effect(() => {
{#snippet renderLine(line: LineData, lineIndex: number)}
<div
bind:this={lineElements[lineIndex]}
class="relative flex w-full justify-center items-center whitespace-nowrap"
style:height={`${heightControl.value}em`}
style:line-height={`${heightControl.value}em`}
>
{#each line.text.split('') as char, charIndex}
{@const { proximity, isPast } = charComparison.getCharState(lineIndex, charIndex, line, sliderPos)}
{@const { proximity, isPast } = charComparison.getCharState(
charIndex,
sliderPos,
lineElements[lineIndex],
container,
),}
<!--
Single Character Span
- Font Family switches based on `isPast`
@@ -177,29 +185,25 @@ $effect(() => {
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
group relative w-full py-16 px-0 sm:py-24 sm:px-0 overflow-hidden
rounded-[2.5rem]
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
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)]
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:-z-10 before:blur-sm
"
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
drop-shadow-[0_3px_6px_rgba(255,255,255,0.9)]
"
style:perspective="1000px"
>