תמיד ידעתי ש-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 עובד מחוץ ל-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
transform
,perspective
, orfilter
property set to something other thannone
(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 ולצל שלו להעלם אל מתחת לשכבה אחרת. ככה זה נראה:
פוסט שכתבתי מגיע לעזרה
בדקתי את שאר הקומפוננטות שלנו שמשתמשות ב-elevation כדי להבין למה שם לא קרתה התקלה שעלתה ב-card, וגיליתי שלכולם מוגדר z-index
משלהם ולכן לא הייתה שם בעיה דומה.
אחד הפוסטים האחרונים שכתבתי היה המדריך ה(לא)שלם ל: z-index, ובו הצעתי פתרון מצב של ״מלחמות z-index
" באמצעות המאפיין: isolation
.
isolation
בעצם עושה מעין scoping לאלמנטים ומשאיר את הz-index
בתוך האבא שלו בלי להשפיע על שאר האלמנטים בעמוד.
::slotted(*) {
position: relative;
isolation: isolate;
...
}
הוספתי שורה של קוד ופתרתי את התקלה 🙂
אני מכירה, כותבת ואוהבת css כבר מעל 10 שנים. אני מחשיבה את עצמי לאחת ששולטת לא רע ברוב המאפיינים, לפחות אלה המשמעותיים. ועדיין אני מצליחה להיות מופתעת מהתנהגויות שלא הכרתי, אפילו כאלה של מאפיינים ותיקים שתמיד היו פה, וההתנהגות הזאת היא לא באג אלא דווקא פיצ׳ר 🙂
אחלה פוסט רחל, תודה רבה
כל המקרי קצה האלה של css פשוט מרתקים כי הם מחילת ארנב שמובילה להיכרות מאוד עמוקה עם css
ושאלה:
איך קורה תהליך הכתיבה שלך באתר?
את כותבת בזמנך הפנוי? מנסה להקדיש שעה ביום לכתיבה? כשיש רעיון פשוט כותבת? עורכת?
ממש מסקרן אותי כי אני גם רוצה מאוד להתחיל אתר דומה
תודה רבה, שמחתי שנהנית מהפוסט 🙂
לגבי הכתיבה: כן אני כותבת בעיקר בזמן הפנוי שלי, אני כותבת בעיקר כשיש לי רעיון (בדר״כ הוא מגיע מדברים שעולים בעבודה).
הלוואי והייתי כותבת שעה ביום, לצערי אני כותבת במרווחים הרבה יותר גדולים.
התחלתי את הכתיבה בתור סיכומים לפרויקטים ודברים שלמדתי, וכך הבלוג הלך וגדל.
מאחלת לך בהצלחה.