نظرة عامة أساسية حول كيفية إنشاء عنصر مخصّص لأداة تلميحية متوافق مع الألوان ويمكن الوصول إليه
في هذه المشاركة، أريد أن أشارك أفكاري حول كيفية إنشاء <tool-tip>
عنصر مخصّص<tool-tip>
متوافق مع الألوان ويمكن الوصول إليه. جرِّب
العرض التوضيحي واطّلِع على
المصدر.
إذا كنت تفضّل مشاهدة فيديو، إليك نسخة من هذا المنشور على YouTube:
نظرة عامة
تتألف تلميحات الأدوات من طبقة غير مشروطة وغير حاصرة وغير تفاعلية تحتوي على معلومات تكميلية لواجهات المستخدم. ويكون مخفيًا تلقائيًا، ويتم إظهاره عند تمرير مؤشر الماوس فوق عنصر مرتبط أو التركيز عليه. لا يمكن تحديد التلميح أو التفاعل معه مباشرةً. لا تشكّل تلميحات الأدوات بديلاً عن التصنيفات أو المعلومات الأخرى المهمة، ويجب أن يتمكّن المستخدم من إكمال مهمته بالكامل بدون تلميح أداة.

لا تعتمد على تلميحات الأدوات بدلاً من التصنيفات
Toggletip في مقابل Tooltip
كما هو الحال مع العديد من المكوّنات، تتوفّر أوصاف مختلفة لماهية تلميح الأدوات، مثلاً في MDN وWAI ARIA وSarah Higley وInclusive Components. أعجبني الفصل بين تلميحات الأدوات وتلميحات التبديل. يجب أن تحتوي تلميحات الأدوات على معلومات تكميلية غير تفاعلية، بينما يمكن أن تحتوي تلميحات التبديل على عناصر تفاعلية ومعلومات مهمة. السبب الرئيسي لهذا التقسيم هو إمكانية الوصول، أي كيف يُتوقّع من المستخدمين الانتقال إلى النافذة المنبثقة والوصول إلى المعلومات والأزرار بداخلها. تصبح تلميحات التبديل معقّدة بسرعة.
في ما يلي فيديو يعرض أداة toggletip من موقع Designcember، وهي عبارة عن طبقة تتضمّن عناصر تفاعلية يمكن للمستخدم تثبيتها مفتوحة واستكشافها، ثم إغلاقها من خلال الإغلاق السريع أو مفتاح Escape:
اتّبعت مسابقة تحدّي واجهة المستخدم الرسومية مسار تلميح الأدوات، وسعت إلى تنفيذ كل شيء تقريبًا باستخدام CSS، وفي ما يلي كيفية إنشائه.
Markup
لقد اخترتُ استخدام عنصر مخصّص <tool-tip>
. لا يحتاج المؤلّفون إلى تحويل العناصر المخصّصة إلى مكوّنات ويب إذا كانوا لا يريدون ذلك. سيتعامل المتصفّح مع
<foo-bar>
تمامًا كما يتعامل مع <div>
. يمكنك التفكير في عنصر مخصّص على أنّه
فئة ذات تحديد أقل. ولا يتضمّن ذلك أي JavaScript.
<tool-tip>A tooltip</tool-tip>
هذا يشبه div مع بعض النص بداخله. يمكننا الربط بشجرة تسهيل الاستخدام
لبرامج قراءة الشاشة المتوافقة من خلال إضافة [role="tooltip"]
.
<tool-tip role="tooltip">A tooltip</tool-tip>
أما الآن، فسيتم التعرّف عليه على أنّه تلميح. في المثال التالي، لاحظ كيف أنّ عنصر الرابط الأول يتضمّن عنصر تلميح أدوات معروفًا في شجرته، بينما لا يتضمّن عنصر الرابط الثاني أي عنصر. لا يملك المستخدم الثاني هذا الدور. في قسم "الأنماط"، سنعمل على تحسين طريقة العرض الشجري هذه.
بعد ذلك، يجب أن يكون تلميح الأدوات غير قابل للتركيز عليه. إذا لم يتمكّن قارئ الشاشة من فهم دور تلميح الأدوات، سيسمح للمستخدمين بالتركيز على <tool-tip>
لقراءة المحتوى، وهذا ليس ضروريًا لتجربة المستخدم. ستضيف برامج قراءة الشاشة المحتوى إلى العنصر الرئيسي، وبالتالي لن يحتاج إلى التركيز ليصبح متاحًا. يمكننا هنا استخدام inert
للتأكّد من أنّ أي مستخدم لن يعثر عن طريق الخطأ على محتوى تلميح الأدوات هذا في مسار علامات التبويب:
<tool-tip inert role="tooltip">A tooltip</tool-tip>
بعد ذلك، اخترت استخدام السمات كواجهة لتحديد موضع تلميح الأدوات. ستفترض جميع <tool-tip>
s تلقائيًا الموضع "أعلى"، ولكن يمكن تخصيص الموضع على مستوى العنصر من خلال إضافة tip-position
:
<tool-tip role="tooltip" tip-position="right ">A tooltip</tool-tip>
أميل إلى استخدام السمات بدلاً من الفئات في حالات كهذه، وذلك لكي لا يتم تعيين مواضع متعددة إلى <tool-tip>
في الوقت نفسه.
يمكن أن يكون هناك واحد فقط أو لا شيء.
أخيرًا، ضَع عناصر <tool-tip>
داخل العنصر الذي تريد توفير تلميح أدوات له. في ما يلي، أشارك نص alt
مع المستخدمين المبصرين من خلال وضع صورة و<tool-tip>
داخل عنصر <picture>
:
<picture>
<img alt="The GUI Challenges skull logo" width="100" src="...">
<tool-tip role="tooltip" tip-position="bottom">
The <b>GUI Challenges</b> skull logo
</tool-tip>
</picture>
في ما يلي، أضع <tool-tip>
داخل عنصر
<abbr>
:
<p>
The <abbr>HTML <tool-tip role="tooltip" tip-position="top">Hyper Text Markup Language</tool-tip></abbr> abbr element.
</p>
تسهيل الاستخدام
بما أنّني اخترت إنشاء تلميحات أدوات وليس تلميحات تبديل، سيكون هذا القسم أبسط بكثير. أولاً، سأوضّح تجربة المستخدم المطلوبة:
- في المساحات المحدودة أو الواجهات المزدحمة، يمكنك إخفاء الرسائل التكميلية.
- عندما يمرّر المستخدم مؤشر الماوس فوق عنصر أو يركّز عليه أو يستخدم اللمس للتفاعل معه، يجب عرض الرسالة.
- عندما ينتهي التمرير أو التركيز أو اللمس، يتم إخفاء الرسالة مرة أخرى.
- أخيرًا، تأكَّد من تقليل أي حركة إذا حدّد المستخدم خيارًا مفضّلاً لتقليل الحركة.
هدفنا هو توفير رسائل تكميلية عند الطلب. يمكن لمستخدم الماوس أو لوحة المفاتيح المبصر تمرير المؤشر للكشف عن الرسالة وقراءتها بعينيه. يمكن لمستخدم قارئ الشاشة المكفوف التركيز للكشف عن الرسالة، وتلقّيها بشكل مسموع من خلال أداته.

