lineFit runs before first paint (no 150ms debounce): no flash of unfitted text

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-15 13:59:03 +03:30
parent 09f6b78198
commit 2e7e5823cc

View File

@@ -20,8 +20,6 @@ const MIN_CHARS = 14;
const MAX_GAP = 2.2;
const observed = new WeakSet<HTMLElement>();
const queue = new Set<HTMLElement>();
let frame = 0;
/** پهنای طبیعیِ هر خط (با ترازِ start) از مستطیل‌های Range. */
function lineWidths(el: HTMLElement, fontSize: number): number[] {
@@ -86,25 +84,26 @@ export function fit(el: HTMLElement) {
el.style.wordSpacing = best ? `${best}em` : '';
}
function schedule(el: HTMLElement) {
queue.add(el);
if (frame) return;
frame = window.setTimeout(() => {
frame = 0;
const batch = [...queue];
queue.clear();
for (const el of batch) fit(el);
}, 150);
}
// همه‌ی اجراها همزمان و پیش از اولین نقاشیِ صفحه‌اند (MutationObserver/ResizeObserver پیش از paint
// صدا زده می‌شوند)، پس متنِ خام و اصلاح‌نشده هرگز دیده نمی‌شود؛ تأخیرِ قبلی (۱۵۰ms) یک چشمک می‌ساخت.
const lastWidth = new WeakMap<HTMLElement, number>();
function watch(el: HTMLElement) {
if (observed.has(el)) return;
observed.add(el);
resizer.observe(el);
fit(el);
}
const resizer = new ResizeObserver((entries) => {
for (const e of entries) schedule(e.target as HTMLElement);
for (const e of entries) {
const el = e.target as HTMLElement;
const width = e.contentRect.width;
// تغییرِ ارتفاعِ خودِ fit نباید دوباره fit را راه بیندازد
if (lastWidth.get(el) === width) continue;
lastWidth.set(el, width);
fit(el);
}
});
function scan(node: ParentNode) {
@@ -116,12 +115,14 @@ export function startLineFit() {
const root = document.getElementById('root')!;
scan(root);
new MutationObserver((records) => {
const changed = new Set<HTMLElement>();
for (const r of records) {
r.addedNodes.forEach((n) => { if (n instanceof HTMLElement) scan(n); });
// متنِ داخلِ پاراگراف عوض شده (مثلاً پاسخِ در حالِ پخش)
// متنِ داخلِ پاراگراف عوض شده (سوالِ بعدی، پاسخِ در حالِ پخش)
const host = (r.target instanceof HTMLElement ? r.target : r.target.parentElement)?.closest<HTMLElement>(SELECTOR);
if (host) schedule(host);
if (host) changed.add(host);
}
changed.forEach(fit);
}).observe(root, { childList: true, subtree: true, characterData: true });
void document.fonts.ready.then(() => document.querySelectorAll<HTMLElement>(SELECTOR).forEach(schedule));
void document.fonts.ready.then(() => document.querySelectorAll<HTMLElement>(SELECTOR).forEach(fit));
}