מה קרה ל position: fixed שלי?

משך זמן קריאה: 4 דקות

תמיד ידעתי ש-position: fixed אומר שהאלמנט ממוקם ביחס ל-view-port ולא ביחס לאלמנט הישיר מעליו כמו position: absolute למשל, אבל מתברר שלא תמיד זה נכון.
למה איך וכיצד – על הכל (אני מקווה) אני אענה בפוסט 🙂

מעשה שהיה כך היה

בערוץ הסלאק ask-vivid, שהוא ערוץ התמיכה שאנחנו מתחזקים בעבודה שלי, כתב אחד המפתחים שמשתמש ב-vivid (ה –design-system שלנו ב-Vonage) שיש לו בעיה עם tooltip בתוך card: ה-tooltip לא נפתח ביחס לעוגן (anchor) שלו. כדי לחזק את הטענה שלו שהבעיה היא ה-card, הוא הוסיף אלמנט נוסף והגדיר אותו כ-position: fixed וגם הוא לא התמקם ביחס לדפדפן, אלא ביחס לקונטיינר שהוא ה-card.
פניתי בהתחלה לראות מה קורה בקוד שלו:

See the Pen Card with tooltip – wrongly positioned by Rachel Bratt Tannenbaum (@rachelbt) on CodePen.

לפני שאמשיך, כמה מילים על ה-tooltip שלנו. אנחנו משתמשים ב-floating ui לבסיס של הקומפוננטה. Floating ui היא ספריית JavaScript שממקמת אלמנטים צפים ויוצרת עבורם אינטראקציות, והיא מגדירה את ה-tooltip כ-position:fixed, ומחשבת את המיקום שלו ב-js. אבל משהו בחישוב נדפק בדוגמת קוד שנשלחה אלינו וה-tooltip לא מוקם כמו שצריך:

ה tooltip רחוק מה anchor שלו (האייקון של סימן השאלה)

דבר ראשון בדקתי אם ה-tooltip עובד מחוץ ל-card – ואכן שם הכל התנהג כצפוי, מה שהוביל אותי לחקור מה קורה בעצם ב-card. התחלתי להסיר אלמנטים שיש בקומפוננטה עד שהגעתי והורדתי את הפילטר שמייצר את הצל והבעיה נעלמה. ואז עשיתי מה שתמיד עושים כשיודעים מה הבעיה ולא יודעים איך להתקדם – שאלתי את גוגל.

לא לקח לי הרבה זמן למצוא את התשובה:

Fixed positioning is similar to absolute positioning, with the exception that the element's containing block is the initial containing block established by the viewport, unless any ancestor has transformperspective, or filter property set to something other than none (see CSS Transforms Spec), which then causes that ancestor to take the place of the element's containing block. 

יופי, נפתרה התעלומה: position:fixed ממקם אלמנט ביחס ל-view-port, אלא אם כן על אחד מהאבות שלו חל אחד המאפיינים שמשנים את ה-containing block (האזור המלבני התוחם, המשפיע על הגודל והמיקום של האלמנט), כמו למשל filter, או transform או perspective. מאפיינים אלה גורמים לאלמנט לשנות את ה-containing block. זה מסביר את ההתנהגות של ה-tooltip, שעל אף שהוא מוגדר כ –position:fixed, הוא מתמקם ביחס לאלמנט ששינה את ה-containing block (ה- card), ולא ביחס ל view-port.
ההתנהגות הזאת בעצם אמורה לקרות. זה לא באג, זו דרכה של ה-css, ולכן אנחנו פשוט ננחה את המשתמשים שלנו למקם tooltip מחוץ ל-card, ככה שבהיררכיית ה-html הוא לא יהיה בתוך האלמנט שמשנה את ה-containing block.

הפתרון הזה החזיק מעמד בערך שניה וחצי, כשעוד פנייה עלתה באותו נושא, אבל הפעם בהקשר של שימוש ב-dialog כשהוא נפתח כ-modal. ה-dialog שלנו מבוסס על אלמנט ה <dialog> ושם, אם מוגדר לו להפתח כ-showModal, הוא עולה ל-top-layer (מצרפת קישור מה זה, לא מרחיבה כי זה לגמרי פוסט בפני עצמו). במקרה הזה, למקם את ה-tooltip מחוץ ל dialog לא עוזר, כי הוא לא מגיע ל-top-layer (שאליה, נכון לעכשיו, נכנסים רק אלמנטי html דיפולטיים).

אז מה עושים?

חזרתי לחשוב, לנסות למצוא פתרון ולנבור ברשת למצוא מה עשו אחרים, וברור שלא הייתי הראשונה שחיפשה פתרון 🙂