في القسم السابق، تناولنا شجرة تسهيل الاستخدام ودور تلميح الأدوات وinert، وما تبقّى هو اختباره والتأكّد من أنّ تجربة المستخدم تعرض رسالة تلميح الأدوات بشكل مناسب. بعد الاختبار، تبيّن أنّه من غير الواضح أي جزء من الرسالة المسموعة هو تلميح أداة. ويمكن رؤية ذلك أثناء تصحيح الأخطاء في شجرة تسهيل الاستخدام أيضًا، حيث يتم دمج نص الرابط "أعلى" مع "انظر، تلميحات الأدوات!" بدون توقّف. لا يمكن لبرنامج قراءة الشاشة تقسيم النص أو التعرّف عليه كمحتوى خاص بأداة شرح.
أضِف عنصرًا زائفًا مخصصًا لبرامج قراءة الشاشة فقط إلى <tool-tip>
، وبذلك يمكننا إضافة نص الطلب الخاص بنا للمستخدمين المكفوفين.
&::before {
content: "; Has tooltip: ";
clip: rect(1px, 1px, 1px, 1px);
clip-path: inset(50%);
height: 1px;
width: 1px;
margin: -1px;
overflow: hidden;
padding: 0;
position: absolute;
}
في ما يلي شجرة تسهيل الاستخدام المعدَّلة، والتي تتضمّن الآن فاصلة منقوطة بعد نص الرابط وطلبًا لعنوان "يتضمّن تلميحًا: ".
الآن، عندما يركّز مستخدم قارئ الشاشة على الرابط، سيقول "أعلى" ويتوقف قليلاً، ثم سيقول "يتضمّن تلميحًا: انظر، تلميحات". يقدّم ذلك بعض التلميحات المفيدة بشأن تجربة المستخدم لمستخدمي قارئ الشاشة. يوفّر التوقف المؤقت فاصلاً مناسبًا بين نص الرابط وتلميح الأداة. بالإضافة إلى ذلك، عند الإعلان عن "يحتوي على تلميح"، يمكن لمستخدم قارئ الشاشة إلغاء التلميح بسهولة إذا كان قد سمعه من قبل. وهي تشبه كثيرًا التمرير السريع للمؤشر فوق العنصر ثم إبعاده عنه، لأنّك تكون قد رأيت الرسالة التكميلية. بدا ذلك وكأنّه تكافؤ جيد في تجربة المستخدم.
الأنماط
سيكون العنصر <tool-tip>
عنصرًا ثانويًا للعنصر الذي يمثّل الرسائل التكميلية، لذا لنبدأ أولاً بالأساسيات الخاصة بتأثير التراكب. إزالة العنصر من تدفّق المستندات باستخدام position absolute
:
tool-tip {
position: absolute;
z-index: 1;
}
إذا لم يكن العنصر الرئيسي سياقًا مكدسًا، سيتم وضع تلميح الأداة بجانب أقرب سياق مكدس، وهذا ليس ما نريده. يتوفّر أداة اختيار جديدة في
القسم يمكن أن تساعدك، وهي :has()
:
:has(> tool-tip) {
position: relative;
}
لا تقلق كثيرًا بشأن توافق المتصفّح. أولاً، تذكَّر أنّ تلميحات الأدوات هذه هي تكميلية. إذا لم تنجح هذه الطريقة، لا بأس في ذلك. ثانيًا، في قسم JavaScript، سننشر نصًا برمجيًا لتوفير الوظائف التي نحتاج إليها للمتصفّحات التي لا تتوافق مع :has()
.
بعد ذلك، لنحوّل تلميحات الأدوات إلى عناصر غير تفاعلية حتى لا تسرق أحداث المؤشر من العنصر الرئيسي:
tool-tip {
…
pointer-events: none;
user-select: none;
}
بعد ذلك، أخفِ التلميح باستخدام التعتيم حتى نتمكّن من نقل التلميح باستخدام تلاشٍ تدريجي:
tool-tip {
opacity: 0;
}
:has(> tool-tip):is(:hover, :focus-visible, :active) > tool-tip {
opacity: 1;
}
تتولّى السمتان :is()
و:has()
الجزء الأكبر من العمل هنا، ما يجعل السمة tool-tip
التي تحتوي على العناصر الرئيسية على دراية بتفاعل المستخدم من أجل تبديل مستوى ظهور تلميح الأدوات التابع. يمكن لمستخدمي الماوس التمرير فوق العناصر، ويمكن لمستخدمي لوحة المفاتيح وقارئ الشاشة التركيز على العناصر، ويمكن لمستخدمي اللمس النقر على العناصر.
بعد أن أصبحت ميزة عرض الطبقة المتراكبة وإخفائها تعمل للمستخدمين المبصرين، حان الوقت لإضافة بعض الأنماط لتحديد المظهر والموضع وإضافة شكل المثلث إلى الفقاعة. تبدأ الأنماط التالية باستخدام خصائص مخصّصة، مع الاستفادة مما أنجزناه حتى الآن، بالإضافة إلى إضافة ظلال وأسلوب خطوط وألوان لكي يبدو وكأنه تلميح أدوات عائم:
tool-tip {
--_p-inline: 1.5ch;
--_p-block: .75ch;
--_triangle-size: 7px;
--_bg: hsl(0 0% 20%);
--_shadow-alpha: 50%;
--_bottom-tip: conic-gradient(from -30deg at bottom, rgba(0,0,0,0), #000 1deg 60deg, rgba(0,0,0,0) 61deg) bottom / 100% 50% no-repeat;
--_top-tip: conic-gradient(from 150deg at top, rgba(0,0,0,0), #000 1deg 60deg, rgba(0,0,0,0) 61deg) top / 100% 50% no-repeat;
--_right-tip: conic-gradient(from -120deg at right, rgba(0,0,0,0), #000 1deg 60deg, rgba(0,0,0,0) 61deg) right / 50% 100% no-repeat;
--_left-tip: conic-gradient(from 60deg at left, rgba(0,0,0,0), #000 1deg 60deg, rgba(0,0,0,0) 61deg) left / 50% 100% no-repeat;
pointer-events: none;
user-select: none;
opacity: 0;
transform: translateX(var(--_x, 0)) translateY(var(--_y, 0));
transition: opacity .2s ease, transform .2s ease;
position: absolute;
z-index: 1;
inline-size: max-content;
max-inline-size: 25ch;
text-align: start;
font-size: 1rem;
font-weight: normal;
line-height: normal;
line-height: initial;
padding: var(--_p-block) var(--_p-inline);
margin: 0;
border-radius: 5px;
background: var(--_bg);
color: CanvasText;
will-change: filter;
filter:
drop-shadow(0 3px 3px hsl(0 0% 0% / var(--_shadow-alpha)))
drop-shadow(0 12px 12px hsl(0 0% 0% / var(--_shadow-alpha)));
}
/* create a stacking context for elements with > tool-tips */
:has(> tool-tip) {
position: relative;
}
/* when those parent elements have focus, hover, etc */
:has(> tool-tip):is(:hover, :focus-visible, :active) > tool-tip {
opacity: 1;
transition-delay: 200ms;
}
/* prepend some prose for screen readers only */
tool-tip::before {
content: "; Has tooltip: ";
clip: rect(1px, 1px, 1px, 1px);
clip-path: inset(50%);
height: 1px;
width: 1px;
margin: -1px;
overflow: hidden;
padding: 0;
position: absolute;
}
/* tooltip shape is a pseudo element so we can cast a shadow */
tool-tip::after {
content: "";
background: var(--_bg);
position: absolute;
z-index: -1;
inset: 0;
mask: var(--_tip);
}
/* top tooltip styles */
tool-tip:is(
[tip-position="top"],
[tip-position="block-start"],
:not([tip-position]),
[tip-position="bottom"],
[tip-position="block-end"]
) {
text-align: center;
}
تعديلات المظهر
لا تحتوي تلميحات الأدوات إلا على بضعة ألوان يمكن إدارتها، لأنّ لون النص مأخوذ من الصفحة باستخدام الكلمة الرئيسية CanvasText
في النظام. بالإضافة إلى ذلك، بما أنّنا أنشأنا خصائص مخصّصة لتخزين القيم، يمكننا تعديل هذه الخصائص المخصّصة فقط وترك الباقي للسمة:
@media (prefers-color-scheme: light) {
tool-tip {
--_bg: white;
--_shadow-alpha: 15%;
}
}
في المظهر الفاتح، نعدّل الخلفية إلى اللون الأبيض ونخفّف من حدة الظلال من خلال ضبط مستوى الشفافية.
من اليسار إلى اليمين
من أجل توفير أوضاع القراءة من اليمين إلى اليسار، سيتم تخزين قيمة اتجاه المستند في سمة مخصّصة، وذلك باستخدام القيمة -1 أو 1 على التوالي.
tool-tip {
--isRTL: -1;
}
tool-tip:dir(rtl) {
--isRTL: 1;
}
يمكن استخدام هذه السمة للمساعدة في تحديد موضع تلميح الأدوات:
tool-tip[tip-position="top"]) {
--_x: calc(50% * var(--isRTL));
}
بالإضافة إلى المساعدة في تحديد مكان المثلث:
tool-tip[tip-position="right"]::after {
--_tip: var(--_left-tip);
}
tool-tip[tip-position="right"]:dir(rtl)::after {
--_tip: var(--_right-tip);
}
أخيرًا، يمكن استخدامها أيضًا لإجراء عمليات تحويل منطقية على translateX()
:
--_x: calc(var(--isRTL) * -3px * -1);
موضع التلميح
حدِّد موضع تلميح الأدوات بشكل منطقي باستخدام السمتَين inset-block
أو inset-inline
للتعامل مع مواضع تلميح الأدوات الفعلية والمنطقية. يوضّح الرمز التالي كيفية تنسيق كل موضع من المواضع الأربعة في الاتجاهين من اليمين إلى اليسار ومن اليسار إلى اليمين.
المحاذاة إلى أعلى وبداية الكتلة
tool-tip:is([tip-position="top"], [tip-position="block-start"], :not([tip-position])) {
inset-inline-start: 50%;
inset-block-end: calc(100% + var(--_p-block) + var(--_triangle-size));
--_x: calc(50% * var(--isRTL));
}
tool-tip:is([tip-position="top"], [tip-position="block-start"], :not([tip-position]))::after {
--_tip: var(--_bottom-tip);
inset-block-end: calc(var(--_triangle-size) * -1);
border-block-end: var(--_triangle-size) solid transparent;
}
المحاذاة إلى اليمين وإلى نهاية السطر
tool-tip:is([tip-position="right"], [tip-position="inline-end"]) {
inset-inline-start: calc(100% + var(--_p-inline) + var(--_triangle-size));
inset-block-end: 50%;
--_y: 50%;
}
tool-tip:is([tip-position="right"], [tip-position="inline-end"])::after {
--_tip: var(--_left-tip);
inset-inline-start: calc(var(--_triangle-size) * -1);
border-inline-start: var(--_triangle-size) solid transparent;
}
tool-tip:is([tip-position="right"], [tip-position="inline-end"]):dir(rtl)::after {
--_tip: var(--_right-tip);
}
المحاذاة إلى الأسفل وإلى نهاية الكتلة
tool-tip:is([tip-position="bottom"], [tip-position="block-end"]) {
inset-inline-start: 50%;
inset-block-start: calc(100% + var(--_p-block) + var(--_triangle-size));
--_x: calc(50% * var(--isRTL));
}
tool-tip:is([tip-position="bottom"], [tip-position="block-end"])::after {
--_tip: var(--_top-tip);
inset-block-start: calc(var(--_triangle-size) * -1);
border-block-start: var(--_triangle-size) solid transparent;
}
المحاذاة إلى اليسار وإلى بداية السطر
tool-tip:is([tip-position="left"], [tip-position="inline-start"]) {
inset-inline-end: calc(100% + var(--_p-inline) + var(--_triangle-size));
inset-block-end: 50%;
--_y: 50%;
}
tool-tip:is([tip-position="left"], [tip-position="inline-start"])::after {
--_tip: var(--_right-tip);
inset-inline-end: calc(var(--_triangle-size) * -1);
border-inline-end: var(--_triangle-size) solid transparent;
}
tool-tip:is([tip-position="left"], [tip-position="inline-start"]):dir(rtl)::after {
--_tip: var(--_left-tip);
}
Animation
حتى الآن، لم نفعّل سوى إمكانية إظهار تلميح الأدوات أو إخفائه. في هذا القسم، سنحرّك أولاً مستوى الشفافية لدى جميع المستخدمين، لأنّها عملية انتقال آمنة بشكل عام ومناسبة لتقليل الحركة. بعد ذلك، سنحرّك موضع التحويل لكي يظهر تلميح الأداة وكأنّه ينزلق خارج العنصر الرئيسي.
انتقال تلقائي آمن وفعّال
نسِّق عنصر تلميح الأداة للانتقال إلى التعتيم والتحويل، على النحو التالي:
tool-tip {
opacity: 0;
transform: translateX(var(--_x, 0)) translateY(var(--_y, 0));
transition: opacity .2s ease, transform .2s ease;
}
:has(> tool-tip):is(:hover, :focus-visible, :active) > tool-tip {
opacity: 1;
transition-delay: 200ms;
}
إضافة حركة إلى تأثير الانتقال
بالنسبة إلى كل جانب يمكن أن تظهر عليه تلميحات الأدوات، إذا كان المستخدم موافقًا على الحركة، اضبط موضع السمة translateX قليلاً من خلال منحها مسافة صغيرة للانتقال منها:
@media (prefers-reduced-motion: no-preference) {
:has(> tool-tip:is([tip-position="top"], [tip-position="block-start"], :not([tip-position]))):not(:hover):not(:focus-visible):not(:active) tool-tip {
--_y: 3px;
}
:has(> tool-tip:is([tip-position="right"], [tip-position="inline-end"])):not(:hover):not(:focus-visible):not(:active) tool-tip {
--_x: -3px;
}
:has(> tool-tip:is([tip-position="bottom"], [tip-position="block-end"])):not(:hover):not(:focus-visible):not(:active) tool-tip {
--_y: -3px;
}
:has(> tool-tip:is([tip-position="left"], [tip-position="inline-start"])):not(:hover):not(:focus-visible):not(:active) tool-tip {
--_x: 3px;
}
}
لاحظ أنّ هذا الإعداد يضبط الحالة "خارج"، لأنّ الحالة "داخل" تكون عند translateX(0)
.
JavaScript
في رأيي، إنّ JavaScript اختيارية. ويرجع ذلك إلى أنّه لا يجب أن تكون أي من تلميحات الأدوات هذه قراءة إلزامية لإنجاز مهمة في واجهة المستخدم. لذلك، إذا تعذّر عرض تلميحات الأدوات تمامًا، لن تكون مشكلة كبيرة. وهذا يعني أيضًا أنّه يمكننا التعامل مع تلميحات الأدوات على أنّها محسّنة بشكل تدريجي. في النهاية، ستتيح جميع المتصفحات استخدام
:has()
، ويمكن إزالة هذا النص البرمجي بالكامل.
ينفّذ نص polyfill البرمجي إجراءَين، ولا ينفّذهما إلا إذا كان المتصفّح لا يتوافق مع :has()
. أولاً، تحقَّق من توفّر دعم :has()
:
if (!CSS.supports('selector(:has(*))')) {
// do work
}
بعد ذلك، ابحث عن العناصر الرئيسية التي تحتوي على <tool-tip>
وأضِف إليها اسم فئة للعمل بها:
if (!CSS.supports('selector(:has(*))')) {
document.querySelectorAll('tool-tip').forEach(tooltip =>
tooltip.parentNode.classList.add('has_tool-tip'))
}
بعد ذلك، أدرِج مجموعة من الأنماط التي تستخدم اسم الفئة هذا، مع محاكاة أداة الاختيار :has()
للحصول على السلوك نفسه تمامًا:
if (!CSS.supports('selector(:has(*))')) {
document.querySelectorAll('tool-tip').forEach(tooltip =>
tooltip.parentNode.classList.add('has_tool-tip'))
let styles = document.createElement('style')
styles.textContent = `
.has_tool-tip {
position: relative;
}
.has_tool-tip:is(:hover, :focus-visible, :active) > tool-tip {
opacity: 1;
transition-delay: 200ms;
}
`
document.head.appendChild(styles)
}
هذا كل ما في الأمر، ستعرض جميع المتصفحات الآن تلميحات الأدوات إذا لم يكن :has()
متوافقًا.
الخاتمة
بعد أن عرفت كيف تمكّنت من ذلك، كيف ستفعلها أنت؟ 🙂 أتطلّع إلى استخدام popup
API لتسهيل استخدام أزرار التبديل، والطبقة
العلوية لتجنُّب مشاكل z-index، وanchor
API لتحديد موضع العناصر بشكل أفضل في النافذة. حتى ذلك الحين، سأواصل إنشاء تلميحات الأدوات.
لنستكشف الطرق المختلفة لإنشاء مواقع إلكترونية على الويب.
يمكنك إنشاء عرض توضيحي، إرسال تغريدة إليّ تتضمّن رابطًا إليه، وسأضيفه إلى قسم "ريمكسات من المنتدى" أدناه.
ريمكسات من إنشاء المنتدى
ما مِن عناصر للاطّلاع عليها هنا حتى الآن.
الموارد
- رمز المصدر على Github