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

لا تعتمد على التلميحات بدلاً من التصنيفات
Toggletip في مقابل Tooltip
مثل العديد من المكوّنات، هناك أوصاف مختلفة لماهية التلميح، على سبيل المثال، في MDN، WAI ARIA، وسارة هيغلي، وInclusive Components. أحبّ الفصل بين نصائح التلميح ونصائح التبديل. يجب أن يحتوي التلميح على معلومات تكميلية غير تفاعلية، في حين يمكن أن يحتوي التلميح القابل للتبديل على تفاعل ومعلومات مهمة. السبب الرئيسي لهذا التقسيم هو تسهيل الاستخدام، أي كيفية توقّع المستخدمين للانتقال إلى النافذة المنبثقة والوصول إلى المعلومات والأزرار فيها. تصبح نصائح التبديل معقّدة بسرعة.
في ما يلي فيديو يعرض زرّ تبديل من موقع 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>
تلقائيًا في موضع "أعلى"، ولكن يمكن تخصيص
الموضع لأي عنصر من خلال إضافة 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>
تسهيل الاستخدام
بما أنّني اخترت إنشاء نصائح أدوات وليس نصائح مفاتيح التبديل، فإنّ هذا القسم أبسط بكثير. أولاً، دعني أوضّح تجربة المستخدم المطلوبة:
- في المساحات المحدودة أو الواجهات المعقدة، يمكنك إخفاء الرسائل التكميلية.
- عندما يمرّر المستخدم مؤشر الماوس فوق عنصر أو يركّز عليه أو يستخدم اللمس للتفاعل معه، يجب إظهار الرسالة.
- عند انتهاء التمرير بمؤشر الماوس أو التركيز أو اللمس، يتم إخفاء الرسالة مرة أخرى.
- أخيرًا، تأكَّد من تقليل أي حركة إذا حدّد المستخدم إعدادًا مفضّلاً ل تقليل الحركة.
هدفنا هو توفير رسائل تكميلية عند الطلب. يمكن لمستخدم الماوس أو لوحة المفاتيح المبصرين تمرير مؤشر الماوس فوق الرسالة لإظهارها وقراءتها. يمكن لمستخدم قارئ الشاشة غير المبصر التركيز على إظهار الرسالة، وتلقّيها صوتيًا من خلال أداته.

في القسم السابق، غطّينا شجرة تسهيل الاستخدام ودور التلميح و العنصر غير النشط. ما بقي هو اختباره والتأكّد من تجربة المستخدم بشكلٍ مناسب لإظهار رسالة التلميح للمستخدم. بعد الاختبار، لم يتضح لنا أي جزء من الرسالة الصوتية هو تلميح. يمكن أيضًا الاطّلاع عليه أثناء تصحيح الأخطاء في شجرة تسهيل الاستخدام، حيث يتم تشغيل نص الرابط "top" معًا، بدون تردد، مع "Look, tooltips!". لا يقسّم برنامج قراءة الشاشة النص أو يحدّده كمحتوى تلميح.
أضِف عنصرًا زائفًا لبرامج قراءة الشاشة فقط إلى <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;
}
يمكنك الاطّلاع أدناه على شجرة تسهيل الاستخدام المعدَّلة التي تتضمّن الآن فاصلة منقوطة بعد نص الرابط وطلبًا للنص التوضيحي "يتضمّن نصًا توضيحيًا: ".
الآن، عندما يركز مستخدم قارئ الشاشة على الرابط، يُعلن قارئ الشاشة عن "أعلى" ويتوقف قليلاً، ثم يُعلن عن "يحتوي على تلميح: انظر، تلميحات". يقدّم ذلك لمستخدم قارئ الشاشة بعض النصائح الرائعة حول تجربة المستخدم. يمنح الفاصل الفواصل الجميلة بين نص الرابط ونص التلميح. بالإضافة إلى ذلك، عند الإعلان عن "has tooltip"، يمكن لمستخدم تطبيقات قراءة الشاشة إلغاء الوصف بسهولة إذا سبق له سماعه. يشبه ذلك إلى حد كبير التمرير سريعًا فوق العنصر ثم إزالته من فوقه، لأنّك سبق أن رأيت الرسالة التكميلية. لقد كان هذا الشعور مشابهًا لتجربة المستخدم الرائعة.
الأنماط
سيكون العنصر <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
واجهة برمجة التطبيقات لإنشاء نصائح التبديل بسهولة، والطبقة العلوية لتجنّب الصعوبات المتعلّقة بترتيب عناصر z-index، وanchor
واجهة برمجة التطبيقات لتحديد موضع العناصر في النافذة بشكل أفضل. وحتى ذلك الحين، سأعمل على إنشاء
نصائح توضيحية.
لننوّع أساليبنا ونتعرّف على جميع الطرق لإنشاء تطبيقات على الويب.
أنشئ عرضًا توضيحيًا وأرسِل إلينا رابطًا على Twitter، وسنضيفه إلى قسم الريمكسات التي أنشأها المستخدمون أدناه.
الريمكسات التي أنشأها المستخدمون
ما مِن عناصر للاطّلاع عليها هنا حتى الآن.
الموارد
- الرمز المصدر على GitHub