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

תאריך פרסום: 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: 140.
  • 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 מופיע רק כשמשתמשים בקבוצות מקוננות. הגודל שלו מוגדר לפי תיבת הגבול של הרכיב המקורי, והוא מקבל גבול שקוף עם אותה צורה ועובי גבול כמו הרכיב שיצר את רכיב ה-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 כשיוצרים את רכיבי הפסאודו. השינוי הזה מאפשר להשתמש במגוון אפקטים שלא היו זמינים קודם במעברי תצוגה, וחלק מהם מתוארים כאן.

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