نظرة عامة أساسية حول كيفية إنشاء عنصر مخصّص قابل للوصول إليه ومتوافق مع الألوان
في هذه المشاركة، أريد مشاركة أفكاري حول كيفية إنشاء عنصر <tool-tip>
مخصّص يتكيّف مع الألوان ويسهل الوصول إليه. جرِّب الإصدار التمهيدي واطّلِع على ملف المصدر.
إذا كنت تفضّل الفيديو، يمكنك الاطّلاع على نسخة من هذه المشاركة على YouTube:
نظرة عامة
التلميح هو عنصر مركّب غير مشروط وغير حظر وغير تفاعلي يحتوي على معلومات إضافية لواجهات المستخدم. يكون مخفيًا بشكل افتراضي ويصبح غير مخفي عند التمرير فوق عنصر مرتبط أو التركيز عليه. لا يمكن اختيار تلميح أو التفاعل معه مباشرةً. التلميحات ليست بدائل للتصنيفات أو المعلومات الأخرى عالية القيمة، يجب أن يكون المستخدم قادرًا على إكمال مهمته بالكامل دون تلميح.
Toggletip في مقابل Tooltip
مثل العديد من المكوّنات، هناك أوصاف مختلفة لماهية التلميح، على سبيل المثال في MDN وWAI ARIA وSarah Higley والمكوّنات الشاملة. أحب الفصل بين التلميحات وتلميحات التبديل. يجب أن يحتوي التلميح على معلومات تكميلية غير تفاعلية، في حين يمكن أن يحتوي التلميح القابل للتبديل على تفاعل ومعلومات مهمة. السبب الرئيسي لهذا التقسيم هو تسهيل الاستخدام، أي كيفية توقّع المستخدمين للانتقال إلى النافذة المنبثقة والوصول إلى المعلومات والأزرار فيها. تصبح نصائح التبديل معقّدة بسرعة.
في ما يلي فيديو يعرض زرّ تبديل من موقع 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، سننشر نصًا برمجيًا لإضافة polyfill إلى الوظائف التي نحتاج إليها
في المتصفّحات التي لا تتوافق مع :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;
}
}
يُرجى العلم أنّ هذا الإجراء يؤدي إلى ضبط الحالة "out"، لأنّ الحالة "in" هي في 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
واجهة برمجة التطبيقات لإنشاء نصائح التبديل بسهولة، وtop
layer لتجنّب الصعوبات المتعلّقة بترتيب عناصر z-index، وanchor
واجهة برمجة التطبيقات لتحديد موضع العناصر في النافذة بشكل أفضل. حتى ذلك الحين، سأقوم
بإنشاء تلميحات.
لننوّع أساليبنا ونتعرّف على جميع الطرق لإنشاء تطبيقات على الويب.
أنشئ عرضًا توضيحيًا وأرسِل إلينا رابطًا على Twitter، وسنضيفه إلى قسم الريمكسات التي أنشأها المستخدمون أدناه.
ريمكسات من إنشاء المنتدى
ما مِن عناصر للاطّلاع عليها هنا حتى الآن.
الموارد
- الرمز المصدر على Github