סקירה כללית בסיסית על יצירת רכיב מותאם אישית של תיבת עזרה עם התאמה לצבע ונגישות.
בפוסט הזה אני רוצה לשתף את הטיפים שלי ליצירת רכיב <tool-tip>
בהתאמה אישית, נגיש ומתאים לצבע. אפשר לנסות את הדמו ולעיין במקור.
אם אתם מעדיפים סרטון, הנה גרסה של הפוסט הזה ב-YouTube:
סקירה כללית
הסבר קצר הוא שכבת-על לא אינטראקטיבית, לא מודאלית ולא חוסמת שמכילה מידע נוסף לממשקי משתמש. הוא מוסתר כברירת מחדל, ומוצג כשעובר מעליו העכבר או כשממקדים אותו. אי אפשר לבחור הסבר קצר או לבצע איתו פעולה ישירה. תיאורי העזרה לא מחליפים תוויות או מידע חשוב אחר. המשתמש צריך להיות מסוגל להשלים את המשימה ללא תיאורי עזרה.
Toggletip לעומת הסבר קצר
כמו לרכיבים רבים, יש תיאורים שונים לגבי ההסבר הקצר. לדוגמה ב-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>
יהיו במיקום 'top', אבל אפשר להתאים אישית את המיקום של רכיב על ידי הוספת 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;
}
למטה ניתן לראות את עץ הנגישות המעודכן, שעכשיו הוא כולל נקודה ופסיק אחרי טקסט הקישור והנחיה להסבר הקצר 'כולל הסבר קצר:'.
עכשיו, כשמשתמשים בקורא מסך מטרגטים את הקישור, כתוב 'top' ומופיע השהיה קטנה, ואז מכריז "יש tooltip: look, tooltips". כך יכול להיות לקורא מסך כמה טיפים טובים לגבי חוויית המשתמש. ההיסוס מספק הפרדה נחמדה בין טקסט הקישור לבין ההסבר הקצר. בנוסף, כשמוקרא "יש הסבר קצר", משתמשים בקורא מסך יכולים לבטל את ההודעה בקלות אם הם כבר שמעו אותה בעבר. זה דומה מאוד להעברת העכבר מעל התמונה והסרת העכבר במהירות, כי כבר ראיתם את ההודעה הנוספת. זה נראה כמו שוויון טוב בחוויית המשתמש.
סגנונות
האלמנט <tool-tip>
יהיה צאצא של האלמנט שמייצג את ההודעות המשלימות, לכן נתחיל קודם עם היסודות של אפקט שכבת-העל. כדי להסיר אותו מזרם המסמכים, משתמשים ב-position absolute
:
tool-tip {
position: absolute;
z-index: 1;
}
אם הרכיב ההורה הוא לא הקשר של סטאקינג, הסמל של ההנחיה יוצג ליד הרכיב הקרוב ביותר שהוא הקשר של סטאקינג, וזה לא מה שאנחנו רוצים. יש בורר חדש בבלוק שיכול לעזור, :has()
:
:has(> tool-tip) {
position: relative;
}
אין צורך לדאוג יותר מדי לגבי תמיכת הדפדפן. קודם כל, חשוב לזכור שההסברים הקצרים האלה
משלימים את התהליך. אם הם לא עובדים, זה בסדר. שנית, בקטע JavaScript נפרוס סקריפט כדי להוסיף פונקציונליות שתומכת ב-:has()
בדפדפנים ללא תמיכה ב-: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()
לא נתמך.
סיכום
עכשיו, אחרי שסיפרתי לך איך עשיתי את זה, איך היית עושה את זה? 🙂 אני מחכה מאוד ל-API של popup
כדי שיהיה קל יותר ליצור טיפים להחלפה, ל-שכבה העליונה כדי שלא תצטרכו להילחם עם z-index, ול-API של anchor
כדי למקם דברים בחלון בצורה טובה יותר. עד אז, אשלח הסבר קצר.
בואו לגוון את הגישות שלנו ונלמד את כל הדרכים לבניית אתרים באינטרנט.
אתם יכולים ליצור גרסת דמו, לשלוח לי קישורים בטוויטר ואוסיף אותה לקטע 'רמיקסים של הקהילה' שבהמשך.
רמיקסים של הקהילה
עדיין אין מה לראות כאן.
משאבים
- קוד המקור ב-GitHub