لقياس نص DOM على نحو موثوق، انتظر ورقة الأنماط ذات الصلة، وضع النص في التخطيط المقصود، ثم انتظر document.fonts.ready قبل قراءة الأبعاد الهندسية. يوضح هذا الدليل التسلسل في متصفح سطح مكتب على Mac، ويشرح متى ينبغي تكراره.
المهمة هنا هي قياس تسمية اصطناعية أو فقرة ملتفة في صفحة متصفح، مثل تحديد المساحة التي تحتاج إليها بطاقة. ينبغي للمتصفح إجراء تخطيط النص؛ أما شيفرتك فعليها التحقق من الجاهزية وقراءة المربعات الناتجة.
أي إشارة جاهزية ينبغي انتظارها؟
هناك مراحل منفصلة لإنشاء المستند وتحميل أوراق الأنماط وتخطيط الخطوط. اختر المرحلة التي تطابق العمل الذي تتحكم فيه، ثم أجرِ القياس بعد وضع المحتوى وأنماطه.
| الإشارة | ما الذي تؤكده | ما الذي لا يزال بحاجة إلى الانتباه |
|---|---|---|
DOMContentLoaded |
اكتمال التحليل وتنفيذ البرامج النصية المؤجلة | لا ينتظر أوراق الأنماط مباشرةً |
حدث load لورقة أنماط |
تحميل ورقة الأنماط ومحتواها المستورد وتحليلهما | تحميل الخطوط والتخطيط الناتج للعنصر المستهدف |
حدث load للنافذة |
تحميل موارد الصفحة الأولية، بما فيها أوراق الأنماط، باستثناء الموارد الكسولة | تغييرات التطبيق اللاحقة وجاهزية الخطوط |
document.fonts.ready |
اكتمال التحميل والتخطيط اللذين يتضمنان الخطوط المستخدمة في المستند | تغييرات المحتوى أو الأنماط أو الحجم اللاحقة |
توضح MDN التفصيل المتعلق بأوراق الأنماط: حدث DOMContentLoaded نفسه لا ينتظرها، رغم أن البرامج النصية المؤجلة تنتظرها، ولذلك قد تؤخر الحدث بصورة غير مباشرة. يفيد هذا الحدث في العثور على العناصر، لكنه لا يوفر شروطًا مكتملة للقياس. Document: DOMContentLoaded event.
إذا أدرجت ورقة أنماط بنفسك، فأضف معالجي load وerror قبل إدراج الرابط. تذكر MDN أن حدث load لورقة الأنماط يقع بعد التحميل والتحليل، وقبل بدء تطبيق الأنماط مباشرةً. كما تميز معالجة أوراق الأنماط في WHATWG بين التحميل الناجح وحدث error. Stylesheet load events، HTML Standard: Stylesheet links.
لتحميل الموارد الأولي للصفحة، يُعد حدث load للنافذة نقطة تحقق أوسع. على البرنامج النصي الذي يبدأ بعد ذلك التحقق من document.readyState قبل الاشتراك، إذ لن يُشغَّل الحدث الذي وقع بالفعل عند إضافة معالج جديد. وإذا أدرجت شيفرة التطبيق ورقة أنماط لاحقًا، فلا يزال عليها تتبع هذا الإدراج صراحةً. Window: load event، Checking document readiness.
ما الذي تضمنه جاهزية الخطوط فعلًا؟
توضح مواصفة W3C لتحميل خطوط CSS أن ready يُتم بنجاح بعد اكتمال عمليات التخطيط ذات الصلة وعدم الحاجة إلى تحميل مزيد من الخطوط لهذا العمل. ويشمل ذلك الحالات التي يؤدي فيها استخدام خط بديل إلى تحميل خطوط إضافية. لا يتم هذا الوعد إلا مرة واحدة، وقد تؤدي التغييرات اللاحقة إلى تحميل مزيد من الخطوط. اقرأ document.fonts.ready بعد التغييرات ذات الصلة بكل قياس، بدلًا من الاحتفاظ بوعد واحد من بدء التشغيل إلى الأبد. CSS Font Loading Module Level 3: The ready attribute.
تتعلق الجاهزية بالخطوط المستخدمة في المستند. ولا تعني أن كل خط معلن قد نُزّل. وتشير MDN تحديدًا إلى أن font-display: optional قد يجعل مجموعة الخطوط المستخدمة مختلفة عن المجموعة المعلنة. لذلك قد يصف القياس المكتمل النص المعروض بخط بديل على نحو صحيح. Document: fonts property.
حدد النتيجة التي تحتاج إليها قبل كتابة شيفرة الانتظار:
- قياس النتيجة المعروضة حاليًا: انتظر جاهزية الخطوط واقرأ الأبعاد الهندسية من DOM، مع قبول اختيارات المتصفح للخطوط البديلة.
- اشتراط وجه خط معلن قابل للتنزيل: حمّل وجه الخط المطابق صراحةً، وعالج حالات الفشل، ثم انتظر جاهزية خطوط المستند قبل القياس.
- قياس محتوى متغير: كرر التسلسل بعد كل تحديث ذي صلة، وتخلص من النتائج التي استُبدلت بتحديث آخر.
بالنسبة إلى النتيجة الثانية، تقبل document.fonts.load() مواصفة خط CSS ونصًا نموذجيًا اختياريًا. مع عائلة خطوط معلنة اصطناعية اسمها SampleFace، يمكن أن تكون المواصفة 400 18px \"SampleFace\"، ويمكن أن يكون النص Panel Alpha. يُرفض الوعد المعاد إذا أخفق وجه مطابق في التحميل. وتحدد وسيطة النص الأوجه عبر نطاقات Unicode المعلنة؛ لكنها لا تتحقق من تغطية المحارف الفردية. FontFaceSet: load() method.
لا تستبدل document.fonts.check() بهذا الشرط. توضح MDN أنه قد يعيد true حتى عندما لا يكون الخط المطلوب متاحًا. وبالمثل، تحدد W3C أن ready لا يُرفض أبدًا، لذا فإن اكتماله بنجاح وحده لا يثبت نجاح تحميل الخط. FontFaceSet: check() method، CSS Font Loading: The ready attribute.
نفّذ مثالًا كاملًا من ورقة الأنماط إلى القياس
على جهاز Mac، افتح about:blank في متصفح سطح مكتب حديث، وافتح أدوات المطور واختر Console. نفّذ برنامج JavaScript الكامل التالي مرة واحدة. ينشئ نصًا نموذجيًا محايدًا وورقة أنماط خاصة به، لذا لا يحتاج إلى خادم أو مكتبة أو ملف خط.
يستخدم المثال عمدًا system-ui. وهو يوضح تسلسل الانتظار من دون الاعتماد على مورد قابل للتنزيل. في صفحة تستخدم خطوط الويب، أبقِ التسلسل كما هو ودع تعريفات الخطوط الفعلية في تلك الصفحة توفر الموارد. جرى فحص مثال المتصفح، لكنه لم يُنفذ لهذا النص.
(async () => {
if (!document.fonts || !document.body) {
throw new Error("A document body and CSS Font Loading support are required.");
}
if (document.getElementById("text-measure-demo")) {
throw new Error("Reload the blank tab before running this example again.");
}
async function withDeadline(promise, label, milliseconds = 10000) {
let timer;
try {
return await Promise.race([
promise,
new Promise((_, reject) => {
timer = setTimeout(() => {
reject(new Error(`${label} did not finish within the deadline.`));
}, milliseconds);
})
]);
} finally {
clearTimeout(timer);
}
}
const paragraph = document.createElement("p");
paragraph.id = "text-measure-demo";
paragraph.textContent =
"Panel Alpha contains a compact label. Panel Beta contains another label.";
document.body.append(paragraph);
const css = `
#text-measure-demo {
box-sizing: border-box;
width: 280px;
margin: 24px;
padding: 12px;
border: 1px solid #777;
font: 400 18px/1.5 system-ui, sans-serif;
letter-spacing: normal;
white-space: normal;
overflow-wrap: normal;
}
`;
const stylesheetURL = URL.createObjectURL(
new Blob([css], { type: "text/css" })
);
const link = document.createElement("link");
link.rel = "stylesheet";
link.href = stylesheetURL;
const stylesheetLoaded = new Promise((resolve, reject) => {
link.onload = resolve;
link.onerror = () => reject(new Error("The sample stylesheet failed."));
});
try {
document.head.append(link);
await withDeadline(stylesheetLoaded, "Stylesheet loading");
// Establish the target's styled layout before reading font readiness.
paragraph.getBoundingClientRect();
await withDeadline(document.fonts.ready, "Font readiness");
const style = getComputedStyle(paragraph);
const range = document.createRange();
range.selectNodeContents(paragraph);
const copyRect = rect => ({
x: rect.x, y: rect.y, width: rect.width, height: rect.height
});
console.log({
text: paragraph.textContent,
fontFamily: style.fontFamily,
fontSize: style.fontSize,
lineHeight: style.lineHeight,
elementBox: copyRect(paragraph.getBoundingClientRect()),
textFragments: Array.from(range.getClientRects(), copyRect)
});
} catch (error) {
link.remove();
paragraph.remove();
throw error;
} finally {
link.onload = null;
link.onerror = null;
URL.revokeObjectURL(stylesheetURL);
}
})().catch(error => console.error(error));
الترتيب المهم هو اكتمال ورقة الأنماط، ثم تثبيت تخطيط العنصر المستهدف، ثم جاهزية الخطوط، ثم قراءة الأبعاد الهندسية النهائية. تثبت قراءة الأبعاد الهندسية الأولى التخطيط الحالي؛ وتُهمل قيمتها عمدًا. أما القراءات النهائية فتصف النتيجة بعد جاهزية الخطوط. مهلة الثواني العشر حد تشغيلي توضيحي: انتهاء المهلة يبلغ عن محاولة غير مكتملة ولا ينتج أي قياس.
افحص fontSize وlineHeight إلى جانب المربعات عند تكييف المثال. تعيد getComputedStyle() قيم الخصائص المحسوبة بعد تطبيق أوراق الأنماط النشطة، ما يجعلها مفيدة لاكتشاف عدم تطابق محدد أو تسلسل الأنماط. وتصف قيمة fontFamily قائمة عائلات CSS؛ فلا ينبغي اعتبارها دليلًا على وجه الخط الذي وفر كل محرف. Window: getComputedStyle() method.
أي قياس من DOM يجيب عن سؤالك؟
استخدم element.getBoundingClientRect() عندما تحتاج إلى المساحة الخارجية التي تشغلها بطاقة أو فقرة. يشمل المستطيل الحشو والحدود، ويكون موضعه بالنسبة إلى إطار العرض. في المثال، عرض صندوق الحدود المحدد هو 280px؛ وعرض المحتوى المتاح للنص هو 254px، ويُحسب بطرح 24 و2 من 280. يصف هذا الحساب CSS المقدمة، لا نتيجة تمت ملاحظتها في المتصفح. Element: getBoundingClientRect() method.
استخدم Range على النص عندما تحتاج إلى أجزاء تخطيطه. يعيد Range.getClientRects() مستطيلات النطاق بدلًا من صندوق الفقرة المحيط فقط. احتفظ بالمجموعة: فتسطيح النص الملتف إلى عرض واحد يفقد معلومات مفيدة عن المناطق التي يشغلها. لا تسمِّ طول المصفوفة lineCount في تنفيذ عام؛ فقد ينتج المحتوى المضمن المعقد عدة مستطيلات. Range: getClientRects() method.
أبقِ مقطع النص الأصلي سليمًا أثناء القياس. قد تؤدي إضافة غلاف حول كل محرف إلى إنشاء حدود تنسيق تؤثر في تشكيل النص. تحدد مسودة W3C لنص CSS الحالات التي تحافظ فيها الحدود المضمنة على التشكيل أو تقطعه. يتيح قياس DOM الحالي باستخدام نطاق تجنب إضافة تلك الأغلفة. CSS Text Module Level 3: Shaping Across Element Boundaries.
لـ Canvas دور مختلف. تعيد measureText() مقاييس نص Canvas لسلسلة نصية مقدمة. في هذه المهمة، أي العثور على الالتفاف الفعلي للنص داخل بطاقة DOM، اقرأ DOM بعد تحقق الجاهزية. تعامل مع قياس Canvas على أنه حساب منفصل له إعدادات النص الخاصة به. CanvasRenderingContext2D: measureText() method.
ماذا تفعل عندما تستمر القياسات في التغير؟
استخدم التسلسل التشخيصي التالي قبل إضافة مهلة أخرى:
- تأكد من أن اكتمال ورقة الأنماط التي انتظرتها يعود إلى الورقة التي تنسق العنصر المستهدف. احتفظ بوعود تحميل الأوراق المدرجة ديناميكيًا مع شيفرة إدراجها.
- افحص الخطوط المحسوبة والعرض للعنصر المستهدف. قد تخسر ورقة الأنماط المحمّلة في تسلسل الأنماط.
- تأكد من وجود نص العنصر والحاوية المقصودة قبل انتظار جاهزية الخطوط.
- قرر ما إذا كان الخط البديل مقبولًا. إذا كان وجه خط معلن بعينه مطلوبًا، فاستخدم التحميل الصريح وأبلغ عن الفشل.
- تحقق مما إذا كان تحديث آخر في التطبيق قد غيّر النص أو الأصناف أو الحاوية بعد الانتظار. أعد القياس للحالة الجديدة.
استدعاء إطار ليس حاجزًا لتحميل الموارد. يجدول requestAnimationFrame() استدعاءً قبل إعادة الرسم، وغالبًا ما توقف المتصفحات تشغيله في علامات التبويب الخلفية. استخدمه عندما يخدم جدولة الإطارات تطبيقك، لكن لا تستبدل وعود أوراق الأنماط أو الخطوط باستدعاء واحد أو اثنين للإطار. Window: requestAnimationFrame() method.
في تخطيط قابل لتغيير الحجم، راقب الحاوية ذات الصلة باستخدام ResizeObserver وجدول قياسات جديدة عندما تتغير أبعادها. يرصد المراقب تغييرات حجم صندوق المحتوى أو صندوق الحدود؛ لكنه ليس إشعارًا عامًا بتغيير النص. شغّل القياس أيضًا من عمليات التطبيق التي تستبدل النص أو تغيّر الخطوط. ResizeObserver.
إذا كنت تستخدم Playwright بالفعل، فنفّذ انتظار الجاهزية وقراءات الأبعاد الهندسية في المتصفح داخل رد نداء غير متزامن من page.evaluate(). ينتظر Playwright الوعد الذي يعيده الرد، ثم يعيد النتيجة إلى البيئة المستدعية. أعد حقول المستطيل الرقمية البسيطة كما يفعل المثال، ومرّر أي قيم من جهة المستدعي صراحةً إلى الرد. Evaluating JavaScript.
يرتبط القياس المفيد بنص معين وحالة ورقة الأنماط ونتيجة الخط وحجم الحاوية. ثبّت هذه المدخلات، وانتظر أعمال التحميل ذات الصلة، ثم قِس DOM السليم. كرر القياس عند تغير هذه المدخلات كي تظل الأبعاد المسجلة تجيب عن سؤال التخطيط الذي لديك فعلًا.
المصادر
- CSS Font Loading Module Level 3 — W3C.
- HTML Standard: Link type stylesheet — WHATWG.
- Document: DOMContentLoaded event — MDN Web Docs.
<link>: The HTML External Resource Link element — MDN Web Docs.- Window: load event — MDN Web Docs.
- Document: fonts property — MDN Web Docs.
- FontFaceSet: load() method — MDN Web Docs.
- FontFaceSet: check() method — MDN Web Docs.
- Window: getComputedStyle() method — MDN Web Docs.
- Element: getBoundingClientRect() method — MDN Web Docs.
- Range: getClientRects() method — MDN Web Docs.
- CSS Text Module Level 3 — W3C.
- CanvasRenderingContext2D: measureText() method — MDN Web Docs.
- Window: requestAnimationFrame() method — MDN Web Docs.
- ResizeObserver — MDN Web Docs.
- Evaluating JavaScript — Playwright.