عندما يحدث انتقال عرض بين مستندَين مختلفَين، يُعرف ذلك باسم انتقال عرض بين مستندَين. ويحدث ذلك عادةً في التطبيقات المتعدّدة الصفحات (MPA). تتوفّر عمليات النقل بين طرق العرض في المستندات المختلفة في Chrome من الإصدار 126.
توافق المتصفّح
تعتمد عمليات الانتقال بين طرق العرض في المستندات المختلفة على الوحدات الأساسية والمبادئ نفسها المستخدَمة في عمليات الانتقال بين طرق العرض في المستند نفسه، وذلك عن قصد:
- يأخذ المتصفّح لقطات من العناصر التي تحتوي على
view-transition-name
فريد في كلّ من الصفحة القديمة والجديدة. - يتم تعديل DOM أثناء إيقاف العرض.
- وأخيرًا، يتم تشغيل عمليات النقل باستخدام رسوم CSS المتحركة.
الفرق بين هذه الانتقالات وانتقالات طريقة العرض في المستند نفسه هو أنّه لا تحتاج إلى استدعاء document.startViewTransition
لبدء انتقال طريقة عرض في جميع المستندات. بدلاً من ذلك، يكون العامل المشغِّل لعملية الانتقال من عرض صفحة إلى عرض مستند آخر هو الانتقال من صفحة إلى أخرى من مصدر مماثل، وهو إجراء ينفّذه عادةً مستخدم موقعك الإلكتروني من خلال النقر على رابط.
بمعنى آخر، لا توجد واجهة برمجة تطبيقات لاستدعاء من أجل بدء انتقال عرض بين مستندين. ومع ذلك، هناك شرطان يجب استيفاؤهما:
- يجب أن يكون كلا المستندَين متوفّرَين في المصدر نفسه.
- يجب أن توافق كلتا الصفحتَين على السماح بنقل العرض.
يتم شرح هذين الشرطين لاحقًا في هذا المستند.
تقتصر عمليات الانتقال بين طرق العرض في المستندات المختلفة على عمليات التنقّل من المصدر نفسه.
تقتصر عمليات الانتقال بين طرق العرض في المستندات المختلفة على عمليات التنقّل من المصدر نفسه فقط. يُعتبر التنقّل من مصدرٍ واحد إذا كان مصدر كلتا الصفحتَين المشارِكتَين هو نفسه.
يشير مصدر الصفحة إلى مجموعة من المخطط واسم المضيف والمنفذ المستخدَمين، كما هو موضّح بالتفصيل على web.dev.
على سبيل المثال، يمكنك الانتقال من عرض في مستند إلى عرض في مستند آخر عند الانتقال من 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()
، ويتم تشغيل عمليات التنقّل في العرض من خلال الانتقال من صفحة إلى أخرى.
تخصيص عمليات انتقال العرض في جميع المستندات
لتخصيص عمليات الانتقال بين طرق العرض في المستندات المختلفة، هناك بعض ميزات منصة الويب التي يمكنك استخدامها.
لا تشكّل هذه الميزات جزءًا من مواصفات View Transition API نفسها، ولكن تم تصميمها ليتم استخدامها مع هذه المواصفات.
الحدثان pageswap
وpagereveal
للسماح لك بتخصيص عمليات النقل بين طرق العرض في المستندات، تتضمّن مواصفات 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
.
معلومات تفعيل التنقّل
في كلّ من حدثَي 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، فقط على العناصر التي تحتاج إليه.
في ما يلي الرمز:
// 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);
}
}
});
الانتظار إلى أن يتم تحميل المحتوى مع حظر العرض
توافق المتصفّح
في بعض الحالات، قد تحتاج إلى تأجيل العرض الأول للصفحة إلى أن يتوفر عنصر معين في 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.
عرض توضيحي
في العرض التوضيحي للتقسيم على صفحات التالي، يتم عرض محتوى الصفحة للأمام أو للخلف استنادًا إلى رقم الصفحة التي تنتقل إليها.
يتم تحديد نوع النقل الذي سيتم استخدامه في حدثَي 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 بدلاً من ذلك.