scroll-state()‎ ב-CSS

כמו שאילתות בקונטיינר, אבל לשאילתות תקועות, שאילתות עם 'צירוף' ושאילתות עם 'חריגה ממלאי'.

תאריך פרסום: 15 בינואר 2025

ב-Chrome 133 אנחנו משיקים שאילתות קונטיינר של מצב גלילה, שמבוססות על שאילתות קונטיינר. עכשיו אפשר לשלוח שאילתות לגבי המצב המנוהל בדפדפן של מיקום דביק, נקודות הצמדה לגלילה ורכיבים שניתנים לגלילה, ולהתאים אותו מ-CSS.

סקירה כללית

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

לפניכם סקירה כללית על שאילתות המצב הזמינות מ-Chrome 133:

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

החדשות הטובות הן שכל מה שלמדתם משאילתות בקונטיינרים יעזור לכם לעבוד עם שאילתות של מצב גלילה.

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

(שמאל) אנימציה שהופעל על ידי scroll-state(),‏ (ימין) אנימציה שמבוססת על גלילה
https://codepen.io/web-dot-dev/pen/emOrBaV

השאילתה הראשונה לגבי מצב גלילה

השלב הראשון הוא להגדיר את המאגר, באמצעות ערך חדש למאפיין container-type. בדומה לשאילתה בקונטיינר, הרכיב שרוצים לשלוח עליו שאילתה הוא זה שנותנים לו את הערך container-type, ואם רוצים, גם את הערך container-name. בשאילתות של מצב גלילה, נותנים לרכיב שמצמיד, תקוע או שיש בו זליגת נתונים את הערך container-type: scroll-state.

.stuck-top {
  container-type: scroll-state;
  position: sticky;
  top: 0px;
}

השלב השני הוא לבחור את הצאצא של הקונטיינר הזה שישיב למצב. בדומה לשאילתות של קונטיינרים, זה לא יכול להיות אותו רכיב שמכיל את container-type.

.stuck-top {
  container-type: scroll-state;
  position: sticky;
  top: 0px;

  > nav {
    @container scroll-state(stuck: top) {
      background: Highlight;
      color: HighlightText;
    }
  }
}

השלב השלישי הוא לנסות את הכלי. בדוגמה הבאה ל-CSS, הרקע יהיה אדום כשהאלמנט .stuck-top ידבק בחלק העליון ב-0. בעזרת כמה שורות נוספות בקוד ה-CSS שכבר היינו כותבים, ורכיב מכיל נוסף שמציג את מצב הדפדפן, הרכיבים שלנו מבינים טוב יותר את הסביבה שלהם.

https://codepen.io/web-dot-dev/pen/ByBxpwR

שיפור הדרגתי

בעזרת כלל at-rule‏ @supports והעיצוב בתצוגת עץ, אפשר להוסיף שיפורים הדרגתיים או שימוש בתכונות מותנות בכמה שורות קוד בלבד:

.stuck-top {
  container-type: scroll-state;
  position: sticky;
  top: 0px;

  @supports (container-type: scroll-state) {
    > nav {
      @container scroll-state(stuck: top) {
        background: Highlight;
        color: HighlightText;
      }
    }
  }
}

בנוסף, אם אתם משתמשים באנימציה של רכיבים בדף באמצעות שאילתות של מצב גלילה, חשוב לזכור להשתמש ב-@media (prefers-reduced-motion: no-preference) {} סביב התנועה.

תרחישים לדוגמה

ללא שינוי

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

@container scroll-state(stuck: top) {}
@container scroll-state(stuck: bottom) {}

רשימת תחביר מלאה

הוספת צל כאשר נתקעים

אחד מתרחישי השימוש הנפוצים ביותר לשאילתה תקועה הוא בסרגלי ניווט שרוצים להוסיף box-shadow כשהם תקועים, כדי שהם יוכלו להיראות כאילו הם צפים מעל התוכן שהם מכסים.

https://codepen.io/web-dot-dev/pen/GgKdryj
.stuck-top {
  container-type: scroll-state;
  position: sticky;
  top: 0px;

  > nav {
    transition: box-shadow .3s ease;

    @container scroll-state(stuck: top) {
      box-shadow: var(--shadow-5);
    }
  }
}

