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