عناصر انتقال عرض متعددة المستندات للتطبيقات المتعددة الصفحات

عندما يحدث انتقال عرض بين مستندَين مختلفَين، يُطلق عليه اسم انتقال عرض بين المستندات. ويحدث ذلك عادةً في التطبيقات المتعددة الصفحات (MPA). تتوفّر انتقالات العرض بين المستندات في Chrome اعتبارًا من الإصدار 126 من Chrome.

Browser Support

  • Chrome: 126.
  • Edge: 126.
  • Firefox: not supported.
  • Safari: 18.2.

Source

تعتمد انتقالات العرض بين المستندات على اللبنات والمبادئ نفسها التي تعتمد عليها انتقالات العرض في المستند نفسه، وهذا مقصود تمامًا:

  1. يتخذ المتصفّح لقطات للعناصر التي تتضمّن view-transition-name فريدًا في كل من الصفحة القديمة والجديدة.
  2. يتم تعديل نموذج المستند (DOM) أثناء إيقاف العرض.
  3. وأخيرًا، يتم تشغيل عمليات الانتقال من خلال حركات CSS.

يختلف هذا النوع عن انتقالات العرض في المستند نفسه، إذ لا تحتاج إلى استدعاء document.startViewTransition لبدء انتقال العرض. بدلاً من ذلك، يكون مشغّل الانتقال بين طرق العرض في المستندات هو عملية تنقّل من المصدر نفسه من صفحة إلى أخرى، وهو إجراء ينفّذه عادةً مستخدم موقعك الإلكتروني من خلال النقر على رابط.

بعبارة أخرى، لا تتوفّر واجهة برمجة تطبيقات يمكن طلبها لبدء انتقال العرض بين مستندَين. ومع ذلك، يجب استيفاء شرطَين:

  • يجب أن يكون المستندان متاحَين على المصدر نفسه.
  • يجب أن توافق كلتا الصفحتَين على السماح بانتقال العرض.

سيتم توضيح هذين الشرطين لاحقًا في هذا المستند.


تقتصر انتقالات العرض بين المستندات على عمليات التنقّل من المصدر نفسه

تقتصر انتقالات العرض بين المستندات على عمليات التنقّل من المصدر نفسه فقط. يُعدّ التنقّل من المصدر نفسه إذا كان مصدر كلتا الصفحتين المشاركتَين هو نفسه.

إنّ مصدر الصفحة هو مزيج من المخطط واسم المضيف والمنفذ المستخدَمين، كما هو موضّح على web.dev.

مثال على عنوان URL مع تمييز المخطّط واسم المضيف والمنفذ وعند دمجها، تشكّل المصدر.
مثال على عنوان URL مع تمييز المخطّط واسم المضيف والمنفذ تشكّل هذه الأجزاء معًا الأصل.

على سبيل المثال، يمكنك إجراء انتقال عرض بين المستندات عند الانتقال من developer.chrome.com إلى developer.chrome.com/blog، لأنّ هذين المستندَين من المصدر نفسه. لا يمكنك إجراء هذا الانتقال عند التنقّل من developer.chrome.com إلى www.chrome.com، لأنّ هذين الموقعَين الإلكترونيَين هما موقعان إلكترونيان متوافقان مع المصدر نفسه.


يجب الموافقة على استخدام انتقالات العرض بين المستندات

لإجراء انتقال عرض بين مستندَين، يجب أن توافق كلتا الصفحتَين المشاركتَين على السماح بذلك. يتم ذلك باستخدام قاعدة @view-transition في CSS.

في قاعدة @view-transition ‎@، اضبط الواصف navigation على auto لتفعيل عمليات الانتقال بين طرق العرض لعمليات التنقّل بين المستندات من المصدر نفسه.

@view-transition {
  navigation: auto;
}

من خلال ضبط واصف navigation على auto، أنت توافق على السماح بانتقالات العرض بأن تحدث لأنواع NavigationType التالية:

  • traverse
  • push أو replace، إذا لم يبدأ المستخدم عملية التفعيل من خلال آليات واجهة مستخدم المتصفّح

تتضمّن عمليات التنقّل المستبعَدة من auto، على سبيل المثال، التنقّل باستخدام شريط عناوين URL أو النقر على إشارة مرجعية، بالإضافة إلى أي شكل من أشكال إعادة التحميل التي يبدأها المستخدم أو البرنامج النصي.

