انتقال‌های بین‌سندی سازگار

مشکل

گذار نمای بین‌سندی عناصر را بین دو صفحه درطول پیمایش با مبدأ یکسان پویانمایی می‌کند. مرورگر عکس آنی از صفحه قدیمی می‌گیرد، پیمایش می‌کند، سپس از عکس آنی به صفحه جدید پویانمایی می‌کند. اگر بار کردن منابع مهم (برگه‌های سبک نگارش، اسکریپت‌های چیدمان، یا عناصر کلیدی DOM) در صفحه جدید تمام نشده باشد، انتقال به حالت ناقص یا بدون سبک پویانمایی می‌شود. این امر باعث بروز اشکالات بصری می‌شود، مانند تغییر شکل عناصر به موقعیت‌های اشتباه، بازچینی محتوا در اواسط پویانمایی، یا چشمک زدن قلم‌های جایگزین به قلم‌های وب پس‌از تکمیل انتقال.

راه‌حل

از blocking="render" در عناصر مهم <link> و <script> در <head> صفحه جدید استفاده کنید و از <link rel="expect"> برای مسدود کردن پردازش تا زمانی که عناصر DOM خاص تجزیه شوند استفاده کنید. این کار تضمین می‌کند که مرورگر پویانمایی انتقال نما را تا زمانی که وضعیت دیداری صفحه جدید پایدار نشده است شروع نکند. مرورگر به تجزیه HTML در پس‌زمینه ادامه می‌دهد — فقط رنگ‌آمیزی به‌تعویق می‌افتد.

راهبرد پیاده‌سازی

  1. اجباری: بااستفاده از قانون CSS at-rule @view-transition در هر دو صفحه، با انتقال‌های نمای بین‌سندی موافقت کنید.
  2. اجباری: مطمئن شوید برگه سبک نگارش‌های مهم در <head> قرار دارند. برگه‌های سبک نگارش در <head> به‌طور پیش‌فرض مسدودکننده پردازش هستند. برگه‌های سبک نگارش تزریق‌شده به‌صورت پویا به blocking="render" صریح نیاز دارند.
  3. باید از blocking="render" در عناصر <script> استفاده کنید که باید قبل‌از پویانمایی انتقال اجرا شوند (برای مثال، اسکریپت‌هایی که زمینه‌ای را اعمال می‌کنند یا بر چیدمان تأثیر می‌گذارند).
  4. باید از <link rel="expect" href="#element-id" blocking="render"> برای مسدود کردن پردازش تا زمانی که محتوای بالای خط تا تجزیه شود استفاده کنید. این امر برای همه انواع انتقال اعمال می‌شود: محو تدریجی متقاطع تمام‌صفحه (برای جلوگیری از پویانمایی به صفحه خالی)، پویانمایی‌های دگردیسی (برای اطمینان از وجود عناصر نام‌گذاری‌شده در DOM)، و چیدمان‌های وابسته به دستورگان (برای اطمینان از تجزیه محتوای سبک‌دار).
  5. پرداخت محتوای غیربحرانی را مسدود نکنید. فقط منابع و عناصری را مسدود کنید که بر نمای اولیه تأثیر می‌گذارند. مسدود کردن محتوای بیش‌ازحد باعث تأخیر در انتقال می‌شود و عملکرد درک‌شده را کاهش می‌دهد.

راهنمای پیاده‌سازی

مرحله ۱: با «انتقال‌های نمای بین‌سندی» موافقت کنید

الزامی: هر دو صفحه مبدأ و مقصد باید شامل قانون @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;
  }
}

مرحله ۲: مسدود کردن پرداز تا زمانی که اسکریپت‌های حیاتی اجرا شوند

اگر یک اسکریپت غیرمسدودکننده در <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>

مرحله ۳: مسدود کردن پردازش تا زمانی که عناصر کلیدی DOM تجزیه شوند

برگه‌های سبک نگارش و اسکریپت‌های blocking="render" در <head> فقط تضمین می‌کنند که <head> به‌طور کامل پردازش شده است. آن‌ها منتظر تجزیه شدن هیچ محتوای <body> نمی‌مانند. بدون مسدود کردن بیشتر، مرورگر ممکن است عکس آنی صفحه جدید را قبل‌از وجود عناصر بالای خط تا در DOM بگیرد — که منجر به انتقالی می‌شود که به صفحه‌ای خالی یا تاحدی رندرشده پویانمایی می‌کند.

