שימוש בקבוצות מקוננות של מעברי תצוגה כדי למנוע בעיות חיתוך (ועוד) במעברי תצוגה

פורסם: 22 בספטמבר 2025

כשמתחילים מעבר בין תצוגות, הדפדפן מצלם באופן אוטומטי תמונות לפני ואחרי של רכיבים שתויגו באמצעות view-transition-name. התמונות האלה מעובדות בעץ של פסאודו-אלמנטים. כברירת מחדל, העץ שנוצר הוא שטוח. המשמעות היא שההיררכיה המקורית ב-DOM אובדת, וכל קבוצות המעברים בין התצוגות שנתפסו הן אחיות תחת פסאודו-אלמנט יחיד ::view-transition.

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

  • חיתוך (overflow, clip-path, border-radius): חיתוך משפיע על רכיבי הצאצא של האלמנט, כלומר רכיבים באותה רמה בקבוצת מעבר התצוגה לא יכולים לחתוך אחד את השני.
  • opacity, mask-image ו-filter: באופן דומה, האפקטים האלה מיועדים לפעול על תמונה שעברה רסטריזציה מלאה של עץ, ולהשפיע על רכיבי הצאצא, ולא על כל פריט בנפרד.
  • טרנספורמציות בתלת-ממד (transform-style, transform, perspective): כדי להציג את כל מגוון האנימציות של טרנספורמציות בתלת-ממד, צריך לשמור על היררכיה מסוימת.

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

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

Nested view transition groups הוא תוסף למעברים בין תצוגות שמאפשר להטמיע ::view-transition-group פסאודו-אלמנטים אחד בתוך השני. כשמקננים קבוצות של מעברי תצוגה, אפשר לשחזר אפקטים כמו חיתוך במהלך המעבר.

Browser Support

  • Chrome: 140.
  • Edge: not supported.
  • Firefox: not supported.
  • Safari: not supported.

מפסאודו-עץ שטוח לפסאודו-עץ מקונן

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

הדגמה בשידור חי

הקלטת הדגמה

הקלטת הדגמה (הילוך איטי)

אם תתבוננו בהדגמה מקרוב, תראו שיש בעיה במעבר: למרות שהפסקאות עם התיאור הן צאצאים של רכיב <dialog> ב-DOM, הטקסט לא נחתך על ידי התיבה של <dialog> במהלך המעבר:

<dialog id="info_bramus" closedby="any">
  <h2><img alt="…" class="avatar" height="96" width="96" src="avatar_bramus.jpg"> <span>Bramus</span></h2>
  <p>Bramus is …</p>
  <p>…</p>
</dialog>

גם לחיצה על overflow: clip ב-<dialog> לא תעשה כלום.

הבעיה היא האופן שבו מעברי תצוגה יוצרים ומעבדים את עץ הפסאודו שלהם:

  • כברירת מחדל, כל התמונות בגיליון המדומה הן באותה רמה.
  • העץ המדומה מעובד ב::view-transition פסאודו-אלמנט שמעובד מעל המסמך כולו.

בדמו הזה, עץ ה-DOM נראה כך:

html
  ├─ ::view-transition
  │  ├─ ::view-transition-group(card)
  │  │  └─ ::view-transition-image-pair(card)
  │  │     ├─ ::view-transition-old(card)
  │  │     └─ ::view-transition-new(card)
  │  ├─ ::view-transition-group(name)
  │  │  └─ ::view-transition-image-pair(name)
  │  │     ├─ ::view-transition-old(name)
  │  │     └─ ::view-transition-new(name)
  │  ├─ ::view-transition-group(avatar)
  │  │  └─ ::view-transition-image-pair(avatar)
  │  │     ├─ ::view-transition-old(avatar)
  │  │     └─ ::view-transition-new(avatar)
  │  ├─ ::view-transition-group(paragraph1.text)
  │  │  └─ ::view-transition-image-pair(paragraph1.text)
  │  │     └─ ::view-transition-new(paragraph1.text)
  │  └─ ::view-transition-group(paragraph2.text)
  │     └─ ::view-transition-image-pair(paragraph2.text)
  │        └─ ::view-transition-new(paragraph2.text)
  ├─ head
  └─ body
        └─ …

מכיוון שהפסאודו-אלמנטים ::view-transition-group(.text) הם אחים של פסאודו-אלמנט ::view-transition-group(card), הם מוצגים מעל הכרטיס.

כדי ליצור קליפ ::view-transition-group(card) ::view-transition-group(.text), רכיבי ה-::view-transition-group(.text) צריכים להיות צאצאים של ::view-transition-group(card). לשם כך, משתמשים ב-view-transition-group שמאפשר להקצות 'קבוצת הורה' ל-::view-transition-group() פסאודו-אלמנט שנוצר.

כדי לשנות את קבוצת האב, יש שתי אפשרויות:

  • בפריט ההורה, מגדירים את view-transition-group ל-contain כדי שייכללו בו כל פריטי הצאצא עם view-transition-name.
  • בכל חשבונות הצאצא, מגדירים את הערך של view-transition-group לערך של view-transition-name בחשבון ההורה. אפשר גם להשתמש ב-nearest כדי לטרגט את קבוצת האב הקרובה ביותר.