הפעלת הכותרת הנוכחית שנתקעה

תרחיש נפוץ נוסף של משוב על ממשק משתמש דביק הוא הדגשת הרכיב הנדבק. ברשימה של להקות שממוינות לפי סדר אלפביתי, האפשרות הזו יכולה להיות מאוד מועילה ותומכת בחוויית השימוש.

https://codepen.io/web-dot-dev/pen/pvzVRaK
.sticky-slide {
  dt {
    container-type: scroll-state;
    position: sticky;
    inset-block-start: 0;
    inset-inline: 0;

    > header {
      transition: 
        background .3s ease,
        box-shadow .5s ease;

      @container scroll-state(stuck: top) {
        background: hsl(265 100% 27%);
        box-shadow: 0 5px 5px #0003;
      }
    }
  }
}

זוהי וריאציה נוספת, שבה הכותרות מופיעות לצד הפריטים ברשימה. יש המון אפשרויות!

https://codepen.io/web-dot-dev/pen/azoGpGg

רעיונות נוספים

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

סיכום שנתי בתמונות

בעזרת שאילתות של מצב מוצמד, אנחנו יכולים להסיר חלק מהאחריות מ-JavaScript ומ-Snap Events ולהעביר את הטיפול ל-CSS.

@container scroll-state(snapped: x) {}
@container scroll-state(snapped: y) {}
@container scroll-state(snapped: inline) {}
@container scroll-state(snapped: block) {}

רשימת תחביר מלאה

תזכורת קטנה, למקרה שדילוגתם על הקטע השאילתה הראשונה של מצב גלילה, המארז של שאילתה של הצמדה הוא האלמנט עם scroll-snap-align, והאלמנט שיכול להתאים את עצמו חייב להיות צאצא של האלמנט הזה. כלומר, יש שלושה רכיבים שצריך להגדיר כדי לעשות זאת:

a scroll container with `scroll-snap-type`
⤷ a snap target with both `scroll-snap-align` and `container-type: scroll-state`
    ⤷ a child of the snap target that can query the container for snap state

שיפור המראה של הפריט שנלכד

כשמשתמשים בגלילה עם הצמדה למרכז, מקובל מאוד להדגיש או להציג את הפריט שמחובר למרכז. בדוגמה הזו של המלצות, נעשה שימוש במילות המפתח not כדי שכל ההמלצות שלא צולמו ב-Snap יקבלו רמת שקיפיות נמוכה, בעוד שההמלצות שצולמו ב-Snap יוצגו במצב התצוגה הטבעי שלהן.

https://codepen.io/web-dot-dev/pen/NPKMdBX
.demo {
  overflow: auto hidden;
  scroll-snap-type: x mandatory;

  > article {
    container-type: scroll-state;
    scroll-snap-align: center;

    @supports (container-type: scroll-state) {
      > * {
        transition: opacity .5s ease;

        @container not scroll-state(snapped: x) {
          opacity: .25;
        }
      }
    }
  }
}

הצגת הכותרת של הפריט שנצמד

זוהי דוגמה טובה לאופן שבו שאילתות לגבי מצב גלילה מאפשרות להפעיל אנימציה בתגובה לגלילה. זו גם דוגמה טובה למקרה שבו כדאי להשתמש ב-CSS עם העדפה לצמצום התנועה.

https://codepen.io/web-dot-dev/pen/XJrqpBG
.demo {
  overflow-x: auto;
  scroll-behavior-x: contain;
  scroll-snap-type: x mandatory;

  > .card {
    container-type: scroll-state;
    scroll-snap-align: center;

    @supports (container-type: scroll-state) {
      @media (prefers-reduced-motion: no-preference) {
        figcaption {
          transform: translateY(100%);

          @container scroll-state(snapped: x) {
            transform: translateY(0);
          }
        }
      }
    }
  }
}

אנימציה ברכיבי השקף

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

https://codepen.io/web-dot-dev/pen/dPbeNqY
html {
  scroll-snap-type: y mandatory;
}

