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

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

توافق المتصفّح

  • Chrome: 126
  • Edge:‏ 126
  • Firefox: غير متوافق
  • Safari Technology Preview: متاح

تعتمد عمليات الانتقال بين طرق العرض في المستندات المختلفة على الوحدات الأساسية والمبادئ نفسها المستخدَمة في عمليات الانتقال بين طرق العرض في المستند نفسه، وذلك عن قصد:

  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، لأنّهما من مصادر متعددة للموقع الإلكتروني نفسه.


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

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

في قاعدة at-rule‏ @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

دعم المتصفح

  • Chrome: 124
  • ‫Edge: 124
  • Firefox: غير متوافق
  • Safari: غير متوافق

المصدر

للسماح لك بتخصيص عمليات النقل بين طرق العرض في المستندات، تتضمّن مواصفات 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.

توافق المتصفّح

  • Chrome: 123
  • Edge:‏ 123.
  • Firefox: غير متوافق
  • Safari: غير متوافق

المصدر

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

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

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

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

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

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

يمكنك الاطّلاع على هذا العرض التوضيحي للملفات الشخصية الذي يستخدِم معلومات 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);
    }
  }
});

الانتظار إلى أن يتم تحميل المحتوى مع حظر العرض

توافق المتصفّح

  • Chrome: 124.
  • ‫Edge: 124
  • Firefox: غير مدعوم.
  • Safari: غير متاح.

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

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

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

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

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


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

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

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

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

@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 بدلاً من ذلك.