ثبّت التعليق التوضيحي على Range في DOM، ثم ضع تسميته باستخدام مستطيلات المقاطع التي يعرضها النطاق. يوضح هذا الدليل عرضًا تجريبيًا صغيرًا لمتصفح سطح مكتب على Mac، يُبقي ملاحظةً مرتبطةً بعبارة تعبر عناصر مضمنة وتلتف على عدة أسطر.

المهمة محددة: إرفاق تعليق توضيحي واحد ظاهر بعبارة في تخطيط صفحة ويب عادي من دون إعادة بناء نصها. ستحدد العبارة، وتختار مقطعًا للتسمية، ثم تعيد القياس بعد تغييرات التخطيط. توثّق MDN أن Range.getClientRects() هي واجهة برمجة التطبيقات في المتصفح لاسترداد المستطيلات التي يشغلها نطاق. الطريقة Range: getClientRects().

لماذا تنفصل التسمية عن النص الملتف؟

للعبارة تمثيلان مهمان: موضعها في المستند وهندستها الحالية على الشاشة. يعرّف WHATWG النطاق من خلال نقطتي بداية ونهاية، تتألف كل منهما من عقدة وإزاحة. ويمكن أن تقع هاتان النقطتان في عقدتين نصيتين مختلفتين، ما يسمح لنطاق واحد بعبور عنصر تأكيد موجود. معيار DOM.

يفقد المستطيل المحيط الواحد التفاصيل. تخيل عبارة تبدأ في منتصف سطر وتنتهي قرب بداية السطر التالي. يشمل المستطيل المحيط بها مساحة فارغة بجوار كلا السطرين. لذلك قد تبدو التسمية التي تتوسط ذلك المستطيل منفصلة عن الكلمات. تشير Floating UI صراحةً إلى هذه المشكلة في الروابط متعددة الأسطر وتحديدات النطاق. مضمن.

وتحتاج المستطيلات المنفردة أيضًا إلى تفسير. تحدد W3C أن النطاق يمكن أن يعيد مستطيلات حدود العناصر المحددة ومستطيلات النص، بترتيب المحتوى. لذلك قد تنتج العناصر المتداخلة مستطيلات متراكبة؛ وعدد المستطيلات ليس مقياسًا موثوقًا لعدد الأسطر. وتستخدم حدود النص مقاييس الخط بدلًا من الحدود الدقيقة للرموز الرسومية المرسومة. وحدة CSSOM View.

أي مقطع ينبغي أن يرتبط به التعليق التوضيحي؟

اختر قاعدة تحديد الموضع قبل كتابة شيفرة التموضع. تساعدك أداة القرار التالية على فصل النص محل التعليق التوضيحي عن النقطة التي تظهر عندها تسميته.

السلوك المقصود الهندسة المرجعية قاعدة التموضع
ملاحظة تتبع عبارة النطاق كاملًا أرفقها أسفل آخر مقطع صالح للاستخدام في تخطيط أفقي بسيط من اليسار إلى اليمين
تسمية قرب تفاعل المؤشر مستطيلات مقاطع النطاق اختر المقطع عند إحداثيات المؤشر بالنسبة إلى مساحة العرض
تسمية عامة تتمركز حول النص المستطيل المحيط اقبل احتمال ظهور التسمية فوق مساحة فارغة
تمييز خلفية العبارة كاملة النطاق كاملًا استخدم تمييز النص بصورة مستقلة عن موضع التسمية

يستخدم العرض التجريبي القاعدة الأولى. ويثبت عمدًا وضع الكتابة والاتجاه حتى يكون المستطيل الأخير مرجعًا مفيدًا لنقطة النهاية. اعتبر ذلك شرطًا في المثال. تحدد W3C ترتيب المحتوى لهذه المستطيلات؛ لكنها لا تضمن ترتيبًا عامًا من الزاوية العلوية اليسرى إلى الزاوية السفلية اليمنى. وحدة CSSOM View.

