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

Bramus
Bramus

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

دعم المتصفح

  • Chrome: 126.
  • الحافة: 126.
  • Firefox: غير مدعوم.
  • Safari: غير متاح.

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

  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

دعم المتصفح

  • Chrome: 124.
  • الحافة: 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.
  • الحافة: 123.
  • Firefox: غير مدعوم.
  • Safari: غير متاح.

المصدر

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

على سبيل المثال، في أداة استكشاف حزم MPA، يعتمد نوع الصورة المتحركة المراد استخدامها على مسار التنقل:

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

لإجراء ذلك، تحتاج إلى معلومات حول التنقّل الذي على وشك الحدوث، في حالة 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.
  • الحافة: 124.
  • Firefox: غير مدعوم.
  • Safari: غير متاح.

في بعض الحالات، قد تحتاج إلى تأجيل العرض الأول للصفحة إلى أن يتوفر عنصر معين في 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 بدلاً من ذلك.