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

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

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

  • 126
  • 126
  • x
  • x

المصدر

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

  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

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

  • 124
  • 124
  • x
  • x

المصدر

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

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

  • 123
  • 123
  • x
  • x

المصدر

في كل من الحدثَين 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);
    }
  }
});

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

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

  • 124
  • 124
  • x
  • x

المصدر

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

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

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

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

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


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

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

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

لضبط هذه الأنواع مقدمًا، أضف الأنواع في قاعدة @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 من وإلى عناوين 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 بدلاً من ذلك.