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

تاریخ انتشار: 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 duplicate، یا اگر 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 را ثبت کنید .

،

تاریخ انتشار: 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) یک نمایش زنده از نمای جدید است. هر دو به‌عنوان «محتوای جایگزین شده» (مانند <img> ) CSS ارائه می‌شوند.

نمای قدیمی از 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;
}

با آن یک تغییر، محو شدن اکنون بسیار کند است:

متقاطع محو شدن طولانی. حداقل نسخه ی نمایشی منبع .

خوب، این هنوز هم چشمگیر نیست. در عوض ، کد زیر انتقال محور مشترک طراحی مواد را پیاده سازی می کند:

@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.
  • Firefox: پشتیبانی نمی شود.
  • پیش نمایش فناوری Safari: پشتیبانی شده.

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

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

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

از آنجا که انتقال View در بالای انیمیشن های CSS ساخته شده است ، پانل انیمیشن ها در Chrome Devtools برای اشکال زدایی انتقال بسیار عالی است.

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

اشکال زدایی مشاهده انتقال با Devtools Chrome.

عناصر انتقال نیازی به همان عنصر 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;
}

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

به روزرسانی های Async Dom ، و منتظر محتوا

بازگشت به تماس به .startViewTransition() می تواند یک وعده را برگرداند ، که امکان به روزرسانی های Async 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;
  }
}

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


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

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

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

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

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

برای این کار می توانید از انواع View Transition استفاده کنید ، که به شما امکان می دهد یک یا چند نوع را به یک انتقال فعال فعال اختصاص دهید. به عنوان مثال ، هنگام انتقال به صفحه بالاتر در یک دنباله صفحه بندی ، از نوع 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 استفاده می کند. سایر خصوصیات این شی در مرجع 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) تصویری از نمای قدیمی است ، در حالی که The ::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;
}

و بس!

انتقال فیلم ، کندتر. نسخه ی نمایشی حداقل منبع .

اکنون این ویدئو در طول انتقال پخش می شود.


ادغام با API ناوبری (و سایر چارچوبها)

مشاهده مشاهده به گونه ای مشخص شده است که می توانند با سایر چارچوب ها یا کتابخانه ها ادغام شوند. به عنوان مثال ، اگر برنامه تک صفحه ای شما (SPA) از روتر استفاده می کند ، می توانید مکانیسم به روزرسانی روتر را تنظیم کنید تا محتوا را با استفاده از یک انتقال مشاهده به روز کنید.

در قطعه کد زیر که از این نسخه نمایشی صفحه بندی گرفته شده است ، کنترل کننده رهگیری API ناوبری برای تماس با document.startViewTransition تنظیم می شود. 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 امکان پذیر باشد.

خوشبختانه ، شما می توانید با استفاده از 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)',
    }
  );
}

مشکل این مثال این است که اگر انتقال نتواند به یک حالت ready برسد ، switchView() رد خواهد کرد ، اما این بدان معنی نیست که این نمایش نتوانسته است تغییر کند. DOM ممکن است با موفقیت به روز شده باشد ، اما view-transition-name S وجود دارد ، بنابراین انتقال از آن رد شد.

در عوض:

انجام دهید
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> در طول انتقال ارائه دهید ، و این باعث می شود بسته به نوع ناوبری ، انتقال را آسان تر کنید .

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


کار با چارچوب ها

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

  • React - کلید در اینجا flushSync است ، که مجموعه ای از تغییرات حالت را همزمان اعمال می کند. بله ، یک هشدار بزرگ در مورد استفاده از آن API وجود دارد ، اما دن آبراموف به من اطمینان می دهد که در این مورد مناسب است. طبق معمول با کد React و Async ، هنگام استفاده از وعده های مختلفی که توسط startViewTransition بازگردانده شده است ، مواظب باشید که کد شما با حالت صحیح کار کند.
  • vue.js - کلید در اینجا nextTick است ، که پس از بروزرسانی DOM ، برآورده می شود.
  • svelte - بسیار شبیه به Vue ، اما روش انتظار برای تغییر بعدی tick است.
  • LIT - نکته اصلی در اینجا این است که this.updateComplete UPDATECOMPLETE در مؤلفه ها است که پس از بروزرسانی DOM ، برآورده می شود.
  • Angular - کلید در اینجا applicationRef.tick است ، که در انتظار تغییر DOM است. از نسخه زاویه ای 17 می توانید از 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 یک قول رد شده را بازگرداند.

این برای انیمیشن کردن عناصر شبه انتقال با 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] پیشوند کنید.

اگر به یک اشکال برسید ، به جای آن یک اشکال کروم را وارد کنید .