عندما يحدث انتقال للعرض بين مستندين مختلفين، يُسمى انتقال عرض عبر المستندات. وعادةً ما يحدث ذلك في التطبيقات المتعددة الصفحات (MPA). تتوفّر عمليات نقل العرض بين المستندات في Chrome بدءًا من الإصدار 126 من Chrome.
تعتمد عمليات انتقال العرض بين المستندات على الوحدات الأساسية والمبادئ نفسها التي تستخدمها عمليات النقل لعرض المستندات نفسها، وهي مقصودة جدًا:
- يأخذ المتصفح لقطات من العناصر التي لها
view-transition-name
فريد في كل من الصفحة القديمة والجديدة. - يتم تعديل نموذج العناصر في المستند (DOM) أثناء إلغاء العرض.
- وأخيرًا، يتم دعم الانتقالات بواسطة الرسوم المتحركة في CSS.
يختلف الأمر عند مقارنته بتأثيرات عرض المستند نفسه، هو أنه عند استخدام عمليات انتقال العرض على جميع المستندات، لا تحتاج إلى طلب document.startViewTransition
لبدء انتقال العرض. بدلاً من ذلك، يكون عامل النقل أثناء الاطّلاع على مستندات متعدّدة هو التنقّل من المصدر نفسه من صفحة إلى أخرى، وهو إجراء ينفذه عادةً مستخدم موقعك الإلكتروني الذي ينقر على رابط.
بمعنى آخر، لا توجد واجهة برمجة تطبيقات يمكن طلبها من أجل بدء انتقال العرض بين مستندين. ومع ذلك، هناك شرطان يجب استيفاؤهما:
- يجب أن يتوفّر كلا المستندَين على المصدر نفسه.
- ويجب أن توافق كلتا الصفحتين على السماح بانتقال العرض.
يتم شرح هذين الشرطين لاحقًا في هذا المستند.
تقتصر عمليات نقل المشاهدات في المستندات المشتركة على عمليات التنقّل من المصدر نفسه.
تقتصر عمليات نقل العرض في مستندات متعدّدة على عمليات التنقّل من المصدر نفسه فقط. يُعتبر التنقّل من المصدر نفسه إذا كان مصدر كلتا الصفحتين المشارِكتين هو نفسه.
إنّ أصل الصفحة هو تركيبة من المخطط واسم المضيف والمنفذ المُستخدَم، كما هو موضّح في web.dev.
![مثال على عنوان URL مع تمييز المخطط واسم المضيف والمنفذ وكلاهما يشكِّلان المنشأ.](https://developer.chrome.com/static/docs/web-platform/view-transitions/image/origin.png?authuser=6&hl=ar)
على سبيل المثال، يمكنك نقل عرض المستندات المتعدّدة عند الانتقال من 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 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
" تلقائيًا ما لم تتمكن من قياس تأثيره على المستخدمين وقياسه بشكل فعّال، وذلك عن طريق قياس تأثيره على مؤشرات أداء الويب الأساسية.
عرض أنواع الانتقالات في انتقالات العرض على جميع المستندات
تتوافق أيضًا انتقالات العرض عبر المستندات مع أنواع الانتقالات للعرض لتخصيص الصور المتحركة والعناصر التي يتم التقاطها.
على سبيل المثال، عند الانتقال إلى الصفحة التالية أو إلى الصفحة السابقة في التقسيم على صفحات، قد تحتاج إلى استخدام صور متحركة مختلفة بناءً على ما إذا كنت ستنتقل إلى صفحة أعلى أو إلى صفحة أقل من التسلسل.
لضبط هذه الأنواع مقدمًا، أضف الأنواع في قاعدة @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 بدلاً من ذلك.