انتقال نمای همان سند برای برنامه های کاربردی تک صفحه ای

تاریخ انتشار: 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 متحرک کنید

پشتیبانی مرورگر

  • کروم: 125.
  • لبه: 125.
  • فایرفاکس: پشتیبانی نمی شود.
  • پیش نمایش فناوری سافاری: پشتیبانی می شود.

فرض کنید که یک انتقال دید با تعدادی کارت و همچنین یک عنوان در صفحه دارید. برای متحرک سازی همه کارت ها به جز عنوان، باید یک انتخابگر بنویسید که تک تک کارت ها را هدف قرار دهد.

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-transition-class ، animation-timing-function را برای همه کارت ها به جز کارت های اضافه یا حذف شده اعمال می کند.

اشکال زدایی انتقال

از آنجایی که انتقال‌های 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;
  }
}

با این حال، ترجیح برای "حرکت کاهش یافته" به این معنی نیست که کاربر هیچ حرکتی نمی خواهد. به جای قطعه قبلی، می توانید انیمیشن ظریف تری را انتخاب کنید، اما انیمیشنی که هنوز رابطه بین عناصر و جریان داده را بیان می کند.


سبک های انتقال چند نمایش را با انواع انتقال مشاهده مدیریت کنید

پشتیبانی مرورگر

  • کروم: 125.
  • لبه: 125.
  • فایرفاکس: پشتیبانی نمی شود.
  • سافاری: 18.

گاهی اوقات انتقال از یک نمای خاص به نمای دیگر باید دارای یک انتقال خاص باشد. برای مثال، هنگام رفتن به صفحه بعدی یا قبلی در یک دنباله صفحه بندی، ممکن است بخواهید بسته به اینکه به صفحه بالاتر یا صفحه پایین تر از دنباله می روید، محتوا را در جهت دیگری بلغزانید.

ضبط نسخه ی نمایشی صفحه بندی . بسته به اینکه به کدام صفحه می روید از انتقال های مختلفی استفاده می کند.

برای این کار می توانید از انواع انتقال مشاهده استفاده کنید که به شما امکان می دهد یک یا چند نوع را به یک انتقال نمای فعال اختصاص دهید. به عنوان مثال، هنگام انتقال به یک صفحه بالاتر در دنباله صفحه بندی از نوع 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 نیست. ضبط در سمت راست شامل بررسی می‌شود، بنابراین از انتقال نمایش دستی صرفنظر می‌شود زیرا مرورگر یک انتقال بصری ارائه می‌دهد.

مقایسه همان سایت بدون (چپ) و عرض (راست) بررسی 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 را ثبت کنید .