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