סקירה כללית בסיסית של בניית אלמנט מותאם אישית עם הסבר קצר ונגיש.
בפוסט הזה אני רוצה לשתף את דעתי לגבי בניית אלמנט מותאם אישית של <tool-tip>
, מותאם לצבעים ונגיש. נסו את ההדגמה וצפו במקור!
אם ברצונך ליצור סרטון, הנה גרסת YouTube של הפוסט הזה:
סקירה כללית
הסבר קצר הוא שכבת-על לא אינטראקטיבית ולא מודאלית, שאינה חוסמת, שמכילה מידע משלים לממשקי המשתמש. כברירת מחדל, ההסתרה מוסתרת ומבוטלת כשמעבירים את העכבר מעליה או מתמקדים בה. אי אפשר לבחור הסבר קצר או לבצע איתו אינטראקציה ישירות. הסברים קצרים הם לא תחליף לתוויות או למידע אחר בעל ערך גבוה, והמשתמשים צריכים להיות מסוגלים להשלים את המשימה באופן מלא בלי הסבר קצר.
החלפת מצב טיפים לעומת הסבר קצר
כמו ברכיבים רבים, גם בהסבר הקצר יש תיאורים שונים של ההסבר הקצר, לדוגמה ב-MDN, ב-WAI ARIA, ב-Sarah Higley וב-Inclusive Components. אני אוהב את ההפרדה בין הסברים קצרים לטיפים. הסבר קצר צריך לכלול מידע משלים לא אינטראקטיבי, והסבר קצר יכול להכיל מידע חשוב ואינטראקטיבי. הסיבה העיקרית לחלוקה היא נגישות – איך המשתמשים מצפים לנווט לחלון הקופץ ולקבל גישה למידע וללחצנים שבתוכו. קלידים נעשים מורכבים במהירות.
בסרטון הבא תוכלו לראות הסבר על החלפת המצב מהאתר עיצוב, שכבת-על עם אינטראקטיביות שהמשתמש יכול להצמיד כדי לפתוח ולחקור אותה, ולאחר מכן לסגור אותה באמצעות כיבוי אור או באמצעות מקש Escape:
אתגר ממשק ה-GUI היה במסלול של הסבר קצר, שנועד לעשות כמעט כל דבר עם 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;
}
למטה מופיע עץ הנגישות המעודכן, שעכשיו מופיע בו נקודה ופסיק אחרי טקסט הקישור ובקשה להסבר הקצר 'כולל הסבר קצר: '.
עכשיו, כשמשתמש בקורא מסך מתמקד בקישור, כתוב "למעלה" ומבצע השהיה קצרה, ואז מכריז "יש הסבר קצר: להסתכל, הסברים קצרים". זה נותן למשתמש בקורא מסך כמה רמזים יפים לגבי חוויית המשתמש. ההיסוס יוצר הפרדה נחמדה בין הטקסט של הקישור להסבר הקצר. בנוסף, כשמתקבלת ההודעה "עם הסבר קצר", משתמשים עם קורא מסך יכולים לבטל את ההודעה בקלות אם הם כבר שמעו את ההודעה בעבר. זה מזכיר מאוד את העברת העכבר מעל התצוגה במהירות, מכיוון שכבר ראית את ההודעה המשלימה. הרגשתי איזון בחוויית המשתמש.
סגנונות
הרכיב <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;
}
}
שימו לב לכך שהמצב הזה מוגדר כ-'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
ל-API כדי להקל על החלפת המצב, לשכבה העליונה ללא קרבות z-index ול-
anchor
API למיקום טוב יותר של הדברים בחלון. עד אז, אני אכין
הסברים קצרים.
בואו נגוון את הגישות שלנו ונלמד את כל הדרכים לבנות באינטרנט.
צור הדגמה (דמו), ציוץ לי קישורים ואני אוסיף אותה לקטע 'רמיקסים של הקהילה' למטה!
רמיקסים של הקהילה
עדיין אין כאן מה לראות.
משאבים
- קוד מקור ב-GitHub