لقياس معاينة نص في المتصفح، قِس عنصر DOM بعد تخطيطه عند عرض إطار العرض المطلوب. استخدم Canvas لقياس النص في سطر واحد. يوضح هذا الدليل كيفية مقارنة الطريقتين على جهاز Mac وفهم سبب اختلاف العرض والارتفاع بينهما.
المهمة العملية هي معرفة مقدار المساحة التي يحتاج إليها عنوان بطاقة اصطناعية مع تغيّر إطار عرض المتصفح. تسجّل النتيجة المفيدة عرض إطار العرض، وعرض النص المتاح، وإعدادات الخط، والصندوق المقاس معًا. يصعب إعادة إنتاج قياس العرض من دون هذه الشروط.
ما عرض النص وارتفاعه اللذان ينبغي قياسهما؟
تجيب واجهات المتصفح عن أسئلة مختلفة بشأن الأحجام. اختر القياس الذي يطابق القرار الذي تحتاج إلى اتخاذه قبل إجراء أي حساب.
| سؤالك | القياس | ما الذي يمثله |
|---|---|---|
| ما مقدار التقدّم الأفقي لنص واحد غير ملتف؟ | Canvas measureText(text).width |
عرض تقدّم النص |
| ما حدود أشكال المحارف في Canvas؟ | خصائص Canvas actualBoundingBox… |
حدود المحارف بالنسبة إلى نقطة المحاذاة وخط الأساس |
| ما مقدار مساحة التخطيط التي تشغلها البطاقة؟ | العنصر getBoundingClientRect() |
صندوق الإحاطة ذي الحدود، بما في ذلك الحشو والحدود |
| أين تقع أجزاء النص بعد التفافه؟ | Range getClientRects() |
المستطيلات المرتبطة بالنص المحدد |
| ما المستطيل الذي يحيط بأجزاء النص؟ | Range getBoundingClientRect() |
مستطيل الإحاطة المجمّع لها |
قد يختلف عرض تقدّم Canvas عن عرض المحارف: فقد تتجاوز الأشكال المائلة عرض التقدّم الخاص بها. لحساب حدود المحارف، احسب العرض بهذه الصيغة: actualBoundingBoxLeft + actualBoundingBoxRight، والارتفاع بهذه الصيغة: actualBoundingBoxAscent + actualBoundingBoxDescent. تُستخدم وحدات بكسل CSS في هذه القياسات. TextMetrics — MDN
يشمل قياس العنصر الحشو والحدود، بينما يقيس Range للنص فقط النص المحدد باستخدام مقاييس الخط. ولا ينبغي وصف أي منهما بأنه مستطيل دقيق للبكسلات المرسومة. تحدد مواصفات W3C مستطيلات نص Range الأفقية باستخدام الصعود والهبوط وتقدّم النص. Element: getBoundingClientRect() method — MDN، CSSOM View Module — W3C
لمعاينة حجم النص في المتصفح، استخدم صندوق العنصر لتخصيص المساحة ومستطيلات Range لفحص تدفق النص. احتفظ بقياسات Canvas كمرجع منفصل للنص غير الملتف.
اضبط إطار العرض وشروط النص أولًا
في Chrome على جهاز Mac، افتح DevTools، وفعّل Toggle device toolbar، ثم اختر Responsive ضمن Dimensions. أدخل العرض والارتفاع المطلوبين في حقول إطار العرض. توثّق Chrome الإدخال الرقمي وتغيير حجم إطار العرض بالسحب. لمعاينة سطح المكتب، استخدم نوع جهاز سطح المكتب عند توفر هذا الخيار. Simulate mobile devices with device mode — Chrome for Developers
ميّز بين عرض إطار العرض وعرض النص. قد يكون للبطاقة حد أقصى للعرض، ومسافة خارجية، وحشو، وحدود. سيتوقف اتساع مساحة النص مع زيادة عرض المتصفح عندما تبلغ البطاقة أقصى عرض لها.
على سبيل المثال، افترض أن عرض صندوق الحدود التوضيحي للبطاقة هو 360px، وأن الحشو الأفقي 16px على كل جانب، وأن هناك حدّين بعرض 1px. يكون عرض المحتوى المتاح لها 360 − 32 − 2 = 326px. وهذا هو العرض المتاح لالتفاف النص. مع ذلك، يظل المستطيل المحيط بالعنصر يعرض 360px. وهذا يتوافق مع سلوك قياس صندوق الحدود الموثّق. Element: getBoundingClientRect() method — MDN
طابق عائلة الخط ووزنه وحجمه ونمطه وارتفاع السطر وتباعده وقواعد الالتفاف المقصودة. وعلى وجه الخصوص، تطوي white-space: normal المسافات البيضاء وتسمح بالتفاف النص، بينما تحافظ pre-wrap على المسافات البيضاء والأسطر الجديدة في المصدر مع السماح بالالتفاف. لا يمكن لمعاينة تستخدم أحدهما أن تمثل الآخر تمثيلًا موثوقًا. white-space — MDN
ميّز أيضًا بين حجم الخط وارتفاع السطر. في الكتابة الأفقية، يساهم line-height في تحديد ارتفاع صندوق السطر؛ لكنه لا يصف الارتفاع المرئي للحروف. لذلك يتطلب خط حجمه 16px وارتفاع سطره 24px حسابًا مختلفًا للارتفاع عن حدود محارفه في Canvas. line-height — MDN
نفّذ معاينة كاملة لنص اصطناعي
افتح علامة تبويب فارغة، وافتح DevTools، واختر Console، ثم شغّل النص البرمجي الكامل التالي في المتصفح مرة واحدة. ينشئ بطاقة محايدة وتقريرًا بالقياسات. لا حاجة إلى مكتبة أو خط خارجي أو محدد موجود أو ملف HTML. أعِد تحميل علامة التبويب الفارغة قبل تشغيل نسخة أخرى.
تتبّع البطاقة إطار العرض إلى أن يصل عرض صندوق حدودها إلى 480px. ويتحدّث التقرير عند تغيير حجم النافذة أو إطار العرض المحاكى. يقتصر هذا المثال عمدًا على كتلة نصية أفقية بسيطة ذات تنسيق موحّد. وقد فُحصت الشيفرة المعتمدة على المتصفح، لكنها لم تُنفّذ في متصفح أثناء الإعداد.
(async () => {
const text =
"Sample collection: round forms, soft gradients, and simple patterns.";
document.body.style.margin = "0";
const card = document.createElement("div");
card.textContent = text;
card.lang = "en";
card.dir = "ltr";
card.style.cssText = [
"box-sizing: border-box",
"width: min(480px, calc(100% - 48px))",
"margin: 24px",
"padding: 16px",
"border: 1px solid #777",
"font: normal 400 16px/24px Arial, sans-serif",
"font-kerning: normal",
"letter-spacing: 0px",
"word-spacing: 0px",
"white-space: normal",
"overflow-wrap: normal",
"word-break: normal",
"hyphens: none",
"color: #222",
"background: #f4f4f4"
].join(";");
const report = document.createElement("pre");
report.style.cssText =
"margin: 24px; white-space: pre-wrap; overflow-wrap: anywhere;";
document.body.append(card, report);
const canvas = document.createElement("canvas");
const ctx = canvas.getContext("2d");
if (!ctx) {
report.textContent = "A Canvas 2D context is unavailable.";
return;
}
// Establish layout before waiting for the fonts used by that layout.
card.getBoundingClientRect();
await document.fonts.ready;
const format = value =>
Number.isFinite(value) ? value.toFixed(2) : "unavailable";
function measure() {
const style = getComputedStyle(card);
const box = card.getBoundingClientRect();
const horizontalExtras =
parseFloat(style.paddingLeft) + parseFloat(style.paddingRight) +
parseFloat(style.borderLeftWidth) + parseFloat(style.borderRightWidth);
const range = document.createRange();
range.selectNodeContents(card);
const textBox = range.getBoundingClientRect();
const fragments = Array.from(range.getClientRects());
ctx.font = "normal 400 16px Arial, sans-serif";
ctx.fontKerning = "normal";
ctx.direction = "ltr";
ctx.textAlign = "left";
ctx.textBaseline = "alphabetic";
const metrics = ctx.measureText(text);
const inkWidth =
metrics.actualBoundingBoxLeft + metrics.actualBoundingBoxRight;
const inkHeight =
metrics.actualBoundingBoxAscent + metrics.actualBoundingBoxDescent;
report.textContent = [
"All dimensions below are CSS pixels.",
`Layout viewport: ${document.documentElement.clientWidth} x ${document.documentElement.clientHeight}`,
`Card border box: ${format(box.width)} x ${format(box.height)}`,
`Available text width: ${format(box.width - horizontalExtras)}`,
`Range bounds: ${format(textBox.width)} x ${format(textBox.height)}`,
`Range fragment count: ${fragments.length}`,
`Canvas unwrapped advance: ${format(metrics.width)}`,
`Canvas glyph bounds: ${format(inkWidth)} x ${format(inkHeight)}`,
`Computed line height: ${style.lineHeight}`
].join("\n");
}
measure();
window.addEventListener("resize", measure);
})().catch(error => console.error("Preview failed:", error));
يستخدم استدعاءا العنصر وRange التخطيط الفعلي للمتصفح. ويقيس Canvas السلسلة الاصطناعية نفسها كسطر واحد غير ملتف. يستبدل تحضير نص Canvas في معيار HTML المسافات البيضاء من نوع ASCII بمسافات؛ لذا فإن تمرير سطر جديد إلى measureText() لا ينشئ فقرة CSS متعددة الأسطر. HTML Standard: The canvas element — WHATWG
ينتظر النص البرمجي document.fonts.ready بعد إنشاء التخطيط. وتذكر MDN أن هذا الوعد يُستوفى عند انتهاء عمليات التحميل والتخطيط للخطوط المستخدمة. ولا يعني ذلك أن كل خط مُعلن قد حُمّل، أو أن الوجه المفضّل أصلًا هو المعروض بالضرورة. Document: fonts property — MDN
اقرأ النتائج وحدد ما إذا كان النص يتّسع
ابدأ بـ Available text width، ثم قارن ارتفاع البطاقة عند حجمين لإطار العرض. عندما يضيف الالتفاف سطرًا، يتغيّر ارتفاع البطاقة، بينما قد يظل تقدّم Canvas للنص غير الملتف كما هو. يساعد هذا التباين على التمييز بين تغيّر الالتفاف وتغيّر الخط.
الأبعاد التالية توضيحية وليست نتائج قياس لخط معيّن. وهي تفترض أن لإطار عرض التخطيط العرض المذكور وأن أنماط النص البرمجي لم تتغير.
| عرض إطار عرض التخطيط | عرض صندوق حدود البطاقة | عرض النص المتاح |
|---|---|---|
360px |
312px |
278px |
800px |
480px |
446px |
1200px |
480px |
446px |
يوضح الصفّان الأخيران سبب احتمال بقاء التفاف النص دون تغيير عند توسيع المتصفح: فقد بلغت البطاقة أقصى عرض لها. لا يمكن لتغيير إطار العرض وحده أن يوسّع مساحة المحتوى في تلك البطاقة.
في كتلة بسيطة لا تحتوي إلا على نص موحّد التنسيق، يشغل تخطيط توضيحي من ثلاثة أسطر بارتفاع سطر 24px مساحة صناديق أسطر قدرها 72px. أضف حشوًا رأسيًا قدره 32px وحدودًا قدرها 2px لتحصل على ارتفاع توضيحي للبطاقة قدره 106px. يفيد هذا الحساب في فهم النتيجة؛ لكن ارتفاع DOM المقاس يظل القيمة الحاسمة عندما يصبح المحتوى أو التنسيق أكثر تعقيدًا. line-height — MDN
لا تعتبر Range fragment count تلقائيًا عددًا عامًا للأسطر. تعيد واجهة Range مستطيلات النص والعناصر وفق قواعد التحديد الخاصة بها. وقد ينتج عن النص المنسّق والعناصر المتداخلة والتخطيط المجزأ مجموعة مستطيلات أكثر تعقيدًا. يحدد المثال عقدة نصية بسيطة واحدة فقط لتسهيل فهم التشخيص. CSSOM View Module — W3C
وبالمثل، تجنّب تقدير ارتفاع النص الملتف باستخدام ceil(unwrappedWidth / availableWidth). تفترض هذه الصيغة إمكان كسر النص عند أي موضع أفقي. يجب أن يتبع التفاف المتصفح سلوك المسافات البيضاء والكسر الذي تم ضبطه، لذا قِس الكتلة الملتفة مباشرة. white-space — MDN
ما الذي ينبغي التحقق منه عند اختلاف القياسات؟
تحقق أولًا من حدود القياس. قد يكون قياس مستطيل بطاقة كاملة العرض ومستطيل Range الأضيق صحيحين معًا. تحجز البطاقة مساحة في التخطيط، بينما يصف Range النص المحدد فيها. ويوفر ارتفاع المحارف في Canvas قيمة ثالثة لغرض مختلف.
بعد ذلك، تحقق من جاهزية الخط. إذا أضفت خط ويب مُعلنًا، يمكن لـ document.fonts.load(fontSpecification, sampleText) طلب وجوه الخطوط المطابقة صراحةً قبل القياس. عالج حالات الرفض بدلًا من استبدالها بصمت بنتيجة ناجحة. ترشح وسيطة النص الاختيارية الخطوط بحسب نطاقات Unicode؛ لكنها لا تتحقق من تغطية المحارف الفردية. FontFaceSet: load() method — MDN
حافظ على حدود تشكيل النص عند حساب عرض عنوان. قِس السلسلة كاملةً بدلًا من جمع عروض محارف قِيست كل على حدة. يجهّز Canvas، وفق مواصفاته، سلسلة محارف موضوعة باستخدام إعدادات الخط والنص الحالية؛ ونتيجته هي الوحدة المناسبة لتلك السلسلة. اقتصر في هذه المقارنة باستخدام Canvas على إعدادات الخط المذكورة، ولا تفترض أنها تحاكي كل ميزات الطباعة في CSS. HTML Standard: The canvas element — WHATWG
في صفحة موجودة، قِس المكوّن الفعلي في موضعه. فنقل النص إلى صندوق عام مخفي يغيّر ظروف التخطيط ما لم تُعِد أيضًا إنتاج الأنماط والسياق المحيط ذوي الصلة. لا يمكن للعناصر التي لا تملك صناديق تخطيط مرتبطة بها توفير قياسات صناديق عادية مفيدة، كما تؤثر تحويلات CSS في المستطيلات التي تعرضها هذه الواجهات. يتجنب المثال الاصطناعي كلا الأمرين. CSSOM View Module — W3C
كيف تكرّر المعاينة عند أحجام متعددة للمتصفح؟
للفحوصات العرضية، أدخل كل حجم لإطار العرض يدويًا وسجّل التقرير. قارن العروض قرب النقطة التي يتغيّر عندها الالتفاف، بدلًا من التحقق من تخطيط ضيق جدًا وآخر واسع جدًا فقط.
للفحوصات المتكررة، يدعم Playwright تحديد أبعاد إطار العرض صراحةً وإعدادًا منفصلًا deviceScaleFactor. وتتيح وثائقه أيضًا تجاوز إطار العرض المحدد مسبقًا للجهاز. يوفر ذلك مسار أتمتة مدعومًا: اضبط إطار العرض، وحمّل الصفحة، وانتظر الخطوط والمحتوى ذي الصلة، ثم اجمع قياسات DOM في المتصفح. Emulation — Playwright
أبقِ أبعاد CSS منفصلة عن كثافة بكسلات الشاشة. يعرض Playwright إطار العرض ومعامل القياس كإعدادين منفصلين، بينما تُعبّر مقاييس نص Canvas بوحدات بكسل CSS. ولا تتطلب الشاشة الأعلى كثافةً، بحد ذاتها، مضاعفة عرض CSS المخصص لعنوان. Emulation — Playwright، TextMetrics — MDN
لذلك، تسجّل نتيجة المعاينة المفيدة الشروط بإيجاز: المتصفح، وإطار العرض، والنص، والطباعة، وعرض المحتوى، وارتفاع البطاقة المقاس. استخدم نتيجة DOM لتحديد حجم المكوّن الملتف، واحتفظ بمقاييس Canvas للأسئلة المتعلقة بسطر واحد، وكرّر القياس في كل متصفح تحتاج إلى تمثيل طريقة عرضه.
المصادر
- CSSOM View Module — W3C.
- HTML Standard: The canvas element — WHATWG.
- TextMetrics — MDN Web Docs.
- Element: getBoundingClientRect() method — MDN Web Docs.
- Document: fonts property — MDN Web Docs.
- FontFaceSet: load() method — MDN Web Docs.
- white-space — MDN Web Docs.
- line-height — MDN Web Docs.
- Simulate mobile devices with device mode — Chrome for Developers.
- Emulation — Playwright.