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

תאריך פרסום: 22 בספטמבר 2025

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

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

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

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

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

מעברי תצוגה בתצוגת עץ היא תוספת למעברי תצוגה שמאפשרת להוסיף רכיבי פסאודו מסוג ::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> לא עושה כלום.

הבעיה היא הדרך שבה מעברי התצוגה יוצרים ומעבדים את עץ ה-pseudo שלהם:

  • בעץ המדומה, כברירת מחדל, כל קובצי ה-snapshot הם אחים זה של זה.
  • עץ ה-pseudo מוצג ברכיב pseudo מסוג ::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), רכיבי ה-pseudo של ::view-transition-group(.text) צריכים להיות צאצאים של ::view-transition-group(card). כדי לעשות את זה, משתמשים ב-view-transition-group, שמאפשר להקצות "קבוצת הורה" לרכיב מדומה (pseudo-element) מסוג ::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;
}

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

html
  ├─ ::view-transition
  │  ├─ ::view-transition-group(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-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)
  ├─ head
  └─ body
        └─ …

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

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

התוצאה היא:

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

הקלטת הדגמה

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

הפסאודו ::view-transition-group-children מופיע רק כשמשתמשים בקבוצות בת. הגודל שלו מותאם ל-border-box של הרכיב המקורי, והוא מקבל גבול שקוף עם אותה צורה ועובי גבול כמו הרכיב שיצר את הרכיב המדומה – 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 כשיוצרים את הרכיבים המדומים. האפשרות הזו פותחת מגוון אפקטים שלא היו אפשריים בעבר עם מעברי תצוגה, שחלקם מתוארים כאן.

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