لتموضع يعتمد على المؤشر باستخدام Floating UI، تقبل البرمجية الوسيطة inline() إحداثيي x وy بالنسبة إلى مساحة العرض. وتقصر الوثائق هذا الاختيار القائم على الإحداثيات على المستطيلات المنفصلة، لذا لا ينبغي وصفه بأنه أداة عامة لاختيار المحارف. مضمن.

أنشئ عرضًا تجريبيًا كاملًا للنص الملتف على Mac

استخدم إصدارًا حديثًا من Firefox لسطح المكتب، وافتح علامة تبويب فارغة على about:blank. اضغط Command + Option + K لفتح وحدة تحكم الويب الخاصة بالصفحة. توثّق Mozilla هذا الاختصار وتؤكد أن وحدة التحكم تنفّذ JavaScript في سياق الصفحة. وحدة تحكم الويب.

شغّل البرنامج الكامل أدناه في وحدة التحكم. فهو ينشئ كل محتوى المثال وتنسيقاته وعناصر التحكم، لذا لا حاجة إلى مشروع أو عمليات استيراد أو مكتبة خارجية. تعبر العبارة الاصطناعية عنصر تأكيد موجودًا. والتعليق التوضيحي تسمية للعرض فقط؛ ولا يتضمن عناصر تحكم للتحرير.

(() => {
  window.fragmentDemo?.destroy();
  const host = document.createElement('section');
  host.style.cssText = 'margin:48px 24px;padding-bottom:900px';
  const p = document.createElement('p');
  p.dir = 'ltr';
  p.style.cssText = 'font:20px/1.6 Georgia,serif;width:24rem;max-width:80vw;writing-mode:horizontal-tb';
  const a = document.createTextNode('Sample A: amber tiles follow the curved path ');
  const em = document.createElement('em');
  em.textContent = 'past the small blue circle and ';
  const c = document.createTextNode('toward the final silver square. End.');
  p.append(a, em, c);
  const button = document.createElement('button');
  button.textContent = 'Change text width';
  const status = document.createElement('p');
  host.append(button, p, status);
  document.body.append(host);
  const range = document.createRange();
  range.setStart(a, 'Sample A: '.length);
  range.setEnd(c, c.data.indexOf(' End.'));
  const note = document.createElement('div');
  note.textContent = 'Note A: review this phrase';
  note.style.cssText = 'position:fixed;z-index:1000;background:#fff2ad;color:#222;padding:6px 10px;font:14px/1.4 system-ui;pointer-events:none';
  document.body.append(note);
  const style = document.createElement('style');
  style.textContent = '::highlight(fragment-demo) {background-color:#dbeafe;}';
  document.head.append(style);
  const canHighlight = typeof Highlight !== 'undefined' && !!window.CSS?.highlights;
  if (canHighlight) CSS.highlights.set('fragment-demo', new Highlight(range));
  status.textContent = canHighlight ? 'The blue phrase owns Note A.' : 'Text highlighting unavailable; Note A still uses the range.';
  function update() {
    if (!host.isConnected) return;
    const rects = Array.from(range.getClientRects()).filter(r => r.width > 0 && r.height > 0);
    const r = rects.at(-1);
    note.hidden = !r || range.collapsed;
    if (note.hidden) return;
    const maxLeft = Math.max(8, innerWidth - note.offsetWidth - 8);
    note.style.left = Math.max(8, Math.min(r.left, maxLeft)) + 'px';
    note.style.top = (r.bottom + 8) + 'px';
  }
  let narrow = false;
  button.onclick = () => {
    narrow = !narrow;
    p.style.width = narrow ? '16rem' : '24rem';
    update();
  };
  const observer = new ResizeObserver(update);
  observer.observe(p);
  window.addEventListener('resize', update);
  window.addEventListener('scroll', update, true);
  document.fonts.ready.then(update);
  update();
  window.fragmentDemo = {destroy() {
    observer.disconnect();
    window.removeEventListener('resize', update);
    window.removeEventListener('scroll', update, true);
    if (canHighlight) CSS.highlights.delete('fragment-demo');
    host.remove(); note.remove(); style.remove();
  }};
})();

