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