העברתי את ההגדרות של ה-filter וה-transition לפסודו אלמנט before, שהוא אלמנט שלא מכיל עוד אלמנטים, וככה אני יכולה לשמור על העיצוב, ולא לפגוע בהתנהגות של אלמנטים פנימיים.

וכולם היו מרוצים.

או שאולי לא ממש…

התיקון שלי יצר בעיה חדשה. לא מיד עלינו עליה, במיוחד לאור העובדה שב-ui-tests שלנו, אין עוד אלמנטים בעמוד של הבדיקה מלבד הקומפוננטה שאותה אנחנו בודקים.

מה הייתה הבעיה?

זוכרים את הפילטר שגרם לבעיה? אז הוא היה חלק מקומפוננטת עיצוב (elevation) שאנחנו משתמשים לייצר צל. בעצם, אין שם אלמנטים של html, אלא אנחנו מחילים את העיצוב על מה שהוא בתוך ה-slot של הקומפוננטה. (אם משהו לא היה מובן, סימן שאתם פחות בהיכרות עם web-components. תוכלו לקרוא עליהן בפוסט שכתבתי).

כדי להסביר נתחיל עם הקוד שכתבתי:

::slotted(*) {
 position: relative;

 &::before {
  position: absolute;
  z-index: -1;
  background: var(elevation.$vvd-elevation-fill);
  block-size: 100%;
  border-radius: inherit;
  content: "";
  filter: var(elevation.$vvd-elevation-shadow);
  inline-size: 100%;
  inset-block-start: 0;
  inset-inline-start: 0;
  transition: background-color 0.15s linear, filter 0.15s linear;
 }
}

ה-z-index שנתתי ל-before גורם לו ״לרדת״ שכבה ובכך לגרום לצבע הרקע של ה-card ולצל שלו להעלם אל מתחת לשכבה אחרת. ככה זה נראה:

כך
כך נראה card בלי הצל שלו

פוסט שכתבתי מגיע לעזרה

בדקתי את שאר הקומפוננטות שלנו שמשתמשות ב-elevation כדי להבין למה שם לא קרתה התקלה שעלתה ב-card, וגיליתי שלכולם מוגדר z-index משלהם ולכן לא הייתה שם בעיה דומה.

אחד הפוסטים האחרונים שכתבתי היה המדריך ה(לא)שלם ל: z-index, ובו הצעתי פתרון מצב של ״מלחמות z-index" באמצעות המאפיין: isolation.

isolation בעצם עושה מעין scoping לאלמנטים ומשאיר את ה z-index בתוך האבא שלו בלי להשפיע על שאר האלמנטים בעמוד.

::slotted(*) {  
 position: relative;
 isolation: isolate;
 ...
}

הוספתי שורה של קוד ופתרתי את התקלה 🙂

card עם עיצוב שלם ו-tooltip במיקום מושלם

אני מכירה, כותבת ואוהבת css כבר מעל 10 שנים. אני מחשיבה את עצמי לאחת ששולטת לא רע ברוב המאפיינים, לפחות אלה המשמעותיים. ועדיין אני מצליחה להיות מופתעת מהתנהגויות שלא הכרתי, אפילו כאלה של מאפיינים ותיקים שתמיד היו פה, וההתנהגות הזאת היא לא באג אלא דווקא פיצ׳ר 🙂

  2 תגובות ל “מה קרה ל position: fixed שלי?

  1. אחלה פוסט רחל, תודה רבה
    כל המקרי קצה האלה של css פשוט מרתקים כי הם מחילת ארנב שמובילה להיכרות מאוד עמוקה עם css

    ושאלה:
    איך קורה תהליך הכתיבה שלך באתר?
    את כותבת בזמנך הפנוי? מנסה להקדיש שעה ביום לכתיבה? כשיש רעיון פשוט כותבת? עורכת?
    ממש מסקרן אותי כי אני גם רוצה מאוד להתחיל אתר דומה

    1. תודה רבה, שמחתי שנהנית מהפוסט 🙂
      לגבי הכתיבה: כן אני כותבת בעיקר בזמן הפנוי שלי, אני כותבת בעיקר כשיש לי רעיון (בדר״כ הוא מגיע מדברים שעולים בעבודה).
      הלוואי והייתי כותבת שעה ביום, לצערי אני כותבת במרווחים הרבה יותר גדולים.
      התחלתי את הכתיבה בתור סיכומים לפרויקטים ודברים שלמדתי, וכך הבלוג הלך וגדל.
      מאחלת לך בהצלחה.

כתיבת תגובה

האימייל לא יוצג באתר.