تستثني نقطتا النهاية السابقة Sample A: واللاحقة End.. انقر Change text width للتبديل بين عرضين للفقرة. يظل النطاق نفسه هو هدف التعليق التوضيحي، بينما يعاد تموضع التسمية أسفل آخر مقطع صالح للاستخدام. مرّر الصفحة وغيّر حجم النافذة لفحص السلوك. أزل العرض التجريبي باستخدام window.fragmentDemo.destroy().

اجتاز البرنامج فحص صياغة JavaScript، واجتازت إزاحاته النموذجية وحسابات مواضع المقاطع اختبارات حتمية. لم يُنفّذ عرض المتصفح هنا؛ وتوفر الخطوات أعلاه التحقق البصري.

كيف يحافظ العرض التجريبي على تدفق النص وتشكيله؟

يقيس النطاق النص الموجود. ويُبقي البرنامج العقد النصية للعبارة وعنصر التأكيد في مواضعها بعد إنشاء المثال. ويضيف التسمية بشكل منفصل، ويمكنه اختياريًا تلوين العبارة باستخدام واجهة CSS Custom Highlight API، التي توثّق MDN أنها تعمل من دون تغيير بنية DOM الأساسية. واجهة CSS custom highlight API.

هذا التمييز مهم عند تكييف النهج. قد تؤدي إضافة الحشو أو الحدود أو الهوامش المضمنة بين وحدات المحارف إلى إنشاء حدود للتشكيل. وتحدد W3C أيضًا vertical-align غير الابتدائية وحدود العزل ثنائي الاتجاه بوصفها شروطًا تكسر التشكيل. تجنب إدراج أغلفة محارف موضوعة بصورة فردية لمجرد الحصول على الإحداثيات؛ قِس النتيجة الموجودة في المتصفح. وحدة CSS Text المستوى 3، التشكيل عبر حدود العناصر.

بالنسبة إلى نص التطبيق الفعلي، اشتق نقاط نهاية النطاق من حدود النص المقصود. تستخدم إزاحات النص في DOM وحدات الشيفرة؛ وهي ليست عددًا للرموز الرسومية المرئية. اجعل نقاط النهاية متوافقة مع المحارف الكاملة المقصودة، بدلًا من افتراض أن كل محرف معروض يشغل إزاحة واحدة. معيار DOM.

التمييز اختياري. تدرج MDN Highlight ضمن Baseline 2025 وتشير إلى أن المتصفحات الأقدم قد لا تدعمها. ويحافظ فحص الميزة في العرض التجريبي على موضع التعليق التوضيحي عند عدم توفر التمييز، ويعرض رسالة حالة توضيحية. Highlight.

ما الذي يجب تحديثه عند إعادة تدفق الصفحة؟

يعيد العرض التجريبي القياس عند تغيير حجم الفقرة أو النافذة وعند التمرير. ترصد ResizeObserver التغييرات في أبعاد العناصر، بما في ذلك التغييرات غير المرتبطة بتغيير حجم نافذة المتصفح. ولا ينقل رد النداء هنا سوى التسمية المنفصلة، متجنبًا حلقة تغذية راجعة تغيّر حجم الفقرة المقيسة مرارًا. ResizeObserver.

يحتاج تحميل الخطوط إلى تحديث مستقل. يقيس البرنامج فورًا، ثم يعيد القياس عند اكتمال document.fonts.ready. وتذكر MDN أن هذا الوعد يكتمل بعد انتهاء تحميل خطوط المستند وعمليات التخطيط المرتبطة به. وينبغي للتغييرات اللاحقة في الخطوط أو المحتوى التي يجريها التطبيق أن تطلب قياسًا آخر صراحةً. الخاصية FontFaceSet: ready.

حافظ على نظام إحداثيات واحد. يستخدم العرض التجريبي تسمية ذات موضع ثابت على صفحة فارغة، ويطبق إحداثيات العميل مباشرةً. تؤدي إضافة إزاحات تمرير المستند إلى تحويلها إلى إحداثيات المستند. وتوضح MDN أن مستطيلات العميل محسوبة بالنسبة إلى مساحة العرض، وأن إضافة إزاحات التمرير هي طريقة التحويل للحصول على إحداثيات بالنسبة إلى المستند. الطريقة Element: getBoundingClientRect().

