fix(comparison): lay out lines to the content box, not a padding guess
availableWidth was containerWidth minus a flat 48/96px constant, but the slider track's gutters are responsive CSS padding (px-4/8/12/lg:px-24, per side). At lg the real padding is 192px while only 96 was subtracted, so lines were broken ~96px too wide and overflowed the container. Measure the container's content box (ResizeObserver contentBoxSize) and use it directly as availableWidth; keep the border box for the slider and split math. The content box already excludes the gutters, so it tracks the breakpoints with no constant to maintain.
This commit is contained in:
@@ -61,14 +61,6 @@ const SLIDER_SPRING_CONFIG = { stiffness: 0.2, damping: 0.7 } as const;
|
|||||||
*/
|
*/
|
||||||
const SLIDER_PERSIST_DEBOUNCE_MS = 100;
|
const SLIDER_PERSIST_DEBOUNCE_MS = 100;
|
||||||
|
|
||||||
/**
|
|
||||||
* Horizontal layout padding subtracted from container width before laying
|
|
||||||
* out the comparison text. Different per breakpoint to match the gutters
|
|
||||||
* around the slider track.
|
|
||||||
*/
|
|
||||||
const SLIDER_PADDING_MOBILE_PX = 48;
|
|
||||||
const SLIDER_PADDING_DESKTOP_PX = 96;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Position bounds (percent of container width).
|
* Position bounds (percent of container width).
|
||||||
*/
|
*/
|
||||||
@@ -94,7 +86,18 @@ const isMobile = $derived(responsive?.isMobile ?? false);
|
|||||||
|
|
||||||
let isDragging = $state(false);
|
let isDragging = $state(false);
|
||||||
let isTypographyMenuOpen = $state(false);
|
let isTypographyMenuOpen = $state(false);
|
||||||
|
/**
|
||||||
|
* Border-box width of the slider track. Drives the slider→position mapping and
|
||||||
|
* the per-char split math (pointer events are measured against this same box).
|
||||||
|
*/
|
||||||
let containerWidth = $state(0);
|
let containerWidth = $state(0);
|
||||||
|
/**
|
||||||
|
* Content-box width of the slider track (border box minus the responsive CSS
|
||||||
|
* gutters). The width the text lays out into, and what the line-breaker fits
|
||||||
|
* within. Measured rather than derived from a padding constant so it tracks the
|
||||||
|
* breakpoint gutters.
|
||||||
|
*/
|
||||||
|
let contentWidth = $state(0);
|
||||||
|
|
||||||
const layout = new DualFontLayout();
|
const layout = new DualFontLayout();
|
||||||
|
|
||||||
@@ -114,10 +117,14 @@ $effect(() => {
|
|||||||
|
|
||||||
const observer = new ResizeObserver(entries => {
|
const observer = new ResizeObserver(entries => {
|
||||||
for (const entry of entries) {
|
for (const entry of entries) {
|
||||||
// Use borderBoxSize if available, fallback to contentRect
|
// Border box for slider/pointer math, content box for text width.
|
||||||
const width = entry.borderBoxSize?.[0]?.inlineSize ?? entry.contentRect.width;
|
const border = entry.borderBoxSize?.[0]?.inlineSize ?? entry.contentRect.width;
|
||||||
if (width > 0) {
|
const content = entry.contentBoxSize?.[0]?.inlineSize ?? entry.contentRect.width;
|
||||||
containerWidth = width;
|
if (border > 0) {
|
||||||
|
containerWidth = border;
|
||||||
|
}
|
||||||
|
if (content > 0) {
|
||||||
|
contentWidth = content;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -226,7 +233,7 @@ $effect(() => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Layout effect — depends on content, settings AND containerWidth.
|
// Layout effect — depends on content, settings, and contentWidth.
|
||||||
// Awaits font loading into the canvas measurement context before invoking
|
// Awaits font loading into the canvas measurement context before invoking
|
||||||
// the engine; otherwise pretext caches fallback-font widths globally per
|
// the engine; otherwise pretext caches fallback-font widths globally per
|
||||||
// font string, and the morph boundary drifts from the thumb visually.
|
// font string, and the morph boundary drifts from the thumb visually.
|
||||||
@@ -236,18 +243,15 @@ $effect(() => {
|
|||||||
const _size = typography.renderedSize;
|
const _size = typography.renderedSize;
|
||||||
const _height = typography.height;
|
const _height = typography.height;
|
||||||
const _spacing = typography.spacing;
|
const _spacing = typography.spacing;
|
||||||
const _width = containerWidth;
|
const availableWidth = contentWidth;
|
||||||
const _isMobile = isMobile;
|
|
||||||
|
|
||||||
if (!container || !fontA || !fontB || _width <= 0) {
|
if (!container || !fontA || !fontB || availableWidth <= 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const fontAStr = getPretextFontString(_weight, _size, fontA.name);
|
const fontAStr = getPretextFontString(_weight, _size, fontA.name);
|
||||||
const fontBStr = getPretextFontString(_weight, _size, fontB.name);
|
const fontBStr = getPretextFontString(_weight, _size, fontB.name);
|
||||||
|
|
||||||
const padding = _isMobile ? SLIDER_PADDING_MOBILE_PX : SLIDER_PADDING_DESKTOP_PX;
|
|
||||||
const availableWidth = Math.max(0, _width - padding);
|
|
||||||
const lineHeight = _size * _height;
|
const lineHeight = _size * _height;
|
||||||
|
|
||||||
let cancelled = false;
|
let cancelled = false;
|
||||||
|
|||||||
Reference in New Issue
Block a user