הבעיה
מעברי תצוגה בין מסמכים יוצרים אנימציה של רכיבים בין שני דפים במהלך ניווט באותו מקור. הדפדפן מצלם תמונת מצב של הדף הישן, מנווט ואז מפעיל אנימציה מתמונת המצב לדף החדש. אם הדף החדש לא סיים לטעון משאבים קריטיים – גיליונות סגנונות, סקריפטים של פריסה או רכיבי DOM מרכזיים – המעבר יציג אנימציה למצב לא שלם או לא מעוצב. זה גורם לתקלות חזותיות, כמו רכיבים שמשתנים למיקומים שגויים, תוכן שמשתנה באמצע האנימציה או גופנים חלופיים שמהבהבים לגופני אינטרנט אחרי שהמעבר מסתיים.
הפתרון
משתמשים ב-blocking="render" ברכיבים קריטיים של <link> ו-<script> ב-<head> של הדף החדש, ומשתמשים ב-<link rel="expect"> כדי לחסום את העיבוד עד לניתוח של רכיבי DOM ספציפיים. כך הדפדפן לא יתחיל את האנימציה של מעבר התצוגה עד שהמצב החזותי של הדף החדש יהיה יציב. הדפדפן ממשיך לנתח את ה-HTML ברקע – רק הרינדור נדחה.
אסטרטגיית הטמעה
- חובה: מביעים הסכמה למעברים בין תצוגות של מסמכים שונים באמצעות כלל ה-CSS
@view-transitionבשני הדפים. - חובה: ודאו שגיליונות הסגנונות הקריטיים נמצאים ב-
<head>. גיליונות סגנונות ב-<head>חוסמים עיבוד כברירת מחדל. גיליונות סגנונות שמוזרקים באופן דינמי דורשיםblocking="render"מפורש. - מומלץ להשתמש ב-
blocking="render"ברכיבי<script>שצריכים לפעול לפני שהאנימציה של המעבר מתחילה (למשל, סקריפטים שמחילים עיצוב או משפיעים על הפריסה). - מומלץ להשתמש ב-
<link rel="expect" href="#element-id" blocking="render">כדי לחסום את העיבוד עד שהתוכן בחלק העליון של דף הנחיתה ינותח. ההשהיה הזו חלה על כל סוגי המעברים: מעברים הדרגתיים בדף מלא (כדי למנוע אנימציה לדף ריק), אנימציות שינוי צורה (כדי לוודא שרכיבים עם שם קיימים ב-DOM) ופריסות שתלויות בסקריפט (כדי לוודא שתוכן מעוצב מנותח). - אל תחסמו עיבוד של תוכן לא קריטי. חוסמים רק משאבים ורכיבים שמשפיעים על אזור התצוגה הראשוני. חסימה של יותר מדי תוכן מעכבת את המעבר ופוגעת בביצועים הנתפסים.
מדריך הטמעה
שלב 1: מפעילים את Cross-Document View Transitions
חובה: גם דף המקור וגם דף היעד חייבים לכלול את הכלל @view-transition. בלי זה, לא מתרחש מעבר בין מסמכים.
/*
MANDATORY: Include this rule in every page that participates
in cross-document view transitions.
`navigation: auto` enables transitions for standard navigations
(link clicks, form submissions, back/forward).
*/
@view-transition {
navigation: auto;
}
/* MANDATORY Copy-Paste Safety: Disable cross-document view transitions for users requesting reduced motion */
@media (prefers-reduced-motion: reduce) {
@view-transition {
navigation: none;
}
}
שלב 2: חסימת העיבוד עד להפעלת הסקריפטים הקריטיים
אם סקריפט לא חוסם ב-<head> חייב לפעול לפני שהמעבר מונפש (למשל, כדי להחיל מחלקת עיצוב או להשפיע על הפריסה), צריך לסמן אותו ב-blocking="render". בלי זה, יכול להיות שהסקריפטים של async, defer או type="module" יופעלו אחרי שהמעבר כבר התחיל.
<head>
<!--
DO: Mark layout-critical scripts with blocking="render".
-->
<script type=module blocking="render">
// Example: apply a stored theme before the page renders,
// so the transition snapshot reflects the correct theme.
document.documentElement.dataset.theme =
localStorage.getItem('theme') || 'light';
</script>
</head>
שלב 3: חסימת העיבוד עד לניתוח של רכיבי DOM מרכזיים
גיליונות סגנונות וסקריפטים של blocking="render" ב-<head> מבטיחים רק שה-<head> עבר עיבוד מלא. הם לא ממתינים לניתוח של תוכן <body>. בלי חסימה נוספת, הדפדפן עשוי לצלם את הדף החדש לפני שרכיבים בחלק העליון של הדף קיימים ב-DOM, וכתוצאה מכך המעבר יציג אנימציה לדף ריק או לדף שעבר רינדור חלקי.
<link rel="expect"> פותר את הבעיה הזו על ידי חסימת העיבוד עד לניתוח של רכיב ספציפי (שמזוהה לפי ה-id שלו). הערך href חייב להיות מזהה מקטע (למשל, #hero) שתואם למאפיין id של רכיב היעד. אחרי שהתג הסוגר של הרכיב הזה מנותח, חסימת העיבוד משתחררת. חשוב לזכור שהפונקציה <link rel="expect"> רק מבטיחה שרכיב ה-DOM של היעד קיים במסמך – היא לא ממתינה להורדה או לפענוח של משאבי משנה חיצוניים (כמו פיקסלים של <img>).
מומלץ להשתמש ב-<link rel="expect"> בכל התרחישים הבאים:
תרחיש לדוגמה 1: מעבר הדרגתי בדף מלא
גם אם לאף רכיב בודד אין view-transition-name, המעבר שמוגדר כברירת מחדל root מבצע מעבר הדרגתי של כל הדף. אם תמונת המצב של הדף החדש נוצרת לפני ניתוח התוכן בחלק העליון ובעיקר, המעבר ההדרגתי יתבצע מהדף הישן לדף ריק או חלקי. חסימת העיבוד ברכיב שמסמן את סוף התוכן שגלוי בחלק העליון של הדף.
<head>
<link rel="stylesheet" href="/css/styles.css">
<!--
DO: Block rendering until the main content area is parsed,
even for a simple cross-fade. Without this, the browser may
snapshot the page before visible content exists in the DOM,
causing the cross-fade to reveal a blank or partial page.
-->
<link rel="expect" href="#main-content" blocking="render">
</head>
<body>
<header>...</header>
<main id="main-content">
<h1>Page Title</h1>
<p>Above-the-fold content the user should see immediately.</p>
</main>
<!-- Content below the fold does NOT need to be blocked on -->
<section>...</section>
</body>
תרחיש לדוגמה 2: אנימציות שינוי צורה בין רכיבים ספציפיים
כשאלמנטים בשני הדפים משתפים view-transition-name, הדפדפן משנה את הצורה שלהם בצורה חלקה במהלך הניווט. אם רכיב היעד לא נותח כשהמעבר מתחיל, הדפדפן לא יכול למצוא אותו – המורפינג משתנה לאנימציות נפרדות של יציאה וכניסה. חסימת העיבוד עד לניתוח הרכיב עם view-transition-name.
התג <link rel="expect"> רק משהה את העיבוד עד שרכיב ה-DOM של היעד מנותח, ולא ממתין להורדה או לפענוח של נתוני התמונה. לכן, יכול להיות שהתג <img> שלא נשמר במטמון יתועד כמלבן ריק בתמונת המצב של הדף החדש. כדי למנוע תמונות מצב ריקות או שינויים בפריסה במהלך שינויי צורה של תמונות, צריך לטעון מראש או לפענח מראש תמונות מעבר קריטיות (לדוגמה, באמצעות <link rel="preload" as="image"> או על ידי רינדור מראש של דף היעד) ולספק חלופות מפורשות לשינוי גודל (המאפיינים width ו-height או CSS aspect-ratio, וגם צבע רקע של פלייסהולדר).
<head>
<link rel="stylesheet" href="/css/styles.css">
<!--
DO: Preload critical transition images so their pixels are
available to decode before the new-page snapshot is captured.
<link rel="expect"> only waits for the DOM element to be parsed,
NOT for image pixels to download or decode.
-->
<link rel="preload" as="image" href="/img/product.webp" fetchpriority="high">
<!--
DO: Block rendering until the element participating in the
morph animation has been parsed. Without this, the browser
may start the transition before #hero exists, causing the
morph to degrade to a fade-out/fade-in.
-->
<link rel="expect" href="#hero" blocking="render">
<!--
When multiple blocking="render" resources are present,
rendering is blocked until ALL of them are satisfied.
Here, the browser waits for both the script to execute
AND the #hero element to be parsed — whichever comes last.
-->
<script async blocking="render" src="/js/transition-setup.js"></script>
</head>
<body>
<header>...</header>
<section id="hero">
<h1 style="view-transition-name: page-title">Product Name</h1>
<!--
DO: Provide explicit dimensions (width/height or aspect-ratio)
as a sizing fallback so the snapshot captures the correct layout
box even if image decoding is still in progress.
-->
<img
style="view-transition-name: hero-image"
src="/img/product.webp"
width="800"
height="600"
alt="Product"
>
</section>
</body>
שלב 4: שימוש בשאילתות מדיה לחסימת עיבוד רספונסיבי
בגדלים שונים של אזור התצוגה עשויות להופיע כמויות שונות של תוכן בחלק העליון של הדף. כדי לחסום את העיבוד רק לתוכן שגלוי ברוחב נתון של אזור התצוגה, צריך להשתמש במאפיין media ב-<link rel="expect">.
<head>
<!--
DO: Use media queries to conditionally block rendering.
On wide screens, both the hero and the sidebar are visible,
so block until both are parsed. On narrow screens, only the
hero is visible initially.
-->
<link
rel="expect"
href="#hero"
blocking="render"
media="screen and (width <= 768px)"
>
<link
rel="expect"
href="#sidebar"
blocking="render"
media="screen and (width > 768px)"
>
</head>
שלב 5: שימוש ב-pagereveal למעברים תלויי-הקשר (אופציונלי)
האירוע pagereveal לא נדרש לאסטרטגיית הליבה של חסימת רינדור. הוא נדרש רק כשצריך להקצות ערכים של view-transition-name באופן דינמי על סמך המקום שממנו המשתמש ניווט – לדוגמה, שינוי של פריט ספציפי ברשימה לכותרת של דף פרטים.
אם הערכים של view-transition-name מוקצים באופן סטטי ב-CSS, או אם אתם משתמשים רק בברירת המחדל של מעבר הדרגתי בדף מלא, דלגו על השלב הזה לגמרי.
<head>
<!--
MANDATORY: The pagereveal listener must be registered before
the page renders. Inline small setup scripts with blocking="render"
to avoid an extra network round-trip on cold loads, or use an
external async script with blocking="render" so the listener is
registered early without blocking HTML parsing.
If the listener is registered too late (e.g., in a deferred
script), the event may have already fired.
-->
<script async blocking="render" src="/js/transition-setup.js"></script>
</head>
// transition-setup.js
window.addEventListener('pagereveal', async (event) => {
if (!event.viewTransition) return;
const from = navigation.activation?.from;
if (!from) return;
const fromUrl = new URL(from.url);
// DO: Assign view-transition-name based on navigation context.
// This enables a morph animation from the product card on the
// list page to the heading on the detail page.
if (fromUrl.pathname === '/products/') {
const heading = document.querySelector('main h1');
if (!heading) return;
heading.style.viewTransitionName = 'product-title';
try {
await event.viewTransition.ready;
} catch (error) {
// Skipped view transitions reject `ready` with InvalidStateError.
if (error.name !== 'InvalidStateError') {
throw error;
}
} finally {
// MANDATORY: Remove the temporary name after the transition
// finishes. Stale names interfere with subsequent navigations
// and prevent the page from entering the bfcache.
await event.viewTransition.finished;
heading.style.viewTransitionName = '';
}
}
});
שיטות מומלצות
- מומלץ להקצות את
view-transition-nameבאמצעות CSS כשאפשר. שמרו את ההקצאה של JavaScript (דרךpagereveal) למקרים שבהם השם תלוי בהקשר של הניווט. - מומלץ להשתמש בסקריפטים קטנים ומהירים לחסימת רינדור. לדפדפן יש זמן קצוב מובנה (בערך 4 שניות), שאחריו המעבר נדחה לחלוטין (דחיית
viewTransition.readyעםInvalidStateErrorאם המעבר התחיל). - אל תשתמשו ב-
<link rel="expect">כדי לחסום רכיבים שנמצאים עמוק בדף ולא גלויים באזור התצוגה הראשוני. הפעולה הזו מעכבת את המעבר בלי תועלת חזותית. - אל תסמנו מטענים של ניתוח נתונים, רשת מודעות, Tag Manager או צד שלישי אחר באמצעות
blocking="render", גם אם הם מוסיפים תוכן לאזור התצוגה הראשוני. זמן האחזור שלהם ברשת נכלל בזמן הקצוב לתפוגה של המעבר, והפלט שלהם לא נכלל במצב היציב של הדף. - אל תקצו את אותו
view-transition-nameלכמה רכיבים באותו דף. אם יש שמות כפולים, המערכת מדלגת על כל המעבר. - השפעת התזמון של טכנולוגיה מסייעת: שימוש ב-
blocking="render"מעכב את העדכונים החזותיים ואת ה-Paint הראשוני. הפעולה הזו מונעת תקלות חזותיות למשתמשים רואים, אבל היא יכולה לגרום לעיכוב בעיבוד או באתחול של קוראי מסך וטכנולוגיות מסייעות אחרות שמסתמכות על עצי נגישות שעברו רינדור. חשוב לשקול את היתרונות של רציפות חזותית לעומת זמן האחזור הראשוני של הקריאה למשתמשים שאינם חזותיים, ולוודא שהסקריפטים שחוסמים את העיבוד הם מינימליים ועברו אופטימיזציה משמעותית.
שיטות חלופיות
מעברי תצוגה בין מסמכים הם מועמדים מצוינים לשיפור הדרגתי (progressive enhancement). בדפדפנים שלא תומכים בהם, המערכת מתעלמת מהכלל @view-transition ומתרחשות פעולות ניווט רגילות באותו מקור, בדיוק כמו שהן היו מתרחשות בלי התכונה. בדפדפנים שתומכים בתכונה, המעברים חלקים. בדפדפנים אחרים, הניווט רגיל. תמיכה מוגבלת בדפדפנים היא לא סיבה להימנע מהטמעה.
כל הדפדפנים שתומכים במעברי תצוגה בין מסמכים תומכים גם ב-blocking="render" וב-<link rel="expect">, כך שאין צורך בגיבוי נפרד לתכונות שחוסמות רינדור שמתוארות במדריך הזה.
שיקולים נוספים
- השפעה על הביצועים: כל משאב שחוסם רינדור מעכב את תחילת האנימציה של מעבר התצוגה. צמצמו את מספר הסקריפטים שחוסמים עיבוד, והשתמשו ב-
<link rel="expect">רק לרכיבים שנמצאים בחלק העליון של הדף. כדי למנוע לחלוטין עיכובים בטעינה, אפשר לעבד מראש את דפי היעד באמצעות Speculation Rules API. - התנהגות במקרה של זמן קצוב לתפוגה: אם הזמן המשולב של חסימת העיבוד חורג מ-4 שניות בערך, הדפדפן מדלג על המעבר (וכל הבטחה פעילה של
viewTransition.readyנדחית עםInvalidStateError). חשוב לוודא שמשאבים קריטיים נטענים בתוך החלון הזה. - תאימות ל-bfcache: הקצאות זמניות של
view-transition-nameשלא מנוקות אחרי המעבר יכולות למנוע מהדף להיכנס ל-bfcache. תמיד צריך להסיר שמות שהוקצו באופן דינמי בקריאה החוזרת (callback) שלfinished.