section {
  container-type: scroll-state;
  scroll-snap-align: start;
  scroll-snap-stop: always;

  @supports (container-type: scroll-state) {
    @media (prefers-reduced-motion: no-preference) {
      > h1 {
        transition: opacity .5s ease, transform .5s var(--ease-spring-3);
        transition-delay: .5s;
        opacity: 0;
        transform: scale(1.25);

        @container scroll-state(snapped: block) {
          opacity: 1;
          transform: scale(1);
        }
      }
    }
  }
}

יכול להיות שתבחינו שכל השאילתות של מצב CSS שנשמרו מתנהגות כמו scrollsnapchanging, בניגוד ל-scrollsnapchange. כך תקבלו את האפשרות המוקדם ביותר לספק משוב חזותי לגבי הרכיב שנצמד. אם האירוע מתרחש מוקדם מדי, כדאי להשתמש באירוע JavaScript.

ניתן לגלילה

השאילתה לגבי מצב הגלילה תעזור מאוד להציג תכונות חזותיות שמאפשרות לדעת מתי אפשר לגלול באזור גלילה. לפני שאפשר היה להריץ שאילתות לגבי מצב הגלילה, היה קשה לדעת מה המצב.

@container scroll-state(scrollable: top) {}
@container scroll-state(scrollable: right) {}
@container scroll-state(scrollable: bottom) {}
@container scroll-state(scrollable: left) {}

רשימת תחביר מלאה

סימון גלילה באמצעות צללים

יש טריק CSS מפורסם של Lea Verou שמשתמש ב-background-attachment: local כדי להשיג אפקט דומה, וגם דרך לעשות זאת באמצעות אנימציה שמבוססת על גלילה. לכל שיטה יש יתרונות וחסרונות, ועלינו לבדוק מתי ואיפה כל אחת מהשיטות האלה מתאימה ביותר.

בדוגמה הבאה נעשה שימוש ברכיב דביק אחד שמשתרע על פני חלון הגלילה. כשהשאילתה של מצב הגלילה לפי הקשר חלה על העברה מדורגת בחלק העליון ועל העברה מדורגת בחלק התחתון, האטימות שלהן מונפשת באמצעות @property: @container scroll-state(scrollable: top).

שימו לב גם שזהו המאגר הראשון שהוא גם מאגר size וגם מאגר scroll-state.

https://codepen.io/web-dot-dev/pen/OPLZWBj
.scroll-container {
  container-type: scroll-state size;
  overflow: auto;

  &::after {
    content: " ";

    background: var(--_shadow-top), var(--_shadow-bottom);
    transition: 
      --_scroll-shadow-color-1-opacity .5s ease,
      --_scroll-shadow-color-2-opacity .5s ease;

    @container scroll-state(scrollable: top) {
      --_scroll-shadow-color-1-opacity: var(--_shadow-color-opacity, 25%);
    }

    @container scroll-state(scrollable: bottom) {
      --_scroll-shadow-color-2-opacity: var(--_shadow-color-opacity, 25%);
    }
  }
}

הנחיה עם חץ

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

https://codepen.io/web-dot-dev/pen/OPLZWBj
@container scroll-state((scrollable: top) or (not (scrollable: bottom))) {
  translate: 0 calc(100% + 10px);
}

@container scroll-state((scrollable: top) and (not (scrollable: bottom))) {
  translate: 0 calc(100% + 10px);
  rotate: .5turn;
}

חזרה לראש הדף

אינטראקציה פופולרית נוספת במצב גלילה היא הלחצן הנוח 'גלילה לראש'. הקוד הבא גורם ללחצן הגלילה לחלק העליון להיעלם כשאין לאן לגלול למעלה.

הפתרון הזה הוא קצת הפוך, אבל הוא מאפשר לצמצם את כמות ה-CSS. מיקום העצירה הטבעי של הלחצן הוא בתצוגה, לכן צריך להורות לו להסתתר כשאין יותר אפשרות לגלול למעלה.

https://codepen.io/web-dot-dev/pen/OPLZWBj
@container not scroll-state(scrollable: top) {
  translate: 0 calc(100% + 10px);
}

המשך המחקר

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