تاریخ انتشار: 17 اوت 2021، آخرین به روز رسانی: 25 سپتامبر 2024
هنگامی که یک انتقال view روی یک سند اجرا می شود، آن را انتقال نمای همان سند می نامند. این معمولاً در برنامه های تک صفحه ای (SPA) که جاوا اسکریپت برای به روز رسانی 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 برای سفارشی سازی انیمیشن ها استفاده کنید.
انتقال پیشفرض: Cross-fade
انتقال نمای پیشفرض یک حالت متقاطع است، بنابراین به عنوان یک مقدمه خوب برای API عمل میکند:
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()
فراخوانی می شود، API وضعیت فعلی صفحه را ثبت می کند. این شامل گرفتن یک عکس فوری نیز می شود.
پس از تکمیل، فراخوانی ارسال شده به .startViewTransition()
فراخوانی می شود. اینجاست که DOM تغییر می کند. سپس، API وضعیت جدید صفحه را ثبت می کند.
هنگامی که حالت جدید گرفته می شود، API یک درخت شبه عنصری مانند زیر می سازد:
::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 سفارشی کرد.
انتقال را سفارشی کنید
تمام عناصر شبه انتقال view را می توان با 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
می توان در شبه عناصر view transition برای اعمال همان قانون سبک استفاده کرد.
#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 بر روی انیمیشنهای CSS ساخته شدهاند، پانل انیمیشنها در Chrome DevTools برای اشکالزدایی انتقالها عالی است.
با استفاده از پانل انیمیشن ها ، می توانید انیمیشن بعدی را مکث کنید، سپس انیمیشن را به جلو و عقب بکشید. در طی این، شبه عناصر انتقال را می توان در پنل Elements یافت.
لازم نیست عناصر انتقالی همان عنصر DOM باشند
تاکنون view-transition-name
برای ایجاد عناصر انتقال جداگانه برای هدر و متن در هدر استفاده کرده ایم. اینها از نظر مفهومی همان عنصر قبل و بعد از تغییر DOM هستند، اما میتوانید در جایی که اینطور نیست، انتقال ایجاد کنید.
به عنوان مثال، جاسازی ویدیوی اصلی را می توان یک 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();
});
};
و نتیجه:
تصویر کوچک اکنون به تصویر اصلی تبدیل می شود. اگرچه آنها از نظر مفهومی (و به معنای واقعی کلمه) عناصر متفاوتی هستند، API انتقال آنها را به عنوان یک چیز در نظر می گیرد زیرا آنها 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)]);
});
با این حال، در برخی موارد بهتر است به طور کلی از تاخیر جلوگیری کنید و از محتوایی که قبلا دارید استفاده کنید.
از محتوایی که در حال حاضر دارید نهایت استفاده را ببرید
در موردی که تصویر کوچک به یک تصویر بزرگتر تبدیل می شود:
انتقال پیشفرض روی cross-fade است، به این معنی که تصویر کوچک میتواند با یک تصویر کامل که هنوز بارگذاری نشده است، متقاطع محو شود.
یکی از راههای رسیدگی به این موضوع این است که قبل از شروع انتقال صبر کنید تا تصویر کامل بارگذاری شود. در حالت ایدهآل، این کار قبل از فراخوانی .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
روی یک عنصر فقط برای انتقال view با آن نوع استفاده کنید.
/* 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 {
…
}
سبک های انتقال چند نما را با نام کلاس در ریشه انتقال view مدیریت کنید
گاهی اوقات انتقال از یک نوع نمای خاص به نوع دیگر باید یک انتقال خاص داشته باشد. یا، ناوبری "بازگشت" باید با ناوبری "به جلو" متفاوت باشد.
قبل از انواع انتقال، راه رسیدگی به این موارد این بود که به طور موقت یک نام کلاس روی ریشه انتقال تنظیم شود. هنگام فراخوانی document.startViewTransition
، این ریشه انتقال عنصر <html>
است که با استفاده از document.documentElement
در جاوا اسکریپت قابل دسترسی است:
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
استفاده میکند، قولی که پس از رسیدن انتقال به حالت پایانی خود برطرف میشود. سایر خصوصیات این شی در مرجع API پوشش داده شده است.
اکنون می توانید از نام کلاس در 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 (و سایر چارچوب ها)
انتقالهای View به گونهای مشخص میشوند که میتوانند با چارچوبها یا کتابخانههای دیگر ادغام شوند. به عنوان مثال، اگر برنامه تک صفحه ای شما (SPA) از روتر استفاده می کند، می توانید مکانیسم به روز رسانی روتر را برای به روز رسانی محتوا با استفاده از یک انتقال مشاهده تنظیم کنید.
در قطعه کد زیر که از این نسخه نمایشی صفحهبندی گرفته شده است، کنترلکننده رهگیری Navigation API طوری تنظیم میشود که در صورت پشتیبانی از انتقال view، 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);
});
}
});
});
برخی از مرورگرها، اما نه همه، زمانی که کاربر یک حرکت تند کشیدن برای پیمایش انجام می دهد، انتقال خود را ارائه می دهند. در آن صورت، شما نباید تغییر دیدگاه خود را فعال کنید، زیرا منجر به تجربه کاربری ضعیف یا گیج کننده می شود. کاربر دو انتقال را مشاهده می کند - یکی توسط مرورگر و دیگری توسط شما - که به صورت متوالی اجرا می شوند.
بنابراین، توصیه می شود زمانی که مرورگر انتقال بصری خود را ارائه کرده است، از شروع انتقال view جلوگیری کنید. برای رسیدن به این هدف، مقدار ویژگی hasUAVisualTransition
نمونه NavigateEvent
را بررسی کنید. زمانی که مرورگر یک انتقال بصری ارائه کرده باشد، این ویژگی روی true
تنظیم می شود. این ویژگی hasUIVisualTransition
در نمونه های PopStateEvent
نیز وجود دارد.
در قطعه قبلی، چکی که تعیین میکند آیا انتقال view اجرا شود یا خیر، این ویژگی را در نظر میگیرد. هنگامی که هیچ پشتیبانی برای انتقال نمای همان سند وجود ندارد یا زمانی که مرورگر قبلاً انتقال خود را ارائه کرده است، انتقال view نادیده گرفته می شود.
if (!document.startViewTransition || e.hasUAVisualTransition) {
setContent(newContent);
return;
}
در ضبط زیر، کاربر با کشیدن انگشت به صفحه قبلی باز می گردد. عکسبرداری در سمت چپ شامل چکی برای پرچم hasUAVisualTransition
نیست. ضبط در سمت راست شامل بررسی میشود، بنابراین از انتقال نمایش دستی صرفنظر میشود زیرا مرورگر یک انتقال بصری ارائه میدهد.
متحرک سازی با جاوا اسکریپت
تا کنون، تمام انتقال ها با استفاده از 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
استفاده می کند، وعده ای که پس از ایجاد موفقیت آمیز شبه عناصر انتقال حل می شود. سایر خصوصیات این شی در مرجع API پوشش داده شده است.
انتقال به عنوان یک افزایش
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 تغییر نکرده است. ممکن است DOM با موفقیت بهروزرسانی شده باشد، اما 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
جایگزین کنید.
پلی پر نیست، اما…
این یک ویژگی آسان برای پلی پر کردن نیست. با این حال، این تابع کمکی کارها را در مرورگرهایی که از انتقال view پشتیبانی نمیکنند بسیار آسانتر میکند:
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,
});
// …
}
در مرورگرهایی که از انتقال view پشتیبانی نمی کنند، updateDOM
همچنان فراخوانی می شود، اما انتقال متحرک وجود نخواهد داشت.
همچنین میتوانید تعدادی classNames
برای افزودن به <html>
در طول انتقال ارائه دهید، که تغییر انتقال را بسته به نوع پیمایش آسانتر میکند.
همچنین اگر انیمیشنی نمیخواهید، حتی در مرورگرهایی که از انتقال view پشتیبانی میکنند، میتوانید به skipTransition
نیز true
بفرستید. اگر سایت شما ترجیحات کاربر برای غیرفعال کردن انتقال ها را داشته باشد، مفید است.
کار با فریمورک ها
اگر با کتابخانه یا چارچوبی کار میکنید که تغییرات DOM را انتزاعی میکند، بخش مشکل این است که بدانید چه زمانی تغییر DOM کامل شده است. در اینجا مجموعه ای از مثال ها با استفاده از کمک کننده بالا در چارچوب های مختلف آورده شده است.
- React— کلید در اینجا
flushSync
است که مجموعه ای از تغییرات حالت را به صورت همزمان اعمال می کند. بله، یک هشدار بزرگ در مورد استفاده از آن API وجود دارد، اما Dan Abramov به من اطمینان می دهد که در این مورد مناسب است. طبق معمول کدهای React و async، هنگام استفاده از وعدههای مختلف بازگردانده شده توسطstartViewTransition
، مراقب باشید که کد شما با وضعیت صحیح اجرا شود. - Vue.js — کلید اینجا
nextTick
است که پس از بهروزرسانی DOM تکمیل میشود. - Svelte - بسیار شبیه به Vue است، اما روش انتظار برای تغییر بعدی
tick
است. - روشن - کلید در اینجا وعده
this.updateComplete
درون کامپوننت ها است که پس از به روز رسانی DOM محقق می شود. - Angular — کلید اینجا
applicationRef.tick
است که تغییرات در انتظار DOM را پاک می کند. از نسخه 17 Angular میتوانیدwithViewTransitions
که با@angular/router
ارائه میشود استفاده کنید .
مرجع API
-
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
یک وعده رد شده را برمی گرداند.این برای متحرک سازی شبه عناصر انتقال با جاوا اسکریپت مفید است.
-
viewTransition.finished
وعده ای که زمانی محقق می شود که حالت پایان به طور کامل برای کاربر قابل مشاهده و تعاملی باشد.
فقط در صورتی رد می کند که
updateCallback
یک وعده رد شده را برگرداند، زیرا این نشان می دهد که حالت پایان ایجاد نشده است.در غیر این صورت، اگر انتقالی شروع نشود، یا در طول انتقال از آن صرفنظر شود، همچنان به حالت پایان رسیده است، بنابراین
finished
انجام میشود.-
viewTransition.types
یک شیء شبیه به
Set
که انواع انتقال نمای فعال را در خود نگه می دارد. برای دستکاری ورودیها، از روشهای نمونهclear()
،add()
وdelete()
استفاده کنید.برای پاسخ به یک نوع خاص در CSS، از انتخابگر شبه کلاس
:active-view-transition-type(type)
در ریشه انتقال استفاده کنید.انواع به طور خودکار پس از اتمام انتقال نمایش پاک می شوند.
-
viewTransition.skipTransition()
بخش انیمیشن انتقال را رد کنید.
این از فراخوانی
updateCallback
صرفنظر نمی کند، زیرا تغییر DOM جدا از انتقال است.
مرجع سبک و انتقال پیشفرض
-
::view-transition
- شبه عنصر ریشه که viewport را پر می کند و شامل هر
::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 را ثبت کنید .