يضبط هذا المثال الصغير الموضع الأفقي ضمن الحدود، لكنه لا ينفذ تجنب التصادم العمودي أو إخفاء العناصر خارج الشاشة أو التعامل مع الحاويات المحوّلة اعتباطيًا. كما أنه لا يستعيد تعليقًا توضيحيًا بعد إعادة تحميل الصفحة أو الاستبدال الكامل للنص. في أحد التطبيقات، احتفظ بطريقة لتحديد المقطع المقصود، وأعد إنشاء نطاقه بعد التصيير، ثم أعد القياس.

متى ينبغي استخدام Floating UI؟

استخدم Floating UI عندما يحتاج موضع التسمية إلى مراعاة القص وإدارة دورة التحديث. تدعم واجهة المرجع الافتراضي الموثقة النطاق عبر طريقتين: getBoundingClientRect() التي تعيد المستطيل المحيط بالنطاق، وgetClientRects() التي تعيد مستطيلاته الجزئية. زوّد contextElement بعنصر حقيقي يحتوي النص، لكي يتوفر سياق عنصر لاكتشاف القص والتحديث. العناصر الافتراضية.

مرّر ذلك المرجع الافتراضي إلى computePosition() وأدرج inline() قرب بداية مصفوفة البرمجيات الوسيطة، قبل flip(). طابق تموضع CSS للعنصر العائم مع الاستراتيجية المختارة. تصف الوثائق flip() لتغيير الجوانب عند عدم كفاية المساحة، وshift() لتحريك التسمية ضمن حدود القص الخاصة بها. مضمن، computePosition.

ابدأ autoUpdate() فقط عندما يكون التعليق التوضيحي مثبتًا في الصفحة، واستدعِ دالة التنظيف التي يعيدها عند إزالة التسمية. وتشمل آليات التحديث الموثقة التمرير في العناصر السلفية وتغيير الحجم وتحولات التخطيط. ومع ذلك، أطلق تحديثًا بعد أي تغيير في التطبيق يغيّر مقاطع النص المختار من دون تغيير أبعاد الحاوية الخاضعة للرصد. autoUpdate.

شخّص مشكلة التعليق التوضيحي إذا ظل مظهره غير صحيح

اتبع هذه الفحوص بالترتيب حتى لا تحجب مشكلة هندسية مشكلةً في استهداف النص.

  1. تأكد من العبارة. افحص range.toString() وتحقق من نقطتي النهاية قبل تعديل الإحداثيات.
  2. افحص المقاطع. سجّل Array.from(range.getClientRects()). قد تعكس الإدخالات المتعددة المتراكبة محتوى مضمنًا متداخلًا؛ لا تفسر عددها على أنه عدد الأسطر.
  3. تحقق من قاعدة التموضع. يخدم المستطيل المحيط والمقطع الأخير والمقطع المحدد بالمؤشر سلوكيات مختلفة للتعليقات التوضيحية.
  4. تحقق من نظام الإحداثيات. في العرض التجريبي للصفحة الفارغة، أبقِ التسمية ثابتة الموضع ضمن إحداثيات العميل.
  5. أعد الفحص بعد تغييرات التخطيط. غيّر العرض، ومرّر الصفحة، وغيّر حجمها، وانتظر اكتمال تحميل الخطوط. أعد إنشاء النطاق إذا استبدل التطبيق عقده النصية.
  6. تحقق من افتراضات التخطيط. في الكتابة العمودية أو الاتجاهات المختلطة أو الأعمدة أو الحاويات المحوّلة، حدّد قاعدة مناسبة لاختيار المقاطع واختبرها قبل إعادة استخدام اختصار المقطع الأخير في العرض التجريبي.

يبدأ التعليق التوضيحي الموثوق بنطاق نصي دقيق وقاعدة تموضع واضحة. دع المتصفح يتولى التفاف النص وتشكيله، ثم أعد قياس مقاطعه المعروضة كلما تغير التخطيط المعني. وهذا يبقي الملاحظة مرتبطة بالعبارة مع السماح للكلمات بالتدفق بصورة طبيعية.

المصادر