<link rel="expect"> این مشکل را با مسدود کردن پردازش تا زمانی که عنصر خاصی (که با id آن شناسایی می‌شود) تجزیه شود حل می‌کند. مقدار href باید شناسه قطعه‌ای (مثلاً #hero) باشد که با مشخصه id عنصر هدف مطابقت داشته باشد. وقتی برچسب پایانی آن عنصر تجزیه شود، بلوک رندر آزاد می‌شود. توجه داشته باشید که <link rel="expect"> فقط تضمین می‌کند که عنصر DOM هدف در سند وجود دارد — این رویداد برای بارگیری یا رمزگشایی زیرمنابع خارجی (مانند پیکسل‌های <img>) منتظر نمی‌ماند.

باید از <link rel="expect"> در همه سناریوهای زیر استفاده کنید:

مورد استفاده ۱: محو تدریجی متقاطع تمام‌صفحه

حتی وقتی هیچ عنصر جداگانه‌ای 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>

مورد استفاده ۲: پویانمایی‌های دگردیسی بین عناصر خاص

وقتی عناصر در هر دو صفحه 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>

مرحله ۴: استفاده از پُرسمان‌های رسانه‌ای برای مسدود کردن پردازش واکنش‌گرا

اندازه‌های مختلف نمای ممکن است مقادیر مختلفی از محتوا را در بالای خط تا نشان دهد. از مشخصه 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>

مرحله ۵: استفاده از 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) اختصاص دهید. تخصیص جاوا اسکریپت را (ازطریق pagereveal) برای مواردی که نام به بافت پیمایش بستگی دارد رزرو کنید.
  • باید اسکریپت‌های مسدودکننده پردازش را کوچک و سریع نگه دارید. مرورگر دارای یک مهلت زمانی داخلی (حدود ۴ ثانیه) است که پس‌از آن انتقال به‌طور کامل نادیده گرفته می‌شود (رد کردن viewTransition.ready با InvalidStateError درصورت شروع انتقال).
  • از <link rel="expect"> برای مسدود کردن عناصر عمیق در صفحه که در نمای اولیه قابل‌مشاهده نیستند استفاده نکنید. این کار انتقال را بدون مزیت دیداری به‌تأخیر می‌اندازد.
  • نباید بارکننده‌های تجزیه‌وتحلیل، شبکه آگهی، مدیر برچسب، یا دیگر بارکننده‌های طرف سوم را با blocking="render" علامت‌گذاری کنید، حتی اگر محتوا را به ناحیه نمایش اولیه تزریق کنند. تأخیر شبکه آن‌ها در مهلت انتقال لحاظ می‌شود و خروجی آن‌ها بخشی از وضعیت پایدار صفحه نیست.
  • view-transition-name یکسان را به چند عنصر در یک صفحه اختصاص ندهید. نام‌های تکراری باعث می‌شود کل انتقال نادیده گرفته شود.
  • تأثیر زمان‌بندی فناوری کمکی: استفاده از blocking="render" به‌روزرسانی‌های دیداری و رنگ‌آمیزی اولیه را به‌تأخیر می‌اندازد. اگرچه این کار از بروز اشکالات بصری برای کاربران بینا جلوگیری می‌کند، اما می‌تواند باعث تأخیر پردازش یا مقداردهی اولیه معوق برای صفحه‌خوان‌ها و سایر فناوری‌های کمکی شود که به درخت‌های دسترس‌پذیری رندرشده وابسته هستند. مزایای تداوم بصری را درمقابل تأخیر خواندن اولیه برای کاربران غیربصری بسنجید و مطمئن شوید که اسکریپت‌های مسدودکننده پردازش حداقل و بسیار بهینه‌سازی شده‌اند.

راهبردهای جایگزین

انتقال‌های نمای بین‌سندی نامزد عالی‌ای برای ارتقای پیش‌رو هستند. در مرورگرهایی که از آن‌ها پشتیبانی نمی‌کنند، قانون @view-transition نادیده گرفته می‌شود و پیمایش‌های استاندارد با مبدأ یکسان دقیقاً همان‌طور که بدون این ویژگی انجام می‌شوند، رخ می‌دهند. مرورگرهای پشتیبانی‌کننده انتقال‌های روان را دریافت می‌کنند؛ بقیه مرورگرها پیمایش استاندارد را دریافت می‌کنند. پشتیبانی محدود مرورگر دلیلی برای اجتناب از پذیرش نیست.

همه مرورگرهایی که از انتقال نمای بین‌سندی پشتیبانی می‌کنند از blocking="render" و <link rel="expect"> نیز پشتیبانی می‌کنند، بنابراین برای ویژگی‌های مسدودکننده پردازش که در این راهنما شرح داده شده است، به بازگشت جداگانه‌ای نیاز نیست.

ملاحظات دیگر

  1. تأثیر بر عملکرد: هر منبع مسدودکننده رندر، شروع پویانمایی انتقال نما را به‌تأخیر می‌اندازد. تعداد اسکریپت‌های مسدودکننده رندر را به‌حداقل برسانید و از <link rel="expect"> فقط برای عناصری که در بالای خط تا هستند استفاده کنید. صفحه‌های مقصد را بااستفاده از Speculation Rules API پیش‌پردازش کنید تا تأخیرهای بارگیری را کاملاً ازبین ببرید.
  2. رفتار اتمام مهلت: اگر مجموع زمان مسدودسازی رندر از حدود ۴ ثانیه فراتر رود، مرورگر از انتقال صرف‌نظر می‌کند (و هر viewTransition.ready فعال با InvalidStateError رد می‌شود). مطمئن شوید منابع حیاتی به‌خوبی در این پنجره بارگیری می‌شوند.
  3. سازگاری حافظه نهان ناوبری بین صفحات: تخصیص‌های موقت view-transition-name که پس‌از انتقال پاک‌سازی نمی‌شوند می‌توانند مانع از ورود صفحه به حافظه نهان ناوبری بین صفحات شوند. همیشه نام‌های اختصاص‌یافته به‌صورت پویا را در فراخوانی برگشتی finished بردارید.