לכן, כדי להשתמש בקבוצות מקוננות של מעברים בין תצוגות בהדגמה הזו, הקוד הופך להיות:

button.clicked,
dialog {
  view-transition-group: contain;
}

או

button.clicked,
dialog *,
  view-transition-group: nearest;
}

אחרי שמוסיפים את הקוד הזה, פסאודו ::view-transition-group(.text) מוטמע בתוך פסאודו ::view-transition-group(card). הפעולה הזו מתבצעת באמצעות פסאודו נוסף ::view-transition-group-children(…), ששומר את כל הפסאודו המקוננים יחד:

html
  ├─ ::view-transition
  │  ├─ ::view-transition-group(card)
  │  │  ├─ ::view-transition-image-pair(card)
  │  │  │  ├─ ::view-transition-old(card)
  │  │  │  └─ ::view-transition-new(card)
  │  │  └─::view-transition-group-children(card)
  │  │    ├─ ::view-transition-group(paragraph1.text)
  │  │    │  └─ ::view-transition-image-pair(paragraph1.text)
  │  │    │     └─ ::view-transition-new(paragraph1.text)
  │  │    └─ ::view-transition-group(paragraph2.text)
  │  │       └─ ::view-transition-image-pair(paragraph2.text)
  │  │          └─ ::view-transition-new(paragraph2.text)
  │  ├─ ::view-transition-group(name)
  │  │  └─ ::view-transition-image-pair(name)
  │  │     ├─ ::view-transition-old(name)
  │  │     └─ ::view-transition-new(name)
  │  └─ ::view-transition-group(avatar)
  │     └─ ::view-transition-image-pair(avatar)
  │        ├─ ::view-transition-old(avatar)
  │        └─ ::view-transition-new(avatar)
  ├─ head
  └─ body
        └─ …

לבסוף, כדי ש-::view-transition-group(card) יחתוך את הפסקאות, מחילים את overflow: clip על פסאודו ::view-transition-group-children(card):

::view-transition-group-children(card) {
  overflow: clip;
}

התוצאה היא:

הדגמה בשידור חי

הקלטת הדגמה

הקלטת הדגמה (הילוך איטי)

הערך ::view-transition-group-children pseudo מופיע רק כשמשתמשים בקבוצות מקוננות. הגודל שלו מוגדר לפי תיבת הגבול של הרכיב המקורי, והוא מקבל גבול שקוף עם אותה צורה ועובי גבול כמו הרכיב שיצר את הרכיב הווירטואלי – card בדוגמה הקודמת.

גזירה ועוד

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

html:active-view-transition-type(open) {
    &::view-transition-old(card) {
        animation-name: rotate-out;
    }
    &::view-transition-new(card) {
        animation-name: rotate-in;
    }
}
html:active-view-transition-type(close) {
    &::view-transition-old(card) {
        animation-name: rotate-in;
    }
    &::view-transition-new(card) {
        animation-name: rotate-out;
    }
}

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

הדגמה בשידור חי

הקלטת הדגמה

הקלטת הדגמה (הילוך איטי)

אם מכניסים את הפסאודו של האווטאר והשם בתוך הכרטיס, אפשר לשחזר את אפקט התלת-ממד. אבל זה לא הדבר היחיד שצריך לעשות. בנוסף לסיבוב של פסאודו ::view-transition-old(card) ופסאודו ::view-transition-new(card), צריך גם לסובב את פסאודו ::view-transition-group-children(card).

html:active-view-transition-type(open) {
    &::view-transition-group-children(card) {
        animation: rotate-in var(--duration) ease;
        backface-visibility: hidden;
    }
}
html:active-view-transition-type(close) {
    &::view-transition-group-children(card) {
        animation: rotate-out var(--duration) ease;
        backface-visibility: hidden;
    }
}

הדגמה בשידור חי

הקלטת הדגמה

הקלטת הדגמה (הילוך איטי)

הדגמות נוספות

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

הדגמה בשידור חי

הקלטת הדגמה

הדבר המעניין בהדגמה הזו הוא שכל פסאודו-האלמנטים ::view-transition-group(.card) מוטמעים בתוך פסאודו-האלמנט האב ::view-transition-group(cards) ונחתכים על ידו. האנימציה #targeted-card לא נכללת כי היא לא אמורה להיחתך על ידי ::view-transition-group(cards) כשהיא נכנסת או יוצאת.

/* The .cards wrapper contains all children */
.cards {
  view-transition-name: cards;
  view-transition-group: contain;
}

/* Contents that bleed out get clipped */
&::view-transition-group-children(cards) {
  overflow: clip;
}

/* Each card is given a v-t-name and v-t-class */
.card {
  view-transition-name: match-element;
  view-transition-class: card;
}

/* The targeted card is given a unique name (to style the pseudo differently)
   and shouldn't be contained by the ::view-transition-group-children(cards) pseudo */
#targeted-card {
  view-transition-name: targeted-card;
  view-transition-group: none;
}

Recap

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

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