تاريخ النشر: 17 أغسطس 2021، تاريخ آخر تعديل: 25 سبتمبر 2024
عندما يتم تنفيذ انتقال عرض على مستند واحد، يُطلق عليه اسم انتقال عرض على المستند نفسه. ويحدث ذلك عادةً في تطبيقات الصفحة الواحدة (SPA) التي يتم فيها استخدام JavaScript لتعديل نموذج المستند (DOM). تتوفّر انتقالات العرض نفسها في Chrome اعتبارًا من الإصدار 111.
لتفعيل انتقال العرض نفسه في المستند، استخدِم الدالة document.startViewTransition
:
function handleClick(e) {
// Fallback for browsers that don't support this API:
if (!document.startViewTransition) {
updateTheDOMSomehow();
return;
}
// With a View Transition:
document.startViewTransition(() => updateTheDOMSomehow());
}
عند استدعاء هذه الدالة، يلتقط المتصفّح تلقائيًا لقطات لجميع العناصر التي تم تعريف خاصية view-transition-name
CSS عليها.
بعد ذلك، يتم تنفيذ دالة معاودة الاتصال التي تم تمريرها والتي تعدّل عنصر DOM، وبعدها يتم أخذ لقطات للحالة الجديدة.
يتم بعد ذلك ترتيب هذه اللقطات في شجرة من العناصر الزائفة وتحريكها باستخدام قوة حركات CSS. تنتقل أزواج اللقطات من الحالة القديمة والجديدة بسلاسة من موضعها وحجمها القديمَين إلى موضعها الجديد، بينما يتلاشى محتواها تدريجيًا. يمكنك استخدام CSS لتخصيص الحركات إذا أردت ذلك.
الانتقال التلقائي: التلاشي التدريجي
انتقال العرض التلقائي هو التلاشي التدريجي، لذا فهو يمثّل مقدمة جيدة لواجهة برمجة التطبيقات:
function spaNavigate(data) {
// Fallback for browsers that don't support this API:
if (!document.startViewTransition) {
updateTheDOMSomehow(data);
return;
}
// With a transition:
document.startViewTransition(() => updateTheDOMSomehow(data));
}
حيث يغيّر updateTheDOMSomehow
DOM إلى الحالة الجديدة. ويمكنك إجراء ذلك بالطريقة التي تريدها. على سبيل المثال، يمكنك إضافة عناصر أو إزالتها أو تغيير أسماء الفئات أو تغيير الأنماط.
وبذلك، تتلاشى الصفحات تدريجيًا على النحو التالي:
حسنًا، ليس من المدهش جدًا أن يتلاشى الصوت تدريجيًا. لحسن الحظ، يمكن تخصيص عمليات الانتقال، ولكن عليك أولاً فهم طريقة عمل هذا التلاشي التدريجي الأساسي.
طريقة عمل هذه الانتقالات
لنعدّل نموذج الرمز السابق.
document.startViewTransition(() => updateTheDOMSomehow(data));
عند استدعاء .startViewTransition()
، تسجّل واجهة برمجة التطبيقات الحالة الحالية للصفحة. ويشمل ذلك التقاط لقطة شاشة.
بعد اكتمال العملية، يتم استدعاء دالة الرجوع التي تم تمريرها إلى .startViewTransition()
. وهذا هو المكان الذي يتم فيه تغيير نموذج المستند. بعد ذلك، تسجّل واجهة برمجة التطبيقات الحالة الجديدة للصفحة.
بعد تسجيل الحالة الجديدة، تنشئ واجهة برمجة التطبيقات شجرة عنصر زائف على النحو التالي:
::view-transition
└─ ::view-transition-group(root)
└─ ::view-transition-image-pair(root)
├─ ::view-transition-old(root)
└─ ::view-transition-new(root)
يظهر ::view-transition
في طبقة متراكبة فوق كل العناصر الأخرى على الصفحة. يكون هذا مفيدًا إذا أردت ضبط لون خلفية للانتقال.
::view-transition-old(root)
هي لقطة شاشة للعرض القديم، و::view-transition-new(root)
هي تمثيل مباشر للعرض الجديد. يتم عرض كليهما على أنّهما "محتوى بديل" في CSS (مثل <img>
).
يتم تحريك العرض القديم من opacity: 1
إلى opacity: 0
، بينما يتم تحريك العرض الجديد من opacity: 0
إلى opacity: 1
، ما يؤدي إلى إنشاء تأثير التلاشي التدريجي.
يتم تنفيذ كل الحركة باستخدام حركات CSS، لذا يمكن تخصيصها باستخدام CSS.
تخصيص الانتقال
يمكن استهداف جميع العناصر الزائفة الخاصة بانتقالات العرض باستخدام CSS، وبما أنّ الحركات يتم تحديدها باستخدام CSS، يمكنك تعديلها باستخدام خصائص حركات CSS الحالية. على سبيل المثال:
::view-transition-old(root),
::view-transition-new(root) {
animation-duration: 5s;
}
بعد إجراء هذا التغيير، أصبح التلاشي بطيئًا جدًا:
حسنًا، هذا ليس مثيرًا للإعجاب. بدلاً من ذلك، ينفّذ الرمز التالي عملية الانتقال بين الأنشطة باستخدام محور مشترك في Material Design:
@keyframes fade-in {
from { opacity: 0; }
}
@keyframes fade-out {
to { opacity: 0; }
}
@keyframes slide-from-right {
from { transform: translateX(30px); }
}
@keyframes slide-to-left {
to { transform: translateX(-30px); }
}
::view-transition-old(root) {
animation: 90ms cubic-bezier(0.4, 0, 1, 1) both fade-out,
300ms cubic-bezier(0.4, 0, 0.2, 1) both slide-to-left;
}
::view-transition-new(root) {
animation: 210ms cubic-bezier(0, 0, 0.2, 1) 90ms both fade-in,
300ms cubic-bezier(0.4, 0, 0.2, 1) both slide-from-right;
}
وفي ما يلي النتيجة:
نقل عناصر متعددة
في العرض التوضيحي السابق، تشارك الصفحة بأكملها في عملية الانتقال على المحور المشترك. هذا الإجراء مناسب لمعظم محتوى الصفحة، ولكنّه لا يبدو مناسبًا للعنوان، إذ يختفي ثم يظهر مجددًا.
لتجنُّب ذلك، يمكنك استخراج العنوان من بقية الصفحة حتى يمكن تحريكه بشكل منفصل. يتم ذلك من خلال تعيين view-transition-name
للعنصر.
.main-header {
view-transition-name: main-header;
}
يمكن أن تكون قيمة view-transition-name
أي قيمة تريدها (باستثناء none
، ما يعني أنّه لا يوجد اسم انتقال). يُستخدَم هذا المعرّف لتحديد العنصر بشكلٍ فريد أثناء الانتقال.
ونتيجةً لذلك:
يبقى العنوان ثابتًا ويتم الانتقال بين الصور بسلاسة.
أدّى تعريف CSS هذا إلى تغيير شجرة العنصر الصوري:
::view-transition
├─ ::view-transition-group(root)
│ └─ ::view-transition-image-pair(root)
│ ├─ ::view-transition-old(root)
│ └─ ::view-transition-new(root)
└─ ::view-transition-group(main-header)
└─ ::view-transition-image-pair(main-header)
├─ ::view-transition-old(main-header)
└─ ::view-transition-new(main-header)
أصبحت هناك الآن مجموعتان للانتقال. أحدهما للرأس والآخر لبقية الصفحة. ويمكن استهداف هذه العناصر بشكل مستقل باستخدام CSS، ومنحها انتقالات مختلفة. مع ذلك، في هذه الحالة، تم ترك main-header
مع الانتقال التلقائي، وهو التلاشي التدريجي.
حسنًا، الانتقال التلقائي ليس مجرد تلاشٍ تدريجي، بل يشمل أيضًا ::view-transition-group
:
- تحديد الموضع وتغيير الشكل (باستخدام
transform
) - العرض
- الطول
لم يكن ذلك مهمًا حتى الآن، لأنّ حجم العنوان وموضعه يظلان كما هما على كلا الجانبين من تغيير DOM. ولكن يمكنك أيضًا استخراج النص في العنوان:
.main-header-text {
view-transition-name: main-header-text;
width: fit-content;
}
يتم استخدام fit-content
ليكون حجم العنصر هو حجم النص، بدلاً من التمدّد إلى العرض المتبقي. وبدون ذلك، سيؤدي السهم الخلفي إلى تقليل حجم عنصر نص العنوان، بدلاً من الحفاظ على الحجم نفسه في كلتا الصفحتين.
لذا، لدينا الآن ثلاثة أجزاء يمكننا التلاعب بها:
::view-transition
├─ ::view-transition-group(root)
│ └─ …
├─ ::view-transition-group(main-header)
│ └─ …
└─ ::view-transition-group(main-header-text)
└─ …
ولكن مرة أخرى، ما عليك سوى استخدام الإعدادات التلقائية:
الآن، ينزلق نص العنوان قليلاً بشكلٍ مُرضٍ لإتاحة مساحة لزر الرجوع.
تحريك عناصر زائفة متعددة بالطريقة نفسها باستخدام view-transition-class
لنفترض أنّ لديك انتقال عرض يتضمّن مجموعة من البطاقات بالإضافة إلى عنوان على الصفحة. لتحريك جميع البطاقات باستثناء العنوان، عليك كتابة أداة اختيار تستهدف كل بطاقة على حدة.
h1 {
view-transition-name: title;
}
::view-transition-group(title) {
animation-timing-function: ease-in-out;
}
#card1 { view-transition-name: card1; }
#card2 { view-transition-name: card2; }
#card3 { view-transition-name: card3; }
#card4 { view-transition-name: card4; }
…
#card20 { view-transition-name: card20; }
::view-transition-group(card1),
::view-transition-group(card2),
::view-transition-group(card3),
::view-transition-group(card4),
…
::view-transition-group(card20) {
animation-timing-function: var(--bounce);
}
هل لديك 20 عنصرًا؟ هذا يعني أنّه عليك كتابة 20 أداة اختيار. هل تريد إضافة عنصر جديد؟ بعد ذلك، عليك أيضًا زيادة أداة الاختيار التي تطبّق أنماط الرسوم المتحركة. ليست قابلة للتوسّع تمامًا.
يمكن استخدام view-transition-class
في العناصر الزائفة لانتقال العرض لتطبيق قاعدة النمط نفسها.
#card1 { view-transition-name: card1; }
#card2 { view-transition-name: card2; }
#card3 { view-transition-name: card3; }
#card4 { view-transition-name: card4; }
#card5 { view-transition-name: card5; }
…
#card20 { view-transition-name: card20; }
#cards-wrapper > div {
view-transition-class: card;
}
html::view-transition-group(.card) {
animation-timing-function: var(--bounce);
}
يستفيد مثال البطاقات التالي من مقتطف CSS السابق. يتم تطبيق التوقيت نفسه على جميع البطاقات، بما في ذلك البطاقات التي تمت إضافتها حديثًا، باستخدام أداة اختيار واحدة: html::view-transition-group(.card)
.
view-transition-class
، يتم تطبيق animation-timing-function
نفسه على جميع البطاقات باستثناء البطاقات التي تمت إضافتها أو إزالتها.تصحيح الأخطاء في عمليات الانتقال
بما أنّ انتقالات العرض تستند إلى رسوم CSS المتحركة، فإنّ لوحة الرسوم المتحركة في "أدوات مطوّري البرامج في Chrome" هي أداة رائعة لتصحيح أخطاء الانتقالات.
باستخدام لوحة الرسوم المتحركة، يمكنك إيقاف الرسوم المتحركة التالية مؤقتًا، ثم التقديم والترجيع خلال الرسوم المتحركة. خلال ذلك، يمكن العثور على العناصر الزائفة للانتقال في لوحة العناصر.
ليس من الضروري أن تكون العناصر الانتقالية هي عنصر DOM نفسه
حتى الآن، استخدمنا view-transition-name
لإنشاء عناصر انتقال منفصلة للعنوان والنص في العنوان. وهي من الناحية النظرية العنصر نفسه قبل وبعد تغيير نموذج المستند، ولكن يمكنك إنشاء انتقالات لا يكون فيها الأمر كذلك.
على سبيل المثال، يمكن منح عملية تضمين الفيديو الرئيسية القيمة view-transition-name
:
.full-embed {
view-transition-name: full-embed;
}
بعد ذلك، عند النقر على الصورة المصغّرة، يمكن منحها قيمة view-transition-name
نفسها، ولكن لمدة الانتقال فقط:
thumbnail.onclick = async () => {
thumbnail.style.viewTransitionName = 'full-embed';
document.startViewTransition(() => {
thumbnail.style.viewTransitionName = '';
updateTheDOMSomehow();
});
};
والنتيجة هي:
تتحوّل الصورة المصغّرة الآن إلى الصورة الرئيسية. على الرغم من أنّها عناصر مختلفة من الناحية النظرية (والحرفية)، تتعامل واجهة برمجة التطبيقات للانتقال معها على أنّها الشيء نفسه لأنّها تشترك في view-transition-name
نفسه.
الرمز الفعلي لهذا الانتقال أكثر تعقيدًا بعض الشيء من المثال السابق، لأنّه يتعامل أيضًا مع الانتقال مرة أخرى إلى صفحة الصور المصغّرة. الاطّلاع على المصدر للحصول على عملية التنفيذ الكاملة
عمليات انتقال مخصّصة للدخول والخروج
اطّلِع على هذا المثال:
يشكّل الشريط الجانبي جزءًا من عملية الانتقال:
.sidebar {
view-transition-name: sidebar;
}
ولكن على عكس العنوان في المثال السابق، لا يظهر الشريط الجانبي على جميع الصفحات. إذا كانت كلتا الحالتين تحتويان على الشريط الجانبي، ستبدو العناصر الزائفة للانتقال على النحو التالي:
::view-transition
├─ …other transition groups…
└─ ::view-transition-group(sidebar)
└─ ::view-transition-image-pair(sidebar)
├─ ::view-transition-old(sidebar)
└─ ::view-transition-new(sidebar)
ومع ذلك، إذا كان الشريط الجانبي يظهر فقط على الصفحة الجديدة، لن يكون العنصر الزائف ::view-transition-old(sidebar)
متوفّرًا. بما أنّه لا توجد صورة "قديمة" للشريط الجانبي، لن يتضمّن زوج الصور سوى ::view-transition-new(sidebar)
. وبالمثل، إذا كان الشريط الجانبي يظهر على الصفحة القديمة فقط، سيحتوي زوج الصور على ::view-transition-old(sidebar)
فقط.
في العرض التوضيحي السابق، يختلف انتقال الشريط الجانبي حسب ما إذا كان سيظهر أو يختفي أو يظهر في كلتا الحالتين. يظهر من خلال الانزلاق من اليمين والتلاشي تدريجيًا، ويختفي من خلال الانزلاق إلى اليمين والتلاشي تدريجيًا، ويبقى في مكانه عندما يكون متوفّرًا في كلتا الحالتين.
لإنشاء انتقالات دخول وخروج محدّدة، يمكنك استخدام الفئة الزائفة :only-child
لاستهداف العناصر الزائفة القديمة أو الجديدة عندما تكون العنصر الثانوي الوحيد في زوج الصور:
/* Entry transition */
::view-transition-new(sidebar):only-child {
animation: 300ms cubic-bezier(0, 0, 0.2, 1) both fade-in,
300ms cubic-bezier(0.4, 0, 0.2, 1) both slide-from-right;
}
/* Exit transition */
::view-transition-old(sidebar):only-child {
animation: 150ms cubic-bezier(0.4, 0, 1, 1) both fade-out,
300ms cubic-bezier(0.4, 0, 0.2, 1) both slide-to-right;
}
في هذه الحالة، لا يوجد انتقال محدّد عندما يكون الشريط الجانبي متوفّرًا في كلتا الحالتين، لأنّ الإعداد التلقائي مثالي.
تعديلات غير متزامنة على نموذج DOM، والانتظار إلى حين تحميل المحتوى
يمكن أن تعرض الدالة التي تم تمريرها إلى .startViewTransition()
وعدًا، ما يتيح إجراء تعديلات غير متزامنة على نموذج العناصر في المستند (DOM) والانتظار إلى أن يصبح المحتوى المهم جاهزًا.
document.startViewTransition(async () => {
await something;
await updateTheDOMSomehow();
await somethingElse;
});
لن تبدأ عملية الانتقال إلا بعد تنفيذ الوعد. خلال هذا الوقت، يتم تجميد الصفحة، لذا يجب الحدّ من التأخيرات هنا قدر الإمكان. على وجه التحديد، يجب إجراء عمليات جلب البيانات من الشبكة قبل استدعاء .startViewTransition()
، بينما تكون الصفحة لا تزال تفاعلية بالكامل، بدلاً من إجرائها كجزء من وظيفة رد الاتصال .startViewTransition()
.
إذا قرّرت الانتظار إلى أن تصبح الصور أو الخطوط جاهزة، احرص على استخدام مهلة قصوى صارمة:
const wait = ms => new Promise(r => setTimeout(r, ms));
document.startViewTransition(async () => {
updateTheDOMSomehow();
// Pause for up to 100ms for fonts to be ready:
await Promise.race([document.fonts.ready, wait(100)]);
});
ومع ذلك، في بعض الحالات، من الأفضل تجنُّب التأخير تمامًا واستخدام المحتوى المتوفّر لديك.
الاستفادة إلى أقصى حد من المحتوى المتوفّر لديك
في حال الانتقال من الصورة المصغّرة إلى صورة أكبر:
الانتقال التلقائي هو التلاشي التدريجي، ما يعني أنّ الصورة المصغّرة قد تتلاشى تدريجيًا مع صورة كاملة لم يتم تحميلها بعد.
إحدى طرق التعامل مع هذه المشكلة هي انتظار تحميل الصورة بالكامل قبل بدء الانتقال. من المفترض أن يتم ذلك قبل طلب .startViewTransition()
، لتبقى الصفحة تفاعلية، ويمكن عرض مؤشر تحميل لإعلام المستخدم بأنّ المحتوى قيد التحميل. ولكن في هذه الحالة، هناك طريقة أفضل:
::view-transition-old(full-embed),
::view-transition-new(full-embed) {
/* Prevent the default animation,
so both views remain opacity:1 throughout the transition */
animation: none;
/* Use normal blending,
so the new view sits on top and obscures the old view */
mix-blend-mode: normal;
}
الآن، لا تختفي الصورة المصغّرة تدريجيًا، بل تظهر أسفل الصورة الكاملة. يعني ذلك أنّه إذا لم يتم تحميل العرض الجديد، ستظهر الصورة المصغّرة طوال عملية الانتقال. وهذا يعني أنّه يمكن بدء الانتقال على الفور، ويمكن تحميل الصورة الكاملة في الوقت المناسب.
لن ينجح ذلك إذا كانت طريقة العرض الجديدة تتضمّن الشفافية، ولكن في هذه الحالة نعلم أنّها لا تتضمّن الشفافية، لذا يمكننا إجراء هذا التحسين.
التعامل مع التغييرات في نسبة العرض إلى الارتفاع
من الملائم أنّ جميع عمليات الانتقال حتى الآن كانت إلى عناصر لها نسبة العرض إلى الارتفاع نفسها، ولكن لن يكون الأمر كذلك دائمًا. ماذا لو كانت نسبة العرض إلى الارتفاع في الصورة المصغّرة 1:1 وفي الصورة الرئيسية 16:9؟
في الانتقال التلقائي، يتم تحريك المجموعة من الحجم السابق إلى الحجم اللاحق. يبلغ عرض كلّ من العرضَين القديم والجديد 100% من عرض المجموعة، ويكون الارتفاع تلقائيًا، ما يعني أنّهما يحافظان على نسبة العرض إلى الارتفاع بغض النظر عن حجم المجموعة.
هذا هو الإعداد التلقائي الجيد، ولكنّه ليس المطلوب في هذه الحالة. وبالتالي:
::view-transition-old(full-embed),
::view-transition-new(full-embed) {
/* Prevent the default animation,
so both views remain opacity:1 throughout the transition */
animation: none;
/* Use normal blending,
so the new view sits on top and obscures the old view */
mix-blend-mode: normal;
/* Make the height the same as the group,
meaning the view size might not match its aspect-ratio. */
height: 100%;
/* Clip any overflow of the view */
overflow: clip;
}
/* The old view is the thumbnail */
::view-transition-old(full-embed) {
/* Maintain the aspect ratio of the view,
by shrinking it to fit within the bounds of the element */
object-fit: contain;
}
/* The new view is the full image */
::view-transition-new(full-embed) {
/* Maintain the aspect ratio of the view,
by growing it to cover the bounds of the element */
object-fit: cover;
}
هذا يعني أنّ الصورة المصغّرة تبقى في وسط العنصر مع توسّع العرض، ولكن يتم "إلغاء اقتصاص" الصورة الكاملة أثناء انتقالها من نسبة 1:1 إلى 16:9.
لمزيد من المعلومات التفصيلية، اطّلِع على عمليات انتقال العرض: التعامل مع تغييرات نسبة العرض إلى الارتفاع.
استخدام طلبات البحث عن الوسائط لتغيير عمليات الانتقال لحالات الأجهزة المختلفة
قد تحتاج إلى استخدام انتقالات مختلفة على الأجهزة الجوّالة مقارنةً بأجهزة الكمبيوتر، مثل هذا المثال الذي ينفّذ شريحة كاملة من الجانب على الأجهزة الجوّالة، ولكن شريحة أكثر دقة على أجهزة الكمبيوتر:
يمكن تحقيق ذلك باستخدام طلبات البحث العادية في الوسائط:
/* Transitions for mobile */
::view-transition-old(root) {
animation: 300ms ease-out both full-slide-to-left;
}
::view-transition-new(root) {
animation: 300ms ease-out both full-slide-from-right;
}
@media (min-width: 500px) {
/* Overrides for larger displays.
This is the shared axis transition from earlier in the article. */
::view-transition-old(root) {
animation: 90ms cubic-bezier(0.4, 0, 1, 1) both fade-out,
300ms cubic-bezier(0.4, 0, 0.2, 1) both slide-to-left;
}
::view-transition-new(root) {
animation: 210ms cubic-bezier(0, 0, 0.2, 1) 90ms both fade-in,
300ms cubic-bezier(0.4, 0, 0.2, 1) both slide-from-right;
}
}
قد تحتاج أيضًا إلى تغيير العناصر التي تحدّد لها view-transition-name
استنادًا إلى طلبات البحث عن الوسائط المطابقة.
التفاعل مع الإعداد المفضّل "الحدّ من الحركة"
يمكن للمستخدمين الإشارة إلى أنّهم يفضّلون تقليل الحركة من خلال نظام التشغيل، ويتم عرض هذا الخيار في CSS.
يمكنك اختيار منع أي عمليات نقل لهؤلاء المستخدمين:
@media (prefers-reduced-motion) {
::view-transition-group(*),
::view-transition-old(*),
::view-transition-new(*) {
animation: none !important;
}
}
ومع ذلك، لا يعني تحديد الخيار المفضّل "تقليل الحركة" أنّ المستخدم يريد عدم ظهور أي حركة. بدلاً من المقتطف السابق، يمكنك اختيار رسم متحرك أكثر دقة، ولكنّه لا يزال يعبّر عن العلاقة بين العناصر وتدفّق البيانات.
التعامل مع أنماط متعدّدة لنقل العرض باستخدام أنواع نقل العرض
في بعض الأحيان، يجب أن يكون الانتقال من طريقة عرض معيّنة إلى أخرى مصمّمًا خصيصًا. على سبيل المثال، عند الانتقال إلى الصفحة التالية أو السابقة في تسلسل تقسيم على صفحات، قد تحتاج إلى تمرير المحتوى في اتجاه مختلف استنادًا إلى ما إذا كنت ستنتقل إلى صفحة أعلى أو أدنى من التسلسل.
يمكنك استخدام أنواع انتقالات العرض، ما يتيح لك تعيين نوع واحد أو أكثر إلى انتقال عرض نشط. على سبيل المثال، عند الانتقال إلى صفحة أعلى في تسلسل التقسيم على صفحات، استخدِم النوع forwards
، وعند الانتقال إلى صفحة أدنى، استخدِم النوع backwards
. لا تكون هذه الأنواع نشطة إلا عند تسجيل عملية انتقال أو تنفيذها، ويمكن تخصيص كل نوع من خلال CSS لاستخدام رسوم متحركة مختلفة.
لاستخدام الأنواع في انتقال عرض في المستند نفسه، عليك تمرير types
إلى طريقة startViewTransition
. للسماح بذلك، تقبل الدالة document.startViewTransition
أيضًا عنصرًا: update
هي دالة ردّ الاتصال التي تعدّل DOM، وtypes
هي مصفوفة تحتوي على الأنواع.
const direction = determineBackwardsOrForwards();
const t = document.startViewTransition({
update: updateTheDOMSomehow,
types: ['slide', direction],
});
للرد على هذه الأنواع، استخدِم أداة اختيار :active-view-transition-type()
. مرِّر type
الذي تريد استهدافه إلى أداة الاختيار. يتيح لك ذلك إبقاء أنماط انتقالات العرض المتعددة منفصلة عن بعضها البعض، بدون أن تتداخل تعريفات إحداها مع تعريفات الأخرى.
بما أنّ الأنواع لا تنطبق إلا عند تسجيل الانتقال أو تنفيذه، يمكنك استخدام أداة الاختيار لضبط view-transition-name
أو إلغاء ضبطها على عنصر فقط من أجل انتقال العرض بهذا النوع.
/* 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 (using the default root snapshot) */
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;
}
}
في عرض التقسيم على صفحات التالي، ينزلق محتوى الصفحة إلى الأمام أو الخلف استنادًا إلى رقم الصفحة التي تنتقل إليها. يتم تحديد الأنواع عند النقر عليها، وبعد ذلك يتم نقلها إلى document.startViewTransition
.
لاستهداف أي انتقال نشط للعرض، بغض النظر عن النوع، يمكنك استخدام أداة اختيار الفئة الزائفة :active-view-transition
بدلاً من ذلك.
html:active-view-transition {
…
}
التعامل مع أنماط انتقالات العرض المتعددة باستخدام اسم فئة في جذر انتقال العرض
في بعض الأحيان، يجب أن يكون الانتقال من نوع معيّن من العرض إلى آخر مصمّمًا خصيصًا. أو يجب أن يكون التنقّل "للخلف" مختلفًا عن التنقّل "للأمام".
قبل أنواع الانتقالات، كانت الطريقة المتبعة للتعامل مع هذه الحالات هي ضبط اسم فئة مؤقتًا على جذر الانتقال. عند استدعاء document.startViewTransition
، يكون جذر الانتقال هذا هو العنصر <html>
، ويمكن الوصول إليه باستخدام document.documentElement
في JavaScript:
if (isBackNavigation) {
document.documentElement.classList.add('back-transition');
}
const transition = document.startViewTransition(() =>
updateTheDOMSomehow(data)
);
try {
await transition.finished;
} finally {
document.documentElement.classList.remove('back-transition');
}
لإزالة الفئات بعد انتهاء الانتقال، يستخدم هذا المثال transition.finished
، وهو وعد يتم تنفيذه بمجرد وصول الانتقال إلى حالة النهاية. يتم تناول السمات الأخرى لهذا العنصر في مرجع واجهة برمجة التطبيقات.
يمكنك الآن استخدام اسم الفئة هذا في CSS لتغيير الانتقال:
/* 'Forward' transitions */
::view-transition-old(root) {
animation: 90ms cubic-bezier(0.4, 0, 1, 1) both fade-out,
300ms cubic-bezier(0.4, 0, 0.2, 1) both slide-to-left;
}
::view-transition-new(root) {
animation: 210ms cubic-bezier(0, 0, 0.2, 1) 90ms both fade-in, 300ms
cubic-bezier(0.4, 0, 0.2, 1) both slide-from-right;
}
/* Overrides for 'back' transitions */
.back-transition::view-transition-old(root) {
animation-name: fade-out, slide-to-right;
}
.back-transition::view-transition-new(root) {
animation-name: fade-in, slide-from-left;
}
كما هو الحال مع طلبات البحث عن الوسائط، يمكن أيضًا استخدام وجود هذه الفئات لتغيير العناصر التي تحصل على view-transition-name
.
تشغيل انتقالات بدون إيقاف الحركات الأخرى
اطّلِع على هذا العرض التوضيحي لفيديو ينتقل إلى موضع آخر:
هل لاحظت أي خطأ فيه؟ لا داعي للقلق إذا لم تكن قد فعلت ذلك. إليك الفيديو نفسه ولكن ببطء شديد:
أثناء عملية الانتقال، يبدو الفيديو وكأنّه متوقف مؤقتًا، ثم يظهر الإصدار الذي يتم تشغيله تدريجيًا. ويرجع ذلك إلى أنّ ::view-transition-old(video)
هي لقطة شاشة للعرض القديم، بينما ::view-transition-new(video)
هي صورة مباشرة للعرض الجديد.
يمكنك إصلاح هذا الخطأ، ولكن قبل ذلك، اسأل نفسك عمّا إذا كان يستحق الإصلاح. إذا لم تلاحظ "المشكلة" عندما كان الانتقال يتم تشغيله بالسرعة العادية، لا أنصحك بتغييرها.
إذا كنت تريد إصلاحها، لا تعرض ::view-transition-old(video)
، بل انتقِل مباشرةً إلى ::view-transition-new(video)
. يمكنك إجراء ذلك من خلال إلغاء الأنماط والرسوم المتحركة التلقائية:
::view-transition-old(video) {
/* Don't show the frozen old view */
display: none;
}
::view-transition-new(video) {
/* Don't fade the new view in */
animation: none;
}
هذا كل ما في الأمر!
سيتم الآن تشغيل الفيديو طوال فترة الانتقال.
التكامل مع Navigation API (والأُطر الأخرى)
يتم تحديد انتقالات العرض بطريقة تتيح دمجها مع أُطر عمل أو مكتبات أخرى. على سبيل المثال، إذا كان تطبيقك ذو الصفحة الواحدة (SPA) يستخدم موجّهًا، يمكنك تعديل آلية التحديث الخاصة بالموجّه لتعديل المحتوى باستخدام انتقال العرض.
في مقتطف الرمز التالي المأخوذ من عرض توضيحي للتنقل بين الصفحات، يتم تعديل معالج الاعتراض في Navigation API لاستدعاء document.startViewTransition
عند توفّر ميزة "انتقالات العرض".
navigation.addEventListener("navigate", (e) => {
// Don't intercept if not needed
if (shouldNotIntercept(e)) return;
// Intercept the navigation
e.intercept({
handler: async () => {
// Fetch the new content
const newContent = await fetchNewContent(e.destination.url, {
signal: e.signal,
});
// The UA does not support View Transitions, or the UA
// already provided a Visual Transition by itself (e.g. swipe back).
// In either case, update the DOM directly
if (!document.startViewTransition || e.hasUAVisualTransition) {
setContent(newContent);
return;
}
// Update the content using a View Transition
const t = document.startViewTransition(() => {
setContent(newContent);
});
}
});
});
توفّر بعض المتصفّحات، وليس كلّها، انتقالًا خاصًا بها عندما ينفّذ المستخدم إيماءة التمرير السريع للتنقّل. في هذه الحالة، يجب عدم بدء انتقال العرض الخاص بك لأنّ ذلك سيؤدي إلى تجربة مستخدم سيئة أو مربكة. سيشاهد المستخدم انتقالَين، أحدهما يوفّره المتصفّح والآخر توفّره أنت، ويتم تشغيلهما بالتتابع.
لذلك، يُنصح بمنع بدء انتقال العرض عندما يوفّر المتصفّح انتقالًا مرئيًا خاصًا به. لتحقيق ذلك، تحقَّق من قيمة السمة hasUAVisualTransition
في مثيل NavigateEvent
. يتم ضبط القيمة على true
عندما يوفّر المتصفّح انتقالًا مرئيًا. تتوفّر سمة hasUIVisualTransition
هذه أيضًا في مثيلات PopStateEvent
.
في المقتطف السابق، يأخذ التحقّق الذي يحدّد ما إذا كان سيتم تنفيذ انتقال العرض هذه السمة في الاعتبار. يتم تخطّي انتقال العرض في حال عدم توفّر دعم لعمليات انتقال العرض في المستند نفسه أو إذا كان المتصفّح قد وفّر عملية الانتقال الخاصة به.
if (!document.startViewTransition || e.hasUAVisualTransition) {
setContent(newContent);
return;
}
في التسجيل التالي، ينزلق المستخدم بإصبعه للرجوع إلى الصفحة السابقة. لا يتضمّن لقطة الشاشة على اليمين علامة hasUAVisualTransition
. يتضمّن التسجيل على اليسار المربّع، وبالتالي يتم تخطّي عملية الانتقال اليدوي للعرض لأنّ المتصفّح وفّر انتقالًا مرئيًا.
hasUAVisualTransition
إنشاء رسوم متحركة باستخدام JavaScript
حتى الآن، تم تحديد جميع عمليات الانتقال باستخدام CSS، ولكن في بعض الأحيان لا تكون CSS كافية:
لا يمكن تحقيق بعض أجزاء عملية الانتقال هذه باستخدام CSS فقط:
- تبدأ الصورة المتحركة من الموقع الجغرافي الذي تم النقر عليه.
- تنتهي الصورة المتحركة بأن يكون للدائرة نصف قطر يصل إلى أبعد زاوية. ومع ذلك، نأمل أن يكون ذلك ممكنًا باستخدام CSS في المستقبل.
لحسن الحظ، يمكنك إنشاء انتقالات باستخدام Web Animation API.
let lastClick;
addEventListener('click', event => (lastClick = event));
function spaNavigate(data) {
// Fallback for browsers that don't support this API:
if (!document.startViewTransition) {
updateTheDOMSomehow(data);
return;
}
// Get the click position, or fallback to the middle of the screen
const x = lastClick?.clientX ?? innerWidth / 2;
const y = lastClick?.clientY ?? innerHeight / 2;
// Get the distance to the furthest corner
const endRadius = Math.hypot(
Math.max(x, innerWidth - x),
Math.max(y, innerHeight - y)
);
// With a transition:
const transition = document.startViewTransition(() => {
updateTheDOMSomehow(data);
});
// Wait for the pseudo-elements to be created:
transition.ready.then(() => {
// Animate the root's new view
document.documentElement.animate(
{
clipPath: [
`circle(0 at ${x}px ${y}px)`,
`circle(${endRadius}px at ${x}px ${y}px)`,
],
},
{
duration: 500,
easing: 'ease-in',
// Specify which pseudo-element to animate
pseudoElement: '::view-transition-new(root)',
}
);
});
}
يستخدم هذا المثال transition.ready
، وهو وعد يتم تنفيذه بعد إنشاء العناصر الزائفة للانتقال بنجاح. يتم تناول الخصائص الأخرى لهذا العنصر في مرجع واجهة برمجة التطبيقات.
الانتقالات كتحسين
تم تصميم View Transition API لتضمين تغيير في نموذج المستند (DOM) وإنشاء انتقال له. ومع ذلك، يجب التعامل مع الانتقال على أنّه تحسين، أي يجب ألا يدخل تطبيقك في حالة "خطأ" إذا نجح تغيير نموذج المستند (DOM)، ولكن تعذّر الانتقال. من المفترض ألا يتعذّر الانتقال، ولكن في حال حدوث ذلك، يجب ألا يؤدي إلى إيقاف بقية تجربة المستخدم.
ولكي يتم التعامل مع عمليات الانتقال على أنّها تحسين، احرِص على عدم استخدام وعود الانتقال بطريقة تؤدي إلى تعذُّر عمل تطبيقك في حال فشل عملية الانتقال.
async function switchView(data) { // Fallback for browsers that don't support this API: if (!document.startViewTransition) { await updateTheDOM(data); return; } const transition = document.startViewTransition(async () => { await updateTheDOM(data); }); await transition.ready; document.documentElement.animate( { clipPath: [`inset(50%)`, `inset(0)`], }, { duration: 500, easing: 'ease-in', pseudoElement: '::view-transition-new(root)', } ); }
المشكلة في هذا المثال هي أنّ switchView()
سيتم رفضه إذا لم يتمكّن الانتقال من الوصول إلى الحالة ready
، ولكن هذا لا يعني أنّه تعذّر تبديل طريقة العرض. ربما تم تعديل نموذج المستند بنجاح، ولكن كانت هناك view-transition-name
مكرّرة، لذا تم تخطّي الانتقال.
بدلاً من ذلك:
async function switchView(data) { // Fallback for browsers that don't support this API: if (!document.startViewTransition) { await updateTheDOM(data); return; } const transition = document.startViewTransition(async () => { await updateTheDOM(data); }); animateFromMiddle(transition); await transition.updateCallbackDone; } async function animateFromMiddle(transition) { try { await transition.ready; document.documentElement.animate( { clipPath: [`inset(50%)`, `inset(0)`], }, { duration: 500, easing: 'ease-in', pseudoElement: '::view-transition-new(root)', } ); } catch (err) { // You might want to log this error, but it shouldn't break the app } }
يستخدم هذا المثال transition.updateCallbackDone
للانتظار إلى أن يتم تعديل نموذج المستند (DOM)، ولرفض العملية إذا تعذّر ذلك. لم يعُد switchView
يرفض إذا تعذّر الانتقال، بل يتم حلّه عند اكتمال تعديل نموذج المستند (DOM)، ويتم رفضه إذا تعذّر ذلك.
إذا كنت تريد أن يتم حلّ switchView
عندما يتم "تسوية" العرض الجديد، أي عندما يكتمل أي انتقال متحرك أو يتم تخطّيه إلى النهاية، استبدِل transition.updateCallbackDone
بـ transition.finished
.
ليس polyfill، ولكن…
ليس من السهل توفير هذه الميزة. ومع ذلك، تسهّل هذه الدالة المساعدة الأمور كثيرًا في المتصفّحات التي لا تتوافق مع عمليات انتقال العرض:
function transitionHelper({
skipTransition = false,
types = [],
update,
}) {
const unsupported = (error) => {
const updateCallbackDone = Promise.resolve(update()).then(() => {});
return {
ready: Promise.reject(Error(error)),
updateCallbackDone,
finished: updateCallbackDone,
skipTransition: () => {},
types,
};
}
if (skipTransition || !document.startViewTransition) {
return unsupported('View Transitions are not supported in this browser');
}
try {
const transition = document.startViewTransition({
update,
types,
});
return transition;
} catch (e) {
return unsupported('View Transitions with types are not supported in this browser');
}
}
ويمكن استخدامها على النحو التالي:
function spaNavigate(data) {
const types = isBackNavigation ? ['back-transition'] : [];
const transition = transitionHelper({
update() {
updateTheDOMSomehow(data);
},
types,
});
// …
}
في المتصفّحات التي لا تتوافق مع عمليات الانتقال بين طرق العرض، سيتم استدعاء updateDOM
، ولكن لن يكون هناك انتقال متحرك.
يمكنك أيضًا تقديم بعض classNames
لإضافتها إلى <html>
أثناء الانتقال، ما يسهّل تغيير الانتقال استنادًا إلى نوع التنقّل.
يمكنك أيضًا تمرير true
إلى skipTransition
إذا كنت لا تريد عرض رسم متحرك، حتى في المتصفحات التي تتوافق مع عمليات الانتقال بين طرق العرض. يكون هذا الإجراء مفيدًا إذا كان موقعك الإلكتروني يتيح للمستخدمين إيقاف التنقلات.
العمل باستخدام الأُطر
إذا كنت تعمل باستخدام مكتبة أو إطار عمل يجرّد تغييرات DOM، فإنّ الجزء الصعب هو معرفة متى يكتمل تغيير DOM. في ما يلي مجموعة من الأمثلة باستخدام الأداة المساعدة أعلاه في أُطر عمل مختلفة.
- React: المفتاح هنا هو
flushSync
، الذي يطبّق مجموعة من تغييرات الحالة بشكل متزامن. نعم، هناك تحذير كبير بشأن استخدام واجهة برمجة التطبيقات هذه، ولكن دان أبراموف يؤكّد لي أنّها مناسبة في هذه الحالة. كما هو معتاد مع React والرمز غير المتزامن، عند استخدام الوعود المختلفة التي تعرضهاstartViewTransition
، احرص على تشغيل الرمز مع الحالة الصحيحة. - Vue.js: المفتاح هنا هو
nextTick
، الذي يتم تنفيذه بعد تعديل DOM. - Svelte: تشبه Vue إلى حد كبير، ولكن طريقة انتظار التغيير التالي هي
tick
. - Lit: المفتاح هنا هو الوعد
this.updateComplete
داخل المكوّنات، والذي يتم تنفيذه بعد تعديل نموذج المستند (DOM). - Angular: المفتاح هنا هو
applicationRef.tick
، الذي يمحو تغييرات DOM المعلّقة. بدءًا من الإصدار 17 من Angular، يمكنك استخدامwithViewTransitions
الذي يأتي مع@angular/router
.
مرجع واجهة برمجة التطبيقات
const viewTransition = document.startViewTransition(update)
ابدأ
ViewTransition
جديدًا.
update
هي دالة يتم استدعاؤها بعد تسجيل الحالة الحالية للمستند.بعد ذلك، عندما يتم استيفاء الوعد الذي تم إرجاعه بواسطة
updateCallback
، يبدأ الانتقال في الإطار التالي. إذا تم رفض الوعد الذي تم إرجاعه من خلالupdateCallback
، سيتم إلغاء الانتقال.const viewTransition = document.startViewTransition({ update, types })
بدء
ViewTransition
جديد بالأنواع المحدّدةيتم استدعاء
update
بعد تسجيل الحالة الحالية للمستند.تضبط
types
الأنواع النشطة للانتقال عند تسجيل الانتقال أو تنفيذه. يكون فارغًا في البداية. يُرجى الاطّلاع علىviewTransition.types
أدناه لمزيد من المعلومات.
أعضاء مثيل ViewTransition
:
viewTransition.updateCallbackDone
وعد يتم تنفيذه عندما يتم تنفيذ الوعد الذي تم إرجاعه بواسطة
updateCallback
، أو يتم رفضه عندما يتم رفضه.تغلف واجهة برمجة التطبيقات View Transition API تغييرًا في نموذج المستند (DOM) وتنشئ عملية انتقال. ومع ذلك، في بعض الأحيان لا يهمّك نجاح أو تعذُّر حركة الانتقال، بل يهمّك فقط معرفة ما إذا كان سيحدث تغيير في نموذج المستند (DOM) ومتى سيحدث.
updateCallbackDone
مخصّص لحالة الاستخدام هذه.viewTransition.ready
وعد يتم تنفيذه عند إنشاء العناصر الزائفة للانتقال، وقبل بدء الحركة مباشرةً.
يتم رفضها إذا تعذّر بدء الانتقال. يمكن أن يكون ذلك بسبب خطأ في الإعداد، مثل تكرار
view-transition-name
، أو إذا عرضتupdateCallback
وعدًا مرفوضًا.ويكون ذلك مفيدًا عند تحريك العناصر الزائفة للانتقال باستخدام JavaScript.
viewTransition.finished
وعد يتم تنفيذه عندما تكون الحالة النهائية مرئية بالكامل للمستخدم ويمكنه التفاعل معها.
ولا يتم الرفض إلا إذا عرضت الدالة
updateCallback
وعدًا مرفوضًا، لأنّ ذلك يشير إلى عدم إنشاء الحالة النهائية.بخلاف ذلك، إذا تعذّر بدء عملية الانتقال أو تم تخطّيها أثناء عملية الانتقال، سيتم الوصول إلى الحالة النهائية، وبالتالي يتم استيفاء
finished
.viewTransition.types
عنصر يشبه
Set
يتضمّن أنواع انتقال العرض النشط. للتعديل على الإدخالات، استخدِم طُرق المثيلclear()
وadd()
وdelete()
.للاستجابة لنوع معيّن في CSS، استخدِم محدّد الفئة الزائفة
:active-view-transition-type(type)
في جذر الانتقال.يتم تنظيف الأنواع تلقائيًا عند انتهاء انتقال طريقة العرض.
viewTransition.skipTransition()
تخطّي جزء الحركة في الانتقال
لن يؤدي ذلك إلى تخطّي استدعاء
updateCallback
، لأنّ تغيير نموذج المستند (DOM) منفصل عن الانتقال.
النمط التلقائي ومرجع الانتقال
::view-transition
- العنصر الزائف الجذر الذي يملأ إطار العرض ويحتوي على كل
::view-transition-group
::view-transition-group
موضع مطلق
تنتقل السمتان
width
وheight
بين حالتَي "قبل" و"بعد".عمليات الانتقال
transform
بين المربّع الرباعي في مساحة منفذ العرض "قبل" والمربّع الرباعي في مساحة منفذ العرض "بعد"::view-transition-image-pair
يتم تحديد موضعها بشكل مطلق لملء المجموعة.
يجب أن يحتوي على
isolation: isolate
للحدّ من تأثيرmix-blend-mode
على طرق العرض القديمة والجديدة.::view-transition-new
و::view-transition-old
يتم تحديد موضعها بشكل مطلق في أعلى يسار العنصر الحاوي.
يملأ 100% من عرض المجموعة، ولكن له ارتفاع تلقائي، لذا سيحافظ على نسبة العرض إلى الارتفاع بدلاً من ملء المجموعة.
يحتوي على
mix-blend-mode: plus-lighter
للسماح بالانتقال السلس بين الأغاني.تنتقل طريقة العرض القديمة من
opacity: 1
إلىopacity: 0
. تنتقل طريقة العرض الجديدة منopacity: 0
إلىopacity: 1
.
الملاحظات
نحن نقدّر دائمًا ملاحظات المطوّرين. لإجراء ذلك، يُرجى الإبلاغ عن مشكلة لدى فريق عمل CSS على GitHub مع تقديم اقتراحات وأسئلة. ابدأ مشكلتك بـ [css-view-transitions]
.
في حال مواجهة خطأ، يُرجى إرسال تقرير عن خلل في Chromium بدلاً من ذلك.