התרחיש כמעט תמיד זהה, שהוא טבלת נתונים בתוך מיכל שניתן לגלול. לכל שורה יש תפריט פעולה, תפריט נפתח קטן עם כמה אפשרויות, כמו עריכה, שכפול ומחק. אתה בונה אותו, נראה שהוא עובד בצורה מושלמת בבידוד, ואז מישהו שם אותו בתוך ה-div שניתן לגלול ודברים מתפרקים. ראיתי את הבאג המדויק הזה בשלושה בסיסי קוד שונים: הקונטיינר, המחסנית והמסגרת, כולם שונים. עם זאת, הבאג זהה לחלוטין. התפריט הנפתח נקטע בקצה המכולה. או שהוא מופיע מאחורי תוכן שאמור להיות מתחתיו באופן הגיוני. או שזה עובד בסדר עד שהמשתמש גולל, ואז זה נסחף. אתה מגיע ל-z-index: 9999. לפעמים זה עוזר, אבל לפעמים זה לא עושה כלום. חוסר העקביות הזה הוא הרמז הראשון שמשהו עמוק יותר קורה. הסיבה שזה כל הזמן חוזר היא שמעורבות שלוש מערכות דפדפן נפרדות, ורוב המפתחים מבינים כל אחת בפני עצמה, אבל אף פעם לא חושבים על מה קורה כששלושתן מתנגשות: הצפת יתר, ערימת הקשרים והכילת בלוקים.
ברגע שאתה מבין איך שלושתם מתקשרים, מצבי הכישלון מפסיקים להרגיש אקראיים. למעשה, הם הופכים להיות צפויים. שלושת הדברים שבעצם גורמים לזה בואו נסתכל על כל אחד מהפריטים הללו בפירוט. בעיית ההצפה כאשר אתה מגדיר overflow: hidden, overflow: scroll, או overflow: auto על אלמנט, הדפדפן יקצץ כל דבר שמתפרש מעבר לגבולותיו, כולל צאצאים ממוקמים לחלוטין. .scroll-container { overflow: אוטומטי; גובה: 300 פיקסלים; /* זה יקטע את התפריט הנפתח, נקודה */ }
.dropdown { מיקום: מוחלט; /* לא משנה -- עדיין נקטע על ידי .scroll-container */ }
זה הפתיע אותי בפעם הראשונה שנתקלתי בזה. תפסתי עמדה: אבסולוט יאפשר לאלמנט להימלט מהגזירה של מיכל. זה לא. בפועל, זה אומר שכל אב קדמון שיש לו ערך גלישה לא גלוי יכול להינתק, גם אם האב הקדמון הזה אינו הבלוק המכיל את התפריט. גזירה ומיצוב הן מערכות נפרדות. הם פשוט מתנגשים בדרכים שנראות אקראיות לחלוטין עד שאתה מבין את שניהם.
הנה דוגמה של React באמצעות createPortal:
ייבוא { createPortal } מ-'react-dom'; לייבא { useState, useEffect, useRef } מ-'react';
function Dropdown({ anchorRef, isOpen, ילדים }) { const [position, setPosition] = useState({ top: 0, left: 0 });
useEffect(() => { if (isOpen && anchorRef.current) { const rect = anchorRef.current.getBoundingClientRect(); setPosition({ למעלה: rect.bottom + window.scrollY, שמאל: rect.left + window.scrollX, }); } }, [isOpen, anchorRef]);
if (!isOpen) החזר null;
החזר ליצורפורטל(
וכמובן, אנחנו לא יכולים להתעלם מנגישות. רכיבים קבועים המופיעים מעל תוכן עדיין חייבים להיות נגישים למקלדת. אם סדר המיקוד לא עובר באופן טבעי לתפריט הנפתח הקבוע, תצטרך לנהל אותו באמצעות קוד. כדאי גם לבדוק שהוא לא יושב על תוכן אינטראקטיבי אחר בלי שום דרך לבטל אותו. זה נושך אותך בבדיקת מקלדת. מיקום עוגן CSS: לאן אני חושב שזה הולך מיקום עוגן CSS הוא הכיוון שאני הכי מתעניין בו כרגע. לא הייתי בטוח כמה מהמפרט באמת ניתן לשימוש כשהסתכלתי עליו לראשונה. זה מאפשר לך להכריז על הקשר בין תפריט נפתח לטריגר שלה ישירות ב-CSS, והדפדפן מטפל בקואורדינטות. .trigger { anchor-name: --my-trigger; }
תפריט נפתח { מיקום: מוחלט; עמדה-עוגן: ---my-trigger; top: anchor(bottom); משמאל: עוגן (שמאל); עמדה-נסה-fallbacks: flip-block, flip-inline; }
מאפיין ה-position-try-fallbacks הוא מה שהופך את זה לכדאי להשתמש על פני חישוב ידני. הדפדפן מנסה מיקומים חלופיים לפני שהוא מוותר, כך שתפריט נפתח בתחתית נקודת התצוגה מתהפך אוטומטית כלפי מעלה במקום להיקטע. תמיכת הדפדפן יציבה בדפדפנים מבוססי Chromium וגדלה בספארי. פיירפוקס צריך polyfill. החבילה @oddbird/css-anchor-positioning מכסה את מפרט הליבה. נתקלתי בו במקרי קצה של פריסה שדרשו נפילות שלא ציפיתי, אז התייחסו אליו כעל שיפור מתקדם או שידכו אותו עםנפילה של JavaScript עבור Firefox. בקיצור, מבטיח אבל עדיין לא אוניברסלי. בדוק בדפדפני היעד שלך. ובכל הנוגע לנגישות, הכרזה על קשר ויזואלי ב-CSS לא אומר לעץ הנגישות כלום. aria-controls, aria-expanded, aria-haspopup - החלק הזה עדיין עליך. לפעמים התיקון הוא רק הזזת האלמנט לפני שאני מגיע לפורטל או עושה חישובי קואורדינטות, אני תמיד שואל שאלה אחת קודם: האם התפריט הנפתח הזה באמת צריך לחיות בתוך מיכל הגלילה? אם לא, העברת הסימון למעטפת ברמה גבוהה יותר מבטלת את הבעיה לחלוטין, ללא JavaScript וללא חישובי קואורדינטות. זה לא תמיד אפשרי. אם הכפתור והתפריט הנפתח מובלעים באותו רכיב, העברת אחד ללא השני פירושו לחשוב מחדש על כל ה-API. אבל כשאתה יכול לעשות את זה, אין מה לנפות באגים. הבעיה פשוט לא קיימת. מה CSS מודרני עדיין לא פותר CSS עבר כאן דרך ארוכה, אבל עדיין יש מקומות שהוא מאכזב אותך. העמדה: בעיות תיקונים ושינויים עדיין שם. זה נמצא במפרט בכוונה, מה שאומר שאין פתרון ל-CSS. אם אתה משתמש בספריית אנימציה שעוטפת את הפריסה שלך באלמנט שעבר שינוי, חזרת להזדקק לפורטלים או למיצוב עוגן. מיקום עוגן ב-CSS מבטיח, אבל חדש. כפי שצוין קודם לכן, פיירפוקס עדיין זקוק ל-polyfill בזמן שאני כותב את זה. הגעתי איתו למקרי קצה של פריסה שדרשו נפילות שלא ציפיתי. אם אתה זקוק להתנהגות עקבית בכל הדפדפנים כיום, אתה עדיין מחפש JavaScript עבור החלקים המסובכים. התוספת שעבורה שיניתי את זרימת העבודה שלי היא ה-HTML Popover API, זמין כעת בכל הדפדפנים המודרניים. אלמנטים עם תכונת popover מוצגים בשכבה העליונה של הדפדפן, מעל הכל, ללא צורך במיקום JavaScript.
טיפול בבריחה, סמנטיקה של ביטול בלחיצה בחוץ וסמנטיקה מוצקה של נגישות מגיעות בחינם לדברים כמו עצות כלים, ווידג'טים של חשיפה ושכבות-על פשוטות. זה הכלי הראשון שאני מגיע אליו כרגע. עם זאת, זה לא פותר מיצוב. זה פותר שכבות. אתה עדיין צריך מיקום עוגן או JavaScript כדי ליישר חלון קופץ לטריגר שלו. ה-API של Popover מטפל בשכבות. מיקום עוגן מטפל במיקום. בשימוש יחד, הם מכסים את רוב מה שקודם לכן הגעתם לספרייה לעשות. מדריך החלטות למצבך אחרי שעברתי את כל זה בדרך הקשה, הנה איך אני חושב על הבחירה עכשיו.
השתמש בפורטל. הייתי משתמש בזה כשהטריגר נמצא עמוק בתוך מיכלי גלילה מקוננים. השתמשתי בדפוס זה עבור תפריטי פעולה בטבלה ושידכתי אותו עם שחזור מיקוד ובדיקות נגישות. זוהי האפשרות האמינה ביותר, אך תקציב זמן עבור החיווט הנוסף. השתמש במיקום קבוע. זה מיועד כאשר אתה נמצא ב-JavaScript של וניל או במסגרת קלת משקל ויכול לוודא שאף אב קדמון לא מחיל טרנספורמציות או מסננים. זה פשוט להגדרה ופשוט לנפות באגים, כל עוד המגבלה האחת מתקיימת. השתמש ב-CSS Anchor Positioning. פנה לשם כאשר תמיכת הדפדפן שלך מאפשרת זאת. אם נדרשת תמיכה ב-Firefox, התאם אותו עם ה-@oddbird polyfill. זה המקום אליו הפלטפורמה פונה בסופו של דבר ובסופו של דבר תהפוך לגישה המומלצת שלך. מבנה מחדש את ה-DOM. השתמש בזה כשהארכיטקטורה מאפשרת זאת, ואתה רוצה אפס מורכבות זמן ריצה. אני מאמין שזו האפשרות המוערכת ביותר. שלב דפוסים. עשה זאת כאשר אתה רוצה מיקום עוגן כגישה העיקרית שלך, בשילוב עם JavaScript fallback עבור דפדפנים שאינם נתמכים. או פורטל למיקום DOM בשילוב עם getBoundingClientRect() לדיוק קואורדינטות.
מסקנה נהגתי להתייחס לבאג הזה כבעיה חד פעמית - משהו שאפשר לתקן ולהמשיך ממנו. אבל ברגע שישבתי עם זה מספיק זמן כדי להבין את כל שלוש המערכות המעורבות - גזירה על גדותיה, הערמת הקשרים והכילת בלוקים - זה הפסיק להרגיש אקראי. יכולתי להסתכל על תפריט נפתח ומיד לאתר איזה אב קדמון אחראי. השינוי הזה באיך שקראתי את ה-DOM היה הטייק-אווי האמיתי. אין תשובה אחת נכונה. מה שהגעתי אליו היה תלוי במה שיכולתי לשלוט בבסיס הקוד: פורטלים כאשר עץ האב היה בלתי צפוי; מיקום קבוע כשהיה נקי ופשוט; הזזת האלמנט כששום דבר לא עצר אותי; ומיקום עוגן עכשיו,איפה שאני יכול. לא משנה מה תבחר בסופו של דבר, אל תתייחס לנגישות כשלב האחרון. מניסיוני, זה בדיוק הזמן שבו מדלגים עליו. מערכות היחסים של ARIA, ניהול הפוקוס, התנהגות המקלדת - אלה לא ליטושים. הם חלק ממה שגורם לדבר לעבוד בפועל. בדוק את קוד המקור המלא במאגר GitHub שלי. קריאה נוספת אלו ההפניות שאליהם חזרתי כל הזמן תוך כדי עבודה על זה:
הקשר הערימה (MDN) "מדריך מיקום עוגן CSS", חואן דייגו רודריגז "תחילת העבודה עם ה-API של Popover", Godstime Aburu ממשק משתמש צף (floating-ui.com) CSS Overflow (MDN)