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

عندما يحدث انتقال عرض بين مستندَين مختلفَين، يُعرف ذلك باسم انتقال عرض بين مستندَين. ويحدث ذلك عادةً في التطبيقات المتعدّدة الصفحات (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 API.

في صفحة مفعّلة، يمكنك الوصول إلى هذا العنصر من خلال 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 بدلاً من ذلك.