إذا استغرقت عملية التنقّل وقتًا طويلاً، أي أكثر من أربع ثوانٍ في حالة Chrome، يتم تخطّي انتقال العرض مع TimeoutError DOMException.

عرض توضيحي لعمليات الانتقال بين طرق العرض في المستندات

اطّلِع على العرض التوضيحي التالي الذي يستخدم انتقالات العرض لإنشاء عرض توضيحي لـ Stack Navigator. لا توجد طلبات إلى document.startViewTransition() هنا، ويتم تشغيل انتقالات العرض من خلال الانتقال من صفحة إلى أخرى.

تسجيل العرض التوضيحي لـ Stack Navigator يجب أن يكون الإصدار 126 من Chrome أو إصدار أحدث.

تخصيص انتقالات العرض بين المستندات

لتخصيص انتقالات العرض بين المستندات، يمكنك استخدام بعض ميزات النظام الأساسي على الويب.

هذه الميزات ليست جزءًا من مواصفات View Transition API نفسها، ولكنّها مصمَّمة لاستخدامها معها.

الحدثان pageswap وpagereveal

Browser Support

  • Chrome: 124.
  • Edge: 124.
  • Firefox: not supported.
  • Safari: 18.2.

Source

للسماح لك بتخصيص انتقالات العرض بين المستندات، يتضمّن مواصفات HTML حدثَين جديدَين يمكنك استخدامهما: pageswap وpagereveal.

يتم تنشيط هذين الحدثين لكل عملية تنقّل بين المستندات من المصدر نفسه بغض النظر عمّا إذا كان سيحدث انتقال عرض أم لا. إذا كان سيحدث انتقال بين الصفحتين، يمكنك الوصول إلى العنصر ViewTransition باستخدام السمة viewTransition في هذه الأحداث.

  • يتم تنشيط الحدث pageswap قبل عرض آخر إطار لصفحة معيّنة. يمكنك استخدام هذا الإجراء لإجراء بعض التغييرات في اللحظة الأخيرة على الصفحة الصادرة، وذلك قبل أخذ اللقطات القديمة مباشرةً.
  • يتم تنشيط الحدث pagereveal على صفحة بعد تهيئتها أو إعادة تنشيطها ولكن قبل فرصة العرض الأولى. باستخدام هذه الميزة، يمكنك تخصيص الصفحة الجديدة قبل التقاط اللقطات الجديدة.

على سبيل المثال، يمكنك استخدام هذه الأحداث لضبط بعض قيم view-transition-name أو تغييرها بسرعة أو نقل البيانات من مستند إلى آخر من خلال كتابة البيانات وقراءتها من sessionStorage لتخصيص انتقال العرض قبل تنفيذه فعليًا.

let lastClickX, lastClickY;
document.addEventListener('click', (event) => {
  if (event.target.tagName.toLowerCase() === 'a') return;
  lastClickX = event.clientX;
  lastClickY = event.clientY;
});

// Write position to storage on old page
window.addEventListener('pageswap', (event) => {
  if (event.viewTransition && lastClick) {
    sessionStorage.setItem('lastClickX', lastClickX);
    sessionStorage.setItem('lastClickY', lastClickY);
  }
});

// Read position from storage on new page
window.addEventListener('pagereveal', (event) => {
  if (event.viewTransition) {
    lastClickX = sessionStorage.getItem('lastClickX');
    lastClickY = sessionStorage.getItem('lastClickY');
  }
});

إذا أردت، يمكنك تخطّي الانتقال في كلا الحدثين.

