Lorsqu'une transition d'affichage s'exécute sur un seul document, elle est appelée transition de vue sur le même document. C'est généralement le cas dans les applications monopages (SPA) où JavaScript est utilisé pour mettre à jour le DOM. Les transitions d'affichage du même document sont compatibles avec Chrome à partir de Chrome 111.
Pour déclencher une transition vers le même document, appelez 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());
}
Lorsqu'il est appelé, le navigateur capture automatiquement des instantanés de tous les éléments sur lesquels une propriété CSS view-transition-name
est déclarée.
Il exécute ensuite le rappel transmis qui met à jour le DOM, après quoi il prend des instantanés du nouvel état.
Ces instantanés sont ensuite organisés dans une arborescence de pseudo-éléments et animés grâce à la puissance des animations CSS. Des paires d'instantanés de l'ancien et du nouvel état passent en douceur de leur ancienne position et taille à leur nouvel emplacement, tandis que leur contenu passe en fondu. Si vous le souhaitez, vous pouvez utiliser du code CSS pour personnaliser les animations.
La transition par défaut: le fondu enchaîné
La transition de la vue par défaut est un fondu enchaîné. Il s'agit donc d'une belle introduction à l'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));
}
Où updateTheDOMSomehow
fait passer le DOM au nouvel état. Vous pouvez le faire comme vous le souhaitez. Par exemple, vous pouvez ajouter ou supprimer des éléments, modifier les noms des classes ou modifier les styles.
Et voilà, les pages en fondu enchaîné:
Un fondu enchaîné n'est pas si impressionnant. Heureusement, les transitions peuvent être personnalisées, mais vous devez d'abord comprendre le fonctionnement de ce fondu enchaîné standard.
Fonctionnement de ces transitions
Mettons à jour l'exemple de code précédent.
document.startViewTransition(() => updateTheDOMSomehow(data));
Lorsque .startViewTransition()
est appelé, l'API capture l'état actuel de la page. Cela inclut la prise d'instantanés.
Une fois l'opération terminée, le rappel transmis à .startViewTransition()
est appelé. C'est là que le DOM est modifié. Ensuite, l'API capture le nouvel état de la page.
Une fois le nouvel état capturé, l'API construit une arborescence de pseudo-élément comme celle-ci:
::view-transition
└─ ::view-transition-group(root)
└─ ::view-transition-image-pair(root)
├─ ::view-transition-old(root)
└─ ::view-transition-new(root)
Le ::view-transition
apparaît en superposition, par-dessus tout le reste de la page. Cela peut être utile si vous souhaitez définir une couleur d'arrière-plan pour la transition.
::view-transition-old(root)
est une capture d'écran de l'ancienne vue, tandis que ::view-transition-new(root)
est une représentation en direct de la nouvelle vue. Les deux s'affichent en tant que contenu de remplacement CSS (comme une <img>
).
L'ancienne vue s'anime de opacity: 1
à opacity: 0
, tandis que la nouvelle s'anime de opacity: 0
à opacity: 1
pour créer un fondu enchaîné.
Toutes les animations sont réalisées à l'aide d'animations CSS. Vous pouvez donc les personnaliser avec CSS.
Personnaliser la transition
Tous les pseudo-éléments de transition d'affichage peuvent être ciblés avec CSS. Étant donné que les animations sont définies à l'aide de CSS, vous pouvez les modifier à l'aide des propriétés d'animation CSS existantes. Exemple :
::view-transition-old(root),
::view-transition-new(root) {
animation-duration: 5s;
}
Avec ce seul changement, le fondu est maintenant très lent:
Ce n'est toujours pas impressionnant. À la place, le code suivant implémente la transition "Axe partagé" de 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;
}
Et voici le résultat:
Effectuer une transition avec plusieurs éléments
Dans la démonstration précédente, toute la page est impliquée dans la transition de l'axe partagé. Cela fonctionne pour la majeure partie de la page, mais cela ne semble pas tout à fait correct pour l'en-tête, car il glisse vers l'extérieur pour revenir à l'intérieur.
Pour éviter cela, vous pouvez extraire l'en-tête du reste de la page afin de l'animer séparément. Pour ce faire, attribuez un view-transition-name
à l'élément.
.main-header {
view-transition-name: main-header;
}
Vous pouvez définir la valeur de view-transition-name
comme bon vous semble (sauf pour none
, qui signifie qu'il n'y a pas de nom de transition). Il permet d'identifier de manière unique l'élément lors de la transition.
Et le résultat de cela:
Maintenant, l'en-tête reste en place et se fonde enchaîné.
Cette déclaration CSS a entraîné la modification de l'arborescence des pseudo-éléments:
::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)
Il existe maintenant deux groupes de transition. une pour l'en-tête et une autre pour le reste. Celles-ci peuvent être ciblées indépendamment avec le CSS et disposer de différentes transitions. Toutefois, dans ce cas, main-header
conserve la transition par défaut, qui est un fondu enchaîné.
La transition par défaut n'est pas un simple fondu enchaîné. ::view-transition-group
effectue également des transitions:
- Position et transformation (à l'aide d'un
transform
) - Largeur
- Hauteur
Cela n'a pas eu d'importance jusqu'à présent, car l'en-tête a la même taille et se positionne des deux côtés de la modification DOM. Mais vous pouvez également extraire le texte de l'en-tête:
.main-header-text {
view-transition-name: main-header-text;
width: fit-content;
}
fit-content
permet d'ajuster l'élément à la taille du texte au lieu de l'étirer jusqu'à la largeur restante. Sans cela, la flèche de retour réduit la taille de l'élément de texte de l'en-tête au lieu de la même taille dans les deux pages.
Nous avons maintenant trois parties à jouer:
::view-transition
├─ ::view-transition-group(root)
│ └─ …
├─ ::view-transition-group(main-header)
│ └─ …
└─ ::view-transition-group(main-header-text)
└─ …
Mais encore une fois, il suffit d'utiliser les valeurs par défaut:
Maintenant, le texte du titre fait un glisser un peu satisfaisant pour faire de la place pour le bouton Retour.
Animer plusieurs pseudo-éléments de la même manière avec view-transition-class
Navigateurs pris en charge
- 125
- 125
- x
- x
Supposons que vous ayez une transition d'affichage avec un grand nombre de fiches, mais aussi un titre sur la page. Pour animer toutes les fiches à l'exception du titre, vous devez écrire un sélecteur qui cible chaque fiche.
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);
}
Vous avez 20 éléments ? Vous devez écrire 20 sélecteurs. Vous souhaitez ajouter un élément ? Ensuite, vous devez aussi agrandir le sélecteur qui applique les styles d'animation. Pas exactement évolutive.
Le view-transition-class
peut être utilisé dans les pseudo-éléments de transition de vue pour appliquer la même règle de style.
#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);
}
L'exemple de fiches suivant exploite l'extrait CSS précédent. Le même moment est appliqué à toutes les fiches, y compris celles qui ont été ajoutées, avec un seul sélecteur: html::view-transition-group(.card)
.
view-transition-class
applique le même animation-timing-function
à toutes les cartes, à l'exception de celles ajoutées ou supprimées.Déboguer les transitions
Étant donné que les transitions d'affichage sont basées sur les animations CSS, le panneau Animations des outils pour les développeurs Chrome est idéal pour déboguer les transitions.
À l'aide du panneau Animations, vous pouvez mettre en pause l'animation suivante, puis faire défiler l'animation vers l'avant et l'arrière. Au cours de cette opération, les pseudo-éléments de transition sont disponibles dans le panneau Éléments.
Les éléments de transition ne doivent pas nécessairement correspondre au même élément DOM.
Jusqu'à présent, nous avons utilisé view-transition-name
afin de créer des éléments de transition distincts pour l'en-tête et pour le texte qu'il contient. Il s'agit des mêmes éléments avant et après le changement DOM, mais vous pouvez créer des transitions lorsque ce n'est pas le cas.
Par exemple, l'élément intégré de la vidéo principale peut recevoir un view-transition-name
:
.full-embed {
view-transition-name: full-embed;
}
Ensuite, lorsque l'utilisateur clique sur la miniature, le même view-transition-name
peut lui être attribué, juste pour la durée de la transition:
thumbnail.onclick = async () => {
thumbnail.style.viewTransitionName = 'full-embed';
document.startViewTransition(() => {
thumbnail.style.viewTransitionName = '';
updateTheDOMSomehow();
});
};
Et le résultat:
La miniature passe maintenant à l'image principale. Même s'ils sont conceptuellement (et littéralement) différents, l'API de transition les traite comme une seule et même chose, car ils partagent le même view-transition-name
.
Le code réel de cette transition est un peu plus compliqué que l'exemple précédent, car il gère également la transition vers la page de vignettes. Consultez la source pour voir l'implémentation complète.
Transitions d'entrée et de sortie personnalisées
Prenons l'exemple suivant:
La barre latérale fait partie de la transition:
.sidebar {
view-transition-name: sidebar;
}
Mais, contrairement à l'en-tête de l'exemple précédent, la barre latérale n'apparaît pas sur toutes les pages. Si les deux états comportent une barre latérale, les pseudo-éléments de transition se présentent comme suit:
::view-transition
├─ …other transition groups…
└─ ::view-transition-group(sidebar)
└─ ::view-transition-image-pair(sidebar)
├─ ::view-transition-old(sidebar)
└─ ::view-transition-new(sidebar)
Toutefois, si la barre latérale se trouve uniquement sur la nouvelle page, le pseudo-élément ::view-transition-old(sidebar)
ne sera pas présent. Puisqu'il n'y a pas de « ancien » pour la barre latérale, la paire d'images ne comportera que ::view-transition-new(sidebar)
. De même, si la barre latérale se trouve uniquement sur l'ancienne page, la paire d'images comportera uniquement un ::view-transition-old(sidebar)
.
Dans la démonstration précédente, la transition de la barre latérale varie selon que la barre change de mode (entrée, sortie ou présence dans les deux états). Elle apparaît en glissant depuis la droite, puis en fondu, en glissant vers la droite et en fondu, et elle reste en place lorsqu'elle est présente dans les deux états.
Pour créer des transitions d'entrée et de sortie spécifiques, vous pouvez utiliser la pseudo-classe :only-child
afin de cibler l'ancien ou le nouveau pseudo-éléments lorsqu'il est le seul enfant de la paire d'images:
/* 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;
}
Dans ce cas, il n'y a pas de transition spécifique lorsque la barre latérale est présente dans les deux états, car la valeur par défaut est parfaite.
Mises à jour DOM asynchrones et en attente de contenu
Le rappel transmis à .startViewTransition()
peut renvoyer une promesse, ce qui permet d'effectuer des mises à jour DOM asynchrones et d'attendre que le contenu important soit prêt.
document.startViewTransition(async () => {
await something;
await updateTheDOMSomehow();
await somethingElse;
});
La transition ne commencera pas tant que la promesse n'aura pas été effectuée. Pendant cette période, la page est bloquée. Vous devez donc réduire au maximum les délais. Plus précisément, les extractions réseau doivent être effectuées avant d'appeler .startViewTransition()
, lorsque la page reste entièrement interactive, plutôt que de les faire dans le cadre du rappel .startViewTransition()
.
Si vous décidez d'attendre que les images ou les polices soient prêtes, veillez à utiliser un délai avant expiration agressif:
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)]);
});
Toutefois, dans certains cas, il est préférable d'éviter tout retard et d'utiliser le contenu dont vous disposez déjà.
Exploitez tout le potentiel de vos contenus existants
Si la miniature passe à une image plus grande:
La transition par défaut est un fondu enchaîné, ce qui signifie que le fondu enchaîné de la miniature est peut-être dû à une image complète qui n'a pas encore été chargée.
Pour gérer ce problème, vous pouvez attendre que l'image soit entièrement chargée avant de lancer la transition. Idéalement, cette opération devrait être effectuée avant d'appeler .startViewTransition()
, afin que la page reste interactive et qu'une icône de chargement puisse s'afficher pour indiquer à l'utilisateur que les éléments sont en cours de chargement. Mais dans ce cas, il existe
une meilleure solution:
::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;
}
La vignette ne disparaît pas, elle reste simplement sous l'image entière. Cela signifie que si la nouvelle vue n'a pas été chargée, la vignette reste visible pendant la transition. Cela signifie que la transition peut commencer immédiatement et que l'image complète peut se charger à son tour.
Cela ne fonctionnerait pas si la nouvelle vue mettait en avant la transparence, mais dans le cas présent, nous savons que ce n'est pas le cas. Nous pouvons donc effectuer cette optimisation.
Gérer les modifications de format
Pratiquement, jusqu'à présent, toutes les transitions ont été effectuées vers des éléments ayant le même format, mais ce ne sera pas toujours le cas. Que se passe-t-il si la miniature est au format 1:1 et que l'image principale est au format 16:9 ?
Dans la transition par défaut, le groupe s'anime de la taille "avant" à la taille "après". L'ancienne et la nouvelle vue ont une largeur de 100% du groupe, et la hauteur est automatique, ce qui signifie qu'elles conservent leurs proportions, quelle que soit la taille du groupe.
Cette valeur par défaut est correcte, mais elle ne correspond pas à celle souhaitée dans ce cas. Exemple :
::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;
}
Cela signifie que la vignette reste au centre de l'élément lorsque la largeur augmente, mais que l'image entière est "recadrée" lors du passage de 1:1 à 16:9.
Pour en savoir plus, consultez Afficher les transitions: gérer les modifications de format.
Utiliser des requêtes média pour modifier les transitions en fonction de l'état de l'appareil
Vous pouvez utiliser des transitions différentes sur mobile et sur ordinateur, comme dans cet exemple, qui affiche une diapositive complète sur le côté mobile et une diapositive plus subtile sur un ordinateur de bureau:
Pour ce faire, utilisez des requêtes média standards:
/* 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;
}
}
Vous pouvez également modifier les éléments auxquels vous attribuez une view-transition-name
en fonction des requêtes média correspondantes.
Réagir à la "réduction du mouvement" préférence
Les utilisateurs peuvent indiquer qu'ils préfèrent les mouvements réduits via leur système d'exploitation, et cette préférence est exposée dans CSS.
Vous pouvez choisir d'empêcher toute transition pour ces utilisateurs:
@media (prefers-reduced-motion) {
::view-transition-group(*),
::view-transition-old(*),
::view-transition-new(*) {
animation: none !important;
}
}
Cependant, une préférence pour « mouvements réduits » ne signifie pas que l'utilisateur ne veut aucun mouvement. Au lieu de l'extrait précédent, vous pouvez choisir une animation plus subtile, mais qui exprime tout de même la relation entre les éléments et le flux de données.
Gérer plusieurs styles de transition de vue avec des types de transition de vue
Navigateurs pris en charge
- 125
- 125
- x
- x
Parfois, une transition d'une vue particulière à une autre doit être spécifiquement adaptée. Par exemple, lorsque vous passez à la page suivante ou précédente dans une séquence de pagination, vous pouvez faire glisser le contenu dans une direction différente selon que vous accédez à une page située plus bas ou plus haut dans la séquence.
Pour ce faire, vous pouvez utiliser des types de transition "Vue", qui vous permettent d'attribuer un ou plusieurs types à une transition Active View. Par exemple, lorsque vous passez à une page supérieure dans une séquence de pagination, utilisez le type forwards
et lorsque vous passez à une page inférieure, utilisez le type backwards
. Ces types ne sont actifs que lors de la capture ou de l'exécution d'une transition, et ils peuvent être personnalisés via CSS afin d'utiliser des animations différentes.
Pour utiliser des types dans une transition de vue d'un même document, transmettez types
à la méthode startViewTransition
. Pour ce faire, document.startViewTransition
accepte également un objet: update
est la fonction de rappel qui met à jour le DOM, et types
est un tableau contenant les types.
const direction = determineBackwardsOrForwards();
const t = document.startViewTransition({
update: updateTheDOMSomehow,
types: ['slide', direction],
});
Pour répondre à ces types, utilisez le sélecteur :active-view-transition-type()
. Transmettez le type
que vous souhaitez cibler au sélecteur. Cela vous permet de séparer les styles de plusieurs transitions de vue les uns des autres, sans que les déclarations de l'une n'interfèrent avec celles de l'autre.
Étant donné que les types ne s'appliquent que lors de la capture ou de l'exécution de la transition, vous pouvez utiliser le sélecteur pour définir (ou non) une view-transition-name
sur un élément uniquement pour la transition de vue avec ce type.
/* Determine what gets captured when the type is forwards or backwards */
html:active-view-transition-type(forwards, backwards) {
:root {
view-transition-name: none;
}
article {
view-transition-name: content;
}
.pagination {
view-transition-name: pagination;
}
}
/* Animation styles for forwards type only */
html:active-view-transition-type(forwards) {
&::view-transition-old(content) {
animation-name: slide-out-to-left;
}
&::view-transition-new(content) {
animation-name: slide-in-from-right;
}
}
/* Animation styles for backwards type only */
html:active-view-transition-type(backwards) {
&::view-transition-old(content) {
animation-name: slide-out-to-right;
}
&::view-transition-new(content) {
animation-name: slide-in-from-left;
}
}
/* Animation styles for reload type only (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;
}
}
Dans la démonstration de la pagination suivante, le contenu de la page glisse vers l'avant ou vers l'arrière en fonction du numéro de la page à laquelle vous accédez. Les types sont déterminés au moment du clic sur lequel ils sont transmis à document.startViewTransition
.
Pour cibler n'importe quelle transition Active View, quel que soit son type, vous pouvez utiliser le sélecteur de pseudo-classe :active-view-transition
à la place.
html:active-view-transition {
…
}
Gérer plusieurs styles de transition de vue avec un nom de classe à la racine de la transition de vue
Parfois, une transition d'un type de vue particulier à un autre doit être spécifiquement adaptée. Ou, un "retour" la navigation doit être différente la navigation.
Avant les types de transition, la façon de gérer ces cas de figure consistait à définir temporairement un nom de classe à la racine de la transition. Lors de l'appel de document.startViewTransition
, cette racine de transition correspond à l'élément <html>
, accessible à l'aide de document.documentElement
en 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');
}
Pour supprimer les classes une fois la transition terminée, cet exemple utilise transition.finished
, une promesse qui se résout une fois la transition atteint son état final. Les autres propriétés de cet objet sont présentées dans la documentation de référence de l'API.
Vous pouvez maintenant utiliser ce nom de classe dans votre CSS pour modifier la transition:
/* '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;
}
Comme pour les requêtes média, la présence de ces classes peut également être utilisée pour modifier les éléments qui obtiennent un view-transition-name
.
Exécuter des transitions sans figer les autres animations
Regardez cette démo d’un poste de transition vidéo:
Avez-vous remarqué quelque chose de mal ? Ne vous inquiétez pas si ce n’est pas le cas. Ici, elle est tout de suite ralentie:
Pendant la transition, la vidéo semble se figer, puis la version en cours de lecture apparaît en fondu. En effet, ::view-transition-old(video)
est une capture d'écran de l'ancienne vue, tandis que ::view-transition-new(video)
est une image active de la nouvelle vue.
Vous pouvez résoudre ce problème, mais commencez par vous demander si cela vaut la peine d'être corrigé. Si vous n'avez pas vu le "problème" lorsque la transition se jouait à sa vitesse normale, je ne prendrais pas la peine de la changer.
Si vous voulez vraiment corriger le problème, n'affichez pas ::view-transition-old(video)
. passez directement à ::view-transition-new(video)
. Pour ce faire, remplacez les styles et animations par défaut:
::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;
}
Et voilà !
La vidéo est alors lue tout au long de la transition.
Animation avec JavaScript
Jusqu'à présent, toutes les transitions ont été définies à l'aide de CSS, mais il arrive que le CSS ne suffit pas:
Quelques éléments de cette transition ne peuvent pas être effectués seul avec le CSS:
- L'animation commence à l'emplacement du clic.
- L'animation se termine avec le cercle ayant un rayon jusqu'à l'angle le plus éloigné. Toutefois, cela devrait être possible avec CSS à l'avenir.
Heureusement, vous pouvez créer des transitions à l'aide de l'API Web Animation.
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)',
}
);
});
}
Cet exemple utilise transition.ready
, une promesse qui se résout une fois les pseudo-éléments de transition créés. Les autres propriétés de cet objet sont décrites dans la documentation de référence de l'API.
Les transitions comme amélioration
L'API View Transition est conçue pour encapsuler une modification DOM et créer une transition pour celle-ci. Cependant, la transition doit être considérée comme une amélioration, car votre application ne doit pas saisir d'erreur. si la modification DOM aboutit, mais que la transition échoue. Dans l'idéal, la transition ne devrait pas échouer. Si c'est le cas, cela ne devrait pas affecter le reste de l'expérience utilisateur.
Afin de traiter les transitions comme une amélioration, veillez à ne pas utiliser les promesses de transition d'une manière qui pourrait entraîner le dysfonctionnement de votre application en cas d'échec de la transition.
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)', } ); }
Le problème avec cet exemple est que switchView()
rejette un état si la transition ne peut pas atteindre un état ready
, mais cela ne signifie pas que le changement de vue a échoué. Le DOM a peut-être bien été mis à jour, mais la transition a été ignorée, car il y avait des view-transition-name
en double.
Au lieu de cela :
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 } }
Cet exemple utilise transition.updateCallbackDone
pour attendre la mise à jour DOM et la refuser en cas d'échec. switchView
ne refuse plus la transition en cas d'échec de la transition, se résout une fois la mise à jour DOM terminée et la refuse en cas d'échec.
Si vous souhaitez que switchView
soit résolu lorsque la nouvelle vue est "réglée", comme c'est le cas pour toute transition animée, remplacez transition.updateCallbackDone
par transition.finished
.
Ce n'est pas un polyfill, mais...
Ce n'est pas une fonctionnalité facile à émuler. Toutefois, cette fonction d'assistance facilite grandement les opérations dans les navigateurs qui ne prennent pas en charge les transitions de vue:
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');
}
}
Et il peut être utilisé comme ceci:
function spaNavigate(data) {
const types = isBackNavigation ? ['back-transition'] : [];
const transition = transitionHelper({
update() {
updateTheDOMSomehow(data);
},
types,
});
// …
}
Dans les navigateurs qui ne sont pas compatibles avec les transitions de vue, updateDOM
sera toujours appelé, mais aucune transition ne sera animée.
Vous pouvez également fournir des classNames
à ajouter à <html>
pendant la transition, ce qui vous permettra de modifier plus facilement la transition en fonction du type de navigation.
Vous pouvez également transmettre true
à skipTransition
si vous ne souhaitez pas d'animation, même dans les navigateurs compatibles avec les transitions de vue. Cette option est utile si les utilisateurs de votre site préfèrent désactiver les transitions.
Travailler avec des cadres
Si vous travaillez avec une bibliothèque ou un framework qui élimine les modifications DOM, le plus difficile est de savoir quand ces modifications sont terminées. Voici un ensemble d'exemples, qui utilisent l'outil d'aide ci-dessus, dans différents frameworks.
- React : la clé ici est
flushSync
, qui applique un ensemble de changements d'état de manière synchrone. Oui, l'utilisation de cette API nécessite un avertissement important, mais Dan Abramov assure qu'elle est appropriée dans ce cas. Comme d'habitude avec React et le code asynchrone, lorsque vous utilisez les différentes promesses renvoyées parstartViewTransition
, assurez-vous que votre code s'exécute avec l'état approprié. - Vue.js : il s'agit ici de
nextTick
, qui s'exécute une fois le DOM mis à jour. - Svelte : très semblable à Vue, mais la méthode pour attendre le prochain changement est
tick
. - Lit : la clé ici est la promesse
this.updateComplete
dans les composants, qui s'exécute une fois le DOM mis à jour. - Angular : la clé ici est
applicationRef.tick
, qui supprime les modifications DOM en attente. À partir de la version 17 d'Angular, vous pouvez utiliser lewithViewTransitions
fourni avec@angular/router
.
Documentation de référence de l'API
const viewTransition = document.startViewTransition(update)
Commencez une nouvelle
ViewTransition
.update
est une fonction appelée une fois que l'état actuel du document a été capturé.Ensuite, lorsque la promesse renvoyée par
updateCallback
est exécutée, la transition commence dans le frame suivant. Si la promesse renvoyée parupdateCallback
est refusée, la transition est abandonnée.const viewTransition = document.startViewTransition({ update, types })
Démarrer un nouveau
ViewTransition
avec les types spécifiésupdate
est appelé une fois l'état actuel du document capturé.types
définit les types actifs pour la transition lors de la capture ou de l'exécution de la transition. Il est initialement vide. Pour en savoir plus, consultez la sectionviewTransition.types
ci-dessous.
Membres de l'instance de ViewTransition
:
viewTransition.updateCallbackDone
Promesse qui est exécutée lorsque la promesse renvoyée par
updateCallback
est exécutée, ou rejetée lorsqu'elle est refusée.L'API View Transition encapsule une modification DOM et crée une transition. Cependant, il arrive parfois que la réussite ou l'échec de l'animation de transition ne vous préoccupe pas. Il vous suffit de savoir si et quand le changement DOM se produit.
updateCallbackDone
est destiné à ce cas d'utilisation.viewTransition.ready
Promesse qui est exécutée une fois que les pseudo-éléments de la transition sont créés et que l'animation est sur le point de commencer.
Il est refusé si la transition ne peut pas commencer. Cela peut être dû à une mauvaise configuration, telle que des
view-transition-name
en double, ou siupdateCallback
renvoie une promesse refusée.Cela s'avère utile pour animer les pseudo-éléments de transition avec JavaScript.
viewTransition.finished
Promesse qui se tient une fois que l'état final est entièrement visible et interactif pour l'utilisateur.
Elle ne rejette que si
updateCallback
renvoie une promesse refusée, car cela indique que l'état final n'a pas été créé.Sinon, si une transition ne démarre pas ou est ignorée pendant la transition, l'état final est quand même atteint.
finished
est donc exécuté.viewTransition.types
Objet de type
Set
contenant les types de transition Active View. Pour manipuler les entrées, utilisez ses méthodes d'instanceclear()
,add()
etdelete()
.Pour répondre à un type spécifique en CSS, utilisez le sélecteur de pseudo-classe
:active-view-transition-type(type)
sur la racine de la transition.Les types sont automatiquement nettoyés une fois la transition d'affichage terminée.
viewTransition.skipTransition()
Ignorez la partie animation de la transition.
L'appel de
updateCallback
ne sera pas ignoré, car la modification DOM est distincte de la transition.
Référence de style et de transition par défaut
::view-transition
- Pseu-élément racine qui remplit la fenêtre d'affichage et contient chaque
::view-transition-group
. ::view-transition-group
Tout à fait.
Transition
width
etheight
entre "avant" et "après" différents états.Ajoute une transition de
transform
entre "avant" et "après" quadruple espace d'affichage.::view-transition-image-pair
Il est tout à fait positionné pour remplir le groupe.
Permet de définir
isolation: isolate
pour limiter l'effet demix-blend-mode
sur l'ancienne et la nouvelle vue.::view-transition-new
et::view-transition-old
Il se trouve tout en haut à gauche du wrapper.
Remplit 100% de la largeur du groupe, mais sa hauteur est définie automatiquement, ce qui permet de conserver ses proportions au lieu de remplir le groupe.
Contient
mix-blend-mode: plus-lighter
pour permettre un vrai fondu enchaîné.L'ancienne vue passe de
opacity: 1
àopacity: 0
. La nouvelle vue passe deopacity: 0
àopacity: 1
.
Commentaires
Les commentaires des développeurs sont toujours les bienvenus. Pour ce faire, signalez un problème au groupe de travail CSS sur GitHub en lui faisant part de suggestions et de questions. Ajoutez un préfixe [css-view-transitions]
à votre problème.
Si vous rencontrez un bug, signalez-le plutôt.