window.addEventListener("pagereveal", async (e) => {
  if (e.viewTransition) {
    if (goodReasonToSkipTheViewTransition()) {
      e.viewTransition.skipTransition();
    }
  }
}

العنصر ViewTransition في pageswap وpagereveal هما عنصران مختلفان. تتعامل هذه المكتبات أيضًا مع الوعود المختلفة بشكل مختلف:

  • pageswap: بعد إخفاء المستند، يتم تخطّي العنصر القديم ViewTransition. عند حدوث ذلك، يتم رفض viewTransition.ready ويتم حلّ viewTransition.finished.
  • pagereveal: تم حلّ الوعد updateCallBack في هذه المرحلة. يمكنك استخدام الوعود viewTransition.ready وviewTransition.finished.

Browser Support

  • Chrome: 123.
  • Edge: 123.
  • Firefox: 147.
  • Safari: 26.2.

Source

في كل من حدثَي pageswap وpagereveal، يمكنك أيضًا اتّخاذ إجراء استنادًا إلى عناوين URL الخاصة بالصفحات القديمة والجديدة.

على سبيل المثال، في MPA Stack Navigator، يعتمد نوع الحركة التي سيتم استخدامها على مسار التنقّل:

  • عند التنقّل من صفحة النظرة العامة إلى صفحة التفاصيل، يجب أن يظهر المحتوى الجديد من اليمين إلى اليسار.
  • عند التنقّل من صفحة التفاصيل إلى صفحة النظرة العامة، يجب أن ينزلق المحتوى القديم من اليمين إلى اليسار.

لإجراء ذلك، تحتاج إلى معلومات حول عملية التنقّل التي ستحدث في حالة pageswap أو حدثت للتو في حالة pagereveal.

لهذا السبب، يمكن للمتصفّحات الآن عرض عناصر NavigationActivation التي تتضمّن معلومات حول التنقّل من المصدر نفسه. يعرض هذا العنصر نوع التنقّل المستخدَم، وإدخالات سجلّ الوجهة الحالية والنهائية كما هو موضّح في navigation.entries() من Navigation API.

في صفحة مفعَّلة، يمكنك الوصول إلى هذا العنصر من خلال navigation.activation. في فعالية pageswap، يمكنك الوصول إلى هذه الميزة من خلال e.activation.

اطّلِع على عرض Profiles التوضيحي هذا الذي يستخدم معلومات NavigationActivation في الحدثَين pageswap وpagereveal لضبط قيم view-transition-name على العناصر التي يجب أن تشارك في عملية انتقال العرض.

بهذه الطريقة، لن تحتاج إلى تزيين كل عنصر في القائمة باستخدام view-transition-name مسبقًا. بدلاً من ذلك، يحدث ذلك في الوقت المناسب باستخدام JavaScript، فقط على العناصر التي تحتاج إليه.

تسجيل العرض التوضيحي لميزة "الملفات الشخصية" تتطلّب هذه الميزة الإصدار 126 من Chrome أو الإصدارات الأحدث.

في ما يلي الرمز:

// OLD PAGE LOGIC
window.addEventListener('pageswap', async (e) => {
  if (e.viewTransition) {
    const targetUrl = new URL(e.activation.entry.url);

    // Navigating to a profile page
    if (isProfilePage(targetUrl)) {
      const profile = extractProfileNameFromUrl(targetUrl);

      // Set view-transition-name values on the clicked row
      document.querySelector(`#${profile} span`).style.viewTransitionName = 'name';
      document.querySelector(`#${profile} img`).style.viewTransitionName = 'avatar';

      // Remove view-transition-names after snapshots have been taken
      // (this to deal with BFCache)
      await e.viewTransition.finished;
      document.querySelector(`#${profile} span`).style.viewTransitionName = 'none';
      document.querySelector(`#${profile} img`).style.viewTransitionName = 'none';
    }
  }
});

// NEW PAGE LOGIC
window.addEventListener('pagereveal', async (e) => {
  if (e.viewTransition) {
    const fromURL = new URL(navigation.activation.from.url);
    const currentURL = new URL(navigation.activation.entry.url);

    // Navigating from a profile page back to the homepage
    if (isProfilePage(fromURL) && isHomePage(currentURL)) {
      const profile = extractProfileNameFromUrl(currentURL);

      // Set view-transition-name values on the elements in the list
      document.querySelector(`#${profile} span`).style.viewTransitionName = 'name';
      document.querySelector(`#${profile} img`).style.viewTransitionName = 'avatar';

      // Remove names after snapshots have been taken
      // so that we're ready for the next navigation
      await e.viewTransition.ready;
      document.querySelector(`#${profile} span`).style.viewTransitionName = 'none';
      document.querySelector(`#${profile} img`).style.viewTransitionName = 'none';
    }
  }
});

يعمل الرمز أيضًا على تنظيف نفسه من خلال إزالة قيم view-transition-name بعد تنفيذ عملية انتقال العرض. بهذه الطريقة، تكون الصفحة جاهزة لعمليات التنقّل المتتالية ويمكنها أيضًا التعامل مع عمليات التنقّل في السجلّ.

للمساعدة في ذلك، استخدِم دالة الأداة المساعدة هذه التي تضبط view-transition-name مؤقتًا.

const setTemporaryViewTransitionNames = async (entries, vtPromise) => {
  for (const [$el, name] of entries) {
    $el.style.viewTransitionName = name;
  }

  await vtPromise;

  for (const [$el, name] of entries) {
    $el.style.viewTransitionName = '';
  }
}

يمكن الآن تبسيط الرمز السابق على النحو التالي:

// OLD PAGE LOGIC
window.addEventListener('pageswap', async (e) => {
  if (e.viewTransition) {
    const targetUrl = new URL(e.activation.entry.url);

    // Navigating to a profile page
    if (isProfilePage(targetUrl)) {
      const profile = extractProfileNameFromUrl(targetUrl);

      // Set view-transition-name values on the clicked row
      // Clean up after the page got replaced
      setTemporaryViewTransitionNames([
        [document.querySelector(`#${profile} span`), 'name'],
        [document.querySelector(`#${profile} img`), 'avatar'],
      ], e.viewTransition.finished);
    }
  }
});

// NEW PAGE LOGIC
window.addEventListener('pagereveal', async (e) => {
  if (e.viewTransition) {
    const fromURL = new URL(navigation.activation.from.url);
    const currentURL = new URL(navigation.activation.entry.url);

    // Navigating from a profile page back to the homepage
    if (isProfilePage(fromURL) && isHomePage(currentURL)) {
      const profile = extractProfileNameFromUrl(currentURL);

      // Set view-transition-name values on the elements in the list
      // Clean up after the snapshots have been taken
      setTemporaryViewTransitionNames([
        [document.querySelector(`#${profile} span`), 'name'],
        [document.querySelector(`#${profile} img`), 'avatar'],
      ], e.viewTransition.ready);
    }
  }
});

انتظِر إلى أن يتم تحميل المحتوى باستخدام ميزة حظر العرض

Browser Support

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

في بعض الحالات، قد تحتاج إلى تأجيل العرض الأول للصفحة إلى حين توفّر عنصر معيّن في DOM الجديد. يساعد ذلك في تجنُّب الوميض ويضمن ثبات الحالة التي يتم تحريكها.

في <head>، حدِّد معرّفًا واحدًا أو أكثر للعناصر التي يجب أن تكون متوفّرة قبل أن يتم عرض الصفحة للمرة الأولى، وذلك باستخدام العلامة الوصفية التالية.

<link rel="expect" blocking="render" href="#section1">

تعني هذه العلامة الوصفية أنّه يجب أن يكون العنصر متوفّرًا في DOM، وليس أنّه يجب تحميل المحتوى. على سبيل المثال، في ما يتعلّق بالصور، يكفي توفّر العلامة <img> مع id المحدّدة في شجرة نموذج المستند (DOM) لكي يتم تقييم الشرط على أنّه صحيح. قد تكون الصورة نفسها لا تزال قيد التحميل.

قبل أن تبدأ في حظر العرض، يجب أن تعلم أنّ العرض التدريجي هو أحد الجوانب الأساسية للويب، لذا يجب توخّي الحذر عند اختيار حظر العرض. يجب تقييم تأثير حظر العرض لكل حالة على حدة. تجنَّب استخدام blocking=render تلقائيًا إلا إذا كان بإمكانك قياس تأثيره على المستخدمين وتقييمه بشكل نشط من خلال قياس تأثيره في مؤشرات أداء الويب الأساسية.


عرض أنواع عمليات الانتقال في عمليات الانتقال بين المستندات

تتيح عمليات الانتقال بين طرق العرض في المستندات أيضًا أنواع عمليات الانتقال بين طرق العرض لتخصيص الرسوم المتحركة والعناصر التي يتم التقاطها.

على سبيل المثال، عند الانتقال إلى الصفحة التالية أو السابقة في تقسيم المحتوى إلى صفحات، قد تحتاج إلى استخدام رسوم متحركة مختلفة استنادًا إلى ما إذا كنت ستنتقل إلى صفحة أعلى أو أدنى من التسلسل.

لضبط هذه الأنواع مسبقًا، أضِف الأنواع في قاعدة @view-transition at-rule:

@view-transition {
  navigation: auto;
  types: slide, forwards;
}

لضبط الأنواع أثناء التنفيذ، استخدِم الحدثَين pageswap وpagereveal لتعديل قيمة e.viewTransition.types.

window.addEventListener("pagereveal", async (e) => {
  if (e.viewTransition) {
    const transitionType = determineTransitionType(navigation.activation.from, navigation.activation.entry);
    e.viewTransition.types.add(transitionType);
  }
});

لا يتم نقل الأنواع تلقائيًا من عنصر ViewTransition في الصفحة القديمة إلى عنصر ViewTransition في الصفحة الجديدة. عليك تحديد الأنواع التي سيتم استخدامها في الصفحة الجديدة على الأقل لكي تعمل الرسوم المتحركة على النحو المتوقّع.

للردّ على هذه الأنواع، استخدِم أداة اختيار الفئة الزائفة :active-view-transition-type() بالطريقة نفسها التي يتم بها استخدام انتقالات العرض في المستند نفسه.

/* Determine what gets captured when the type is forwards or backwards */
html:active-view-transition-type(forwards, backwards) {
  :root {
    view-transition-name: none;
  }
  article {
    view-transition-name: content;
  }
  .pagination {
    view-transition-name: pagination;
  }
}

/* Animation styles for forwards type only */
html:active-view-transition-type(forwards) {
  &::view-transition-old(content) {
    animation-name: slide-out-to-left;
  }
  &::view-transition-new(content) {
    animation-name: slide-in-from-right;
  }
}

/* Animation styles for backwards type only */
html:active-view-transition-type(backwards) {
  &::view-transition-old(content) {
    animation-name: slide-out-to-right;
  }
  &::view-transition-new(content) {
    animation-name: slide-in-from-left;
  }
}

/* Animation styles for reload type only */
html:active-view-transition-type(reload) {
  &::view-transition-old(root) {
    animation-name: fade-out, scale-down;
  }
  &::view-transition-new(root) {
    animation-delay: 0.25s;
    animation-name: fade-in, scale-up;
  }
}

بما أنّ الأنواع لا تنطبق إلا على انتقال نشط للعرض، تتم إزالة الأنواع تلقائيًا عند انتهاء انتقال العرض. لهذا السبب، تعمل الأنواع بشكل جيد مع ميزات مثل BFCache.

عرض توضيحي

في عرض التقسيم على صفحات التالي، ينزلق محتوى الصفحة إلى الأمام أو الخلف استنادًا إلى رقم الصفحة التي تنتقل إليها.

تسجيل عرض توضيحي عن تقسيم المحتوى إلى صفحات (MPA) يستخدم هذا التطبيق انتقالات مختلفة حسب الصفحة التي ستنتقل إليها.

يتم تحديد نوع الانتقال المطلوب استخدامه في الحدثَين pagereveal وpageswap من خلال النظر إلى عناوين URL المصدر والوجهة.

const determineTransitionType = (fromNavigationEntry, toNavigationEntry) => {
  const currentURL = new URL(fromNavigationEntry.url);
  const destinationURL = new URL(toNavigationEntry.url);

  const currentPathname = currentURL.pathname;
  const destinationPathname = destinationURL.pathname;

  if (currentPathname === destinationPathname) {
    return "reload";
  } else {
    const currentPageIndex = extractPageIndexFromPath(currentPathname);
    const destinationPageIndex = extractPageIndexFromPath(destinationPathname);

    if (currentPageIndex > destinationPageIndex) {
      return 'backwards';
    }
    if (currentPageIndex < destinationPageIndex) {
      return 'forwards';
    }

    return 'unknown';
  }
};

الملاحظات

نحن نقدّر دائمًا ملاحظات المطوّرين. للمشاركة، يُرجى الإبلاغ عن مشكلة لدى مجموعة عمل CSS على GitHub مع تضمين الاقتراحات والأسئلة. يجب إضافة البادئة [css-view-transitions] إلى المشكلة. في حال مواجهة خطأ، يُرجى إرسال تقرير عن خلل في Chromium بدلاً من ذلك.