Contenu principal

Comment utiliser l'API View Transitions ? Du hello world aux cas complexes.

lundi 06 février 2024

Vous avez peut-ĂȘtre entendu parler des View Transitions ? C’est la nouvelle API dans les navigateurs qui, avec une ligne de CSS et une ligne de JS promet d’animer vos transitions de maniĂšre performante.

Des dĂ©mos assez chouettes sont dĂ©jĂ  disponibles notamment autour de l’écosystĂšme Astro. Par exemple avec :

En ce qui nous concerne, on va se concentrer sur un cas : passer d’une liste contenant plusieurs articles imagĂ©s Ă  une pleine page qui reprend la mĂȘme image dans son header.

C’est assez classique, mais cela reprĂ©sente plusieurs difficultĂ©s :

  • Comment s’assurer que l’animation de l’image se fasse correctement dans les deux sens ?
  • Comment Ă©viter de dĂ©grader les performances en n’animant que ce qui est nĂ©cessaire ?
  • Les dimensions entre la vue liste et la vue pleine page sont assez diffĂ©rentes : comment faire pour que l’animation soit tout de mĂȘme fluide ?
  • Comment Ă©viter des flashs pendant le chargement de l’image pleine page ?

💡 Attention ce n’est pour l’instant disponible que sur les navigateurs basĂ©s sur Chromium. Mais les autres navigateurs ont donnĂ© leur accord sur l’API et vont donc l’implĂ©menter dans les annĂ©es Ă  venir. De plus, cet article se concentrera uniquement sur les animations dĂ©clenchĂ©es en JavaScript. Nous ne nous attarderont pas sur le cas des Multi Pages Applications (MPAs) qui ne sont pas encore sorties du mode expĂ©rimental dans Chromium.

PrĂ©sentation de l’API View Transition

Avant de rentrer dans le cƓur du sujet, prenons un exemple sans animation :

Element

Essentiellement, il s’agit du code suivant :

input.addEventListener('change', () => {
	element.classList.toggle('active');
});

Par le passĂ©, si j’avais voulu l’animer, j’aurais dĂ» jouer sur des propriĂ©tĂ©s CSS, manipuler des positions, et peut-ĂȘtre utiliser des techniques comme les animations FLIP pour m’assurer que ce soit performant.

Mais grñce à la nouvelle API des View Transitions, si je veux l’animer, je n’ai qu’une seule chose à faire : entourer le code qui active la modification de style par document.startViewTransition.

input.addEventListener('change', () => {
+	document.startViewTransition(() => {
		element.classList.toggle('active');
+	});
})

💡 Attention toutefois, document.startViewTransition n’est pas disponible partout. Veillez donc Ă  vĂ©rifier son existence afin de ne pas casser tous les autres navigateurs :

input.addEventListener('change', () => {
+	if ('startViewTransition' in document) {
		 document.startViewTransition(() => {
			 element.classList.toggle('active');
		 });
+	} else {
+		 element.classList.toggle('active');
+	}
})

Une fois ceci fait, vous obtiendrez le résultat suivant :

Element

Avec ce petit changement de code, on a maintenant un crossfade entre les deux Ă©tats, autrement dit l’état prĂ©cĂ©dent disparaĂźt pendant que le nouvel Ă©tat apparaĂźt. En effet, document.startViewTransition dit essentiellement au navigateur de :

  1. Prendre un screenshot de la page avant
  2. Prendre un screenshot de la page aprĂšs
  3. Animer la transition entre les deux

Mais animer l’opacitĂ© n’est pas toujours le mieux. Heureusement, il est possible d’indiquer au navigateur comment animer la transition.

Notamment, plutît que de faire un screenshot de la page entiùre, on peut lui dire d’animer uniquement une petite partie. Dans notre cas, on ne veut animer que l’Element.

Pour cela, il nous faut ajouter une propriété CSS :

.element {
	view-transition-name: element-id;
}

Vous pouvez lui donner n’importe quelle valeur. L’essentiel est que cet id soit unique sur votre page.

Element

Ainsi, dĂšs cet instant, le navigateur sait que l’élĂ©ment ne disparaĂźt pas mais est simplement dĂ©placĂ©. Alors il va faire sa magie noir et le dĂ©placer. 🎉

💡 Accessibilité : Gardez en tĂȘte toutefois que des animations de la sorte peuvent donner la nausĂ©e Ă  toute une partie de vos utilisateurices (par exemple celles et ceux atteint de troubles vestibulaires).

A ce titre, il est prĂ©fĂ©rable de dĂ©sactiver les animations de transition quand la prĂ©fĂ©rence “reduced motion” est activĂ©e dans le navigateur.

Cela peut ĂȘtre fait de maniĂšre globale avec ces quelques lignes de CSS. AInsi, je vous conseille de l’ajouter dans vos reset dĂšs aujourd’hui, plutĂŽt que de l’oublier demain.

@media (prefers-reduced-motion: reduce) {
	* {
		view-transition-name: unset !important;
	}
}

Faire fonctionner document.startViewTransition dans un framework JS

âžĄïž Si vous vous en moquez des frameworks JS, RDV Ă  la section suivante. 😘

L’exemple ci-dessus est un exemple qui fonctionne en Vanilla JS. Cela dit, gardez en tĂȘte qu’il peut y avoir quelques subtilitĂ©s en fonction du framework que vous utilisez. En effet, la plupart des frameworks font une tĂąche d’optimisation pour vous : quand vous mettez Ă  jour votre state, il ne met pas Ă  jour directement votre DOM. Il attend un petit peu pour faire toutes les modifications d’un coup. La consĂ©quence, c’est que cette mise Ă  jour vient trop tard et donc l’animation Ă©choue.

L’astuce est donc de faire en sorte que la mise Ă  jour soit synchrone ou, Ă  dĂ©faut, d’attendre qu’elle soit finie.

document.startViewTransition en React

En React, pour faire une mise Ă  jour synchrone, vous pouvez importer la mĂ©thode flushSync et entourer votre changement d’état par celle-ci.

+import { flushSync } from 'react-dom';

const [status, setStatus] = useState('closed');

function open() {
+	if ('startViewTransition' in document) {
+		document.startViewTransition(() => {
+			flushSync(() => {
+				 setStatus('opened');
+			});
+		});
+	} else {
		 setStatus('opened');
+	}
}

document.startViewTransition en Vue.js

Si vous modifiez la valeur d’une ref, le DOM sera mis Ă  jour de maniĂšre synchrone. Vous pouvez donc utiliser document.startViewTransition sans plus de cĂ©rĂ©monie.

+import { tick } from 'svelte';
const status = ref('closed');

function open() {
+	if ('startViewTransition' in document) {
+		document.startViewTransition(() => {
+			 status.value = 'opened';
+		});
+	} else {
		 status.value = 'opened';
+	}
}

document.startViewTransition en Svelte

En Svelte, la mise Ă  jour est nĂ©cessairement asynchrone. Donc plutĂŽt que de changer de mode, l’astuce est d’attendre la fin de la mise Ă  jour asynchrone pour dĂ©clencher la transition. Pour cela, on va transformer le callback de startViewTransition pour qu’il soit asynchrone et await tick() pour ĂȘtre sĂ»r que la mise Ă  jour est terminĂ©e.

let status = 'closed';

function open() {
+	if ('startViewTransition' in document) {
+		document.startViewTransition(async () => {
+			setStatus('opened');
+			await tick();
+		});
+	} else {
		 setStatus('opened');
+	}
}

Utiliser les View Transitions dans un cas complexe (vue liste vers vue page)

Si je ne vous ai pas encore achevĂ© avec mes explications, nous avons maintenant les bases de l’API des View Transitions. Mais, vous vous en doutez, on peut rapidement tomber dans des cas plus complexes. Pour mieux comprendre la magie qui se cache derriĂšre les View Transitions, nous allons donc essayer d’animer la transition d’une liste vers un bandeau de header.

Cliquez sur un des chiffres ci-dessous pour simuler la vue page :

L’exemple est plus compliquĂ© parce que :

  1. ce n’est pas juste un ajout de classe, la structure complùte du DOM change. En effet, on passe d’un <button> quand on est dans la vue liste, à un <h4> quand on est sur la nouvelle page
  2. selon oĂč on clique, on ne veut pas animer tous les Ă©lĂ©ments (si je clique sur 4, il ne faudrait pas que le 1 s’anime)
  3. la taille de l’élĂ©ment change afin de simuler le comportement d’une banniĂšre en haut de page

Voyons donc comment gérer ces différentes problématiques pour obtenir une animation aux petits oignons.

Comment faire la transition lorsqu’il y a un changement de DOM ?

Un peu plus haut, je vous disais que l’API fonctionnait en prenant un screenshot avant, un screenshot aprĂšs, puis fait une transition entre les deux. De fait, cela veut dire que c’est compatible mĂȘme si le DOM change complĂštement entre avant et aprĂšs.

La seule chose Ă  bien prendre en compte, c’est de s’assurer que la propriĂ©tĂ© CSS view-transition-name est prĂ©sente avant ET aprĂšs, et que son ID soit stable.

Ici, si je veux faire la transition de l’élĂ©ment 4 de la liste, vers le header 4, il va donc falloir que je fasse en sorte que :

.element--4 {
	view-transition-name: element-id;
}

.header {
	view-transition-name: element-id;
}

Mais ça ouvre le problÚme que je dois avoir un id différent pour chacun des éléments de la liste. Sinon, le navigateur ne saura pas quel screenshot prendre avant de faire la transition.

D’ailleurs, il vous prĂ©viendra dans votre console en affichant le message d’erreur : Unexpected duplicate view-transition-name: <name>.

Le premier rĂ©flexe est donc de gĂ©rer cela en crĂ©ant autant de view-transition-name diffĂ©rents qu’il y a d’élĂ©ments dans votre liste:

<!-- ⚠ ne pas faire ça -->

<!-- Dans la vue liste -->
<button style="view-transition-name: element-1">1</button>
<button style="view-transition-name: element-2">2</button>
<button style="view-transition-name: element-3">3</button>
<button style="view-transition-name: element-4">4</button>

<!-- Dans la vue page -->
<header style="view-transition-name: element-4">4</header>

Mais cela a pour inconvĂ©nient que ça surcharge le travail du navigateur. Notamment, dans la phase avant, le navigateur va ĂȘtre obligĂ© de prendre un screenshot de tous les Ă©lĂ©ments qui ont une view-transition-name. Si vous n’en avez que quelques uns sur votre site, ça peut le faire. Mais quand vous aurez adoptĂ© cette technique plus largement pour animer beaucoup de transitions, cela va commencer Ă  surcharger votre navigateur et risque d’allonger le temps de vos interactions.

Notamment, j’ai fait quelques tests et dĂšs 25 view-transition-name, sur des pĂ©riphĂ©riques pas trĂšs puissants, on dĂ©passe les 100ms de latence entre le click et le dĂ©but de l’animation. Donc Ă  l’intĂ©raction on commence Ă  ressentir un petit lag. DĂšs 200 Ă©lĂ©ments, on constate des lags pendant l’animation, mĂȘme si l’animation est un simple fade.

Screenshot des différents pseudo éléments qui existent pendant la transition
DétailsPage de test

Il est donc important d’ajouter des view-transition-name uniquement quand vous en avez besoin, c’est-Ă -dire juste avant le dĂ©but de l’animation :

/**
 * @var {number} item
 */
function selectItem(item) {
+	const button = document.querySelector(`#item-${item}`)
+	button.style.setProperty('view-transition-name', 'element');

	document.startViewTransition(async () => {
		goToPage(item);
	});
}

Et de s’assurer que le header, une fois la page ouverte, ait bien la view-transition-name aussi.

.header {
	view-transition-name: element;
}

Attention toutefois, cela gùre l’animation dans un sens uniquement : quand vous partez de la page pour revenir à la liste, il faut aussi penser à ajouter la view-transition-name sur le bouton.

/**
 * @var {number} currentItem the id of the displayed header
 */
function unselectItem(currentItem) {
	document.startViewTransition(async () => {
		goToList();

+		const button = document.querySelector(`#item-${currentItem}`)
+		button.style.setProperty('view-transition-name', 'element');
	});
}

Et grùce à ça vous obtenez cette animation :

C’est mieux, mais ce n’est pas encore tout Ă  fait ça. Dans la section suivante, nous allons voir comment amĂ©liorer la transition pour Ă©viter ce sentiment de flash, particuliĂšrement visible sur desktop.

💡 Cependant, avant ça, je veux faire un petit laĂŻus sur les frameworks JS, parce qu'il n'est pas forcĂ©ment Ă©vident de voir comment traduire le code JS ci-dessus dans le framework de votre choix.

Cliquez ici pour afficher expliquer comment gérer les View Transitions en React (adaptable aux autres frameworks :))

Si vous essayez de manipuler directement le DOM, React ne va pas ĂȘtre content parce qu’il ne sera plus synchronisĂ© avec le contenu du DOM. Vous finirez juste par avoir une erreur et une page blanche. On va donc plutĂŽt se reposer sur le systĂšme de rendu classique. Pour cela, nous allons donc utiliser des states.

Les points qu’il faut retenir sont :

  • pour pouvoir correctement animer l’animation de retour il vous faut stocker quelque part l’id de l’item d’origine. Ca peut ĂȘtre dans un state, dans la session ou dans l’URL. A vous d’implĂ©menter les propriĂ©tĂ©s props.select, props.unselect et props.previousItem. Le piste la plus directe serait de faire un composant parent qui possĂšde un useState.
  • pour pouvoir ajouter un view-transition-name juste avant de dĂ©clencher l’animation, vous pouvez sĂ©parer votre handler d’évĂ©nement en 2 Ă©tapes : un premier flush/render qui ajoute la view-transition-name, une deuxiĂšme qui dĂ©clenche la startViewTransition.
  • dans cet exemple, je pars du principe que vous n’utilisez pas de router externe. Si vous en utilisez un, sachez que Remix supporte les View Transitions, TanStack Router semble pouvoir le faire grĂące au hook useNavigate, mais qu’il n’est pour l’instant pas possible de le faire en Next.js.

Voici ce que ça donnerait avec du vrai code :

/**
 * @var {number} props.item
 * @var {(item: number) => void} props.unselect pour revenir Ă  la vue liste
 */
function Page(props) {
	function close(item: number) {
		if ('startViewTransition' in document) {
			document.startViewTransition(() => {
				flushSync(() => {
					props.unselect(item);
				});
			});
		} else {
			props.unselect(item);
		}
	}

	return (
		<div>
			<h4 className="header">{item}</h4>
			<button onClick={close}>
				Revenir Ă  la liste
			</button>
		</div>
	);
}

/**
 * @var {number[]} props.list
 * @var {number|null} props.previousItem récupéré depuis le props.unselect de la Page
 * @var {(item: number) => void} props.select pour aller Ă  la vue page
 */
function List(props) {
	// Si on vient d'une Page, on aura le paramÚtre passé à props.unselect qui
	// sera disponible.
	// En le passant ensuite via props.previousItem, c'est ce qui nous permet
	// de s'assurer qu'on va bien animer le retour Ă  la liste, et c'est pour
	// cette raison qu'on n'initialise pas toujours le state Ă  `null`
	const [itemWithViewTransitionName, setItemWithTransitionName]
		= useState(previousItem);

	function open(item: number) {
		if ('startViewTransition' in document) {
			// Avant de déclencher l'animation on met à jour le state pour que le
			// bouton concerné récupÚre la bonne propriété CSS view-transition-name
			flushSync(() => {
				setItemWithTransitionName(item);
			});

			// Puis seulement on déclenche l'animation
			document.startViewTransition(() => {
				flushSync(() => {
					props.select(item)
				});
			});
		} else {
			props.select(item)
		}
	}

	return (
		<ul>
			{list.map((item) => (
				<li key={item}>
					<button
						style={
							itemWithViewTransitionName === item
								? { viewTransitionName: 'element' }
								: undefined
						}
						onClick={() => open(item)}
					>
						{item}
					</button>
				</li>
			))}
		</ul>
	);
}

Comment faire la View Transition quand l’élĂ©ment change de taille ?

Voyons maintenant plus en dĂ©tail pourquoi l’animation ne paraĂźt pas fluide. Pour cela, nous allons utiliser l’onglet Animations dans les DevTools de Chrome (vu que l’API des View Transitions n’existe pas ailleurs pour le moment).

Voir comment utiliser l'onglet Animations
Screenshot pour ouvrir l'outil Animations dans les DevTools de Chrome
  1. Cliquer sur “Customize and control DevTools”
  2. Cliquer sur “More tools”
  3. Choisir l’outil “Animations”

Cela vous donner accùs à ce panneau, sur lequel nous allons appuyer sur “Pause all”.

Screenshot de l'outil Animations en attente dans les DevTools de Chrome

Dans les faits, cela bloque les prochaines animations qui pourraient advenir dans la page. Ainsi, vous pouvez aller cliquer sur notre exemple de transition et constater qu’une nouvelle timeline est apparue dans la boüte d’animations.

En cliquant dessus, on va voir toutes les animations qui sont en cours d’execution mais avec un temps bloquĂ© Ă  0. Vous pouvez alors dĂ©placer le petit losange rouge afin de jouer petit Ă  petit l’animation.

Screenshot de l'outil Animations avec une animation en cours d'inspection dans les DevTools de Chrome

Ainsi, si on inspecte l’animation en milieu d’exĂ©cution, on constate que cela ressemble Ă  ceci :

Screenshot de l'animation d'ouverture de la liste à lorsqu'elle est jouée à 50%

Il y a bien une transition avec les screen avant/aprĂšs, mais les tailles ne sont pas cohĂ©rentes. Le screenshot qui vient de la vue liste devient trĂšs grand. Il est beaucoup plus haut qu’il ne le devrait.

Heureusement pour nous, on peut le corriger.

Notamment, pendant l’inspection de l’animation, si on utilise l’outil pour cibler un Ă©lĂ©ment du DOM, ça va nous amener tout en haut de l’onglet “Elements” oĂč on verra des ::view-transition-group, ::view-transition-old, ::view-transition-new, etc.

Screenshot des différents pseudo éléments qui existent pendant la transition

En fait, ce sont des pseudo Ă©lĂ©ments qui n’existent que le temps de l’animation. On peut imaginer ça comme la structure du DOM qui permet d’afficher les screenshots avant, aprĂšs, et orchestrer la transition entre les deux.

En les inspectant, on se rend compte qu’on peut vraiment les imaginer comme des tags HTML avec des images auxquels on a donnĂ© des propriĂ©tĂ©s CSS, des animations avec des @keyframes, etc.

Ce que ça veut dire, c’est que si le navigateur a mal positionnĂ© ces images, on peut ajouter des propriĂ©tĂ©s CSS pour corriger ça. Notamment, on va souvent avoir besoin de corriger les tailles, les object-fit, les positions, etc.

Dans notre cas, il faudrait que pendant la transition, le screenshot de l’état aprĂšs, reprĂ©sentĂ© par le pseudo Ă©lĂ©ment ::view-transition-new(<name>) (oĂč <name> est la valeur de la propriĂ©tĂ© CSS view-transition-name), soit de la taille de la petite boĂźte violette plutĂŽt que la grande boĂźte verte.

Screenshot des différents pseudo éléments qui existent pendant la transition

Ce qui nous donnerait le code CSS suivant :

/* J'active ces changements sur le screenshot avant (view-transition-old)
ET sur le screenshot aprĂšs (view-transition-new) parce qu'il faut que
l'animation fonctionne de la mĂȘme façon Ă  l'ouverture et Ă  la fermeture. */
html::view-transition-old(element),
html::view-transition-new(element) {
	/* `block-size` et `inline-size` peuvent ĂȘtre compris comme
	height & width.
	Dans cet exemple, on les unset parce qu'on ne veut pas qu'il y
	ait de transformation de taille, mais uniquement un
	déplacement/agrandissement de la zone visible. */
	block-size: unset;
	inline-size: unset;

	/* Par défaut les pseudo éléments sont déjà positionnés en absolu,
	mais ferrés en haut à gauche. Dans notre cas, on veut que ce soit
	centré, donc on utilise la bonne vieille méthode du 50% - 50% */
	top: 50%;
	left: 50%;
	translate: -50% -50%;

	/* Par défaut les transitions ont un fade-in et un fade-out pour
	s'assurer d'avoir une transition fluide entre des éléments qui changent
	de couleur ou de contenu. Ici, on sait que seul la fenĂȘtre de visibilitĂ©
	change. Donc on peut désactiver cette animation. */
	animation-name: unset;
}

Cependant, si on ne fait que ça, il nous reste un problĂšme : effectivement, l’élĂ©ment n’est plus trop petit, mais on a perdu l’agrandissement. Le nouveau screenshot est toujours visible.

Pour cela, on va s’aider de la totalitĂ© de la structure des ::view-transition-xxx. Notamment, on peut voir que ::view-transition-old et ::view-transition-new sont entourĂ©s par un ::view-transition-image-pair qui, lui, est toujours Ă  la bonne taille.

Screenshot des différents pseudo éléments qui existent pendant la transition

Ne me demandez pas pourquoi sur le screen ci-dessus, ça affiche ::view-transition-group, c’est bien le ::view-transition-image-pair que je survole dans le DOM 😅

Donc pour s’assurer que les images Ă  l’intĂ©rieur ne dĂ©passent jamais celui-ci, quel est le CSS qu’on ajoute ?

html::view-transition-image-pair(element) {
	overflow: hidden;
}

Et voilà 🎉 Notre liste s’anime parfaitement à l’ouverture et à la fermeture d’une page. 👏

Pour aller plus loin

Voici quelques cas que vous pourriez essayer de creuser/implémenter pour vérifier que vous avez bien assimiler toutes ces notions.

Comment est-ce que vous animeriez une liste qui est composée d'un texte ET d'une image pour chaque élément ? (Cliquez pour voir la réponse)

Vous avez plusieurs options, mais cognitivement, moins vous déplacez d'élément, plus l'animation sera facile à comprendre. Donc il y a des chances que ce soit mieux de n'animer que l'image. Notamment la guideline "Unified direction" de Material Design montre bien son impacte.

Mais si vous voulez animer plusieurs Ă©lĂ©ments, n’hĂ©sitez pas Ă  ajouter plusieurs view-transition-name. Par exemple, pour que le lien “Revenir Ă  la liste” ne soit pas trop violent, je lui ai ajoutĂ© une view-transition-name pour qu’il ait un joli fade-in/fade-out le temps de l’animation.

Pourquoi est-ce qu'à l'animation de mon image, j'ai un flash blanc disgracieux ? (Cliquez pour voir la réponse)

Si vous cliquez sur le bouton "Agrandir" avec un réseau qui est suffisamment lent, l'animation n'aura pas le comportement que vous espérez : la premiÚre version de l'image (petite) fera un fade out, puis pendant quelques (milli)secondes, il ne s'affichera rien, et enfin, quand la nouvelle image (grande) aurai fini de se charger, elle apparaßtra aussi sec.

Album de The Strokes : First Impressions of Earth

DĂšs que vous commencerez Ă  manipuler des images dans les View Transitions, ce comportement arrivera. En effet, vous avez des contraintes trĂšs diffĂ©rentes entre vos diffĂ©rentes pages. Par exemple, sur une vue liste, il vous faudra peut ĂȘtre une image en 300x300 et sur la vue pleine page, il vous faudra du 1920x600 sur desktop.

Dans la plupart des dĂ©mos que j'ai vu passer, le partie pris est de charger une image suffisamment grande pour que ce soit la mĂȘme URL d'image sur la liste ET sur la page.

C'est désastreux pour l'expérience de navigation si vous n'avez pas une connexion fibrée parce que votre liste mettra trop de temps à se charger.

A la place, vous avez deux options :

  • Vous pouvez prĂ©charger l'image quand vous anticipez que la prochaine action risque d'ĂȘtre l'ouverture de la page (par exemple : si on a un mouseover ou un focus sur desktop, ou si on a un IntersectionObserver suffisamment long sur mobile). Ainsi, le preload permet d'ajouter la grande image en cache et donc de l'avoir Ă  disposition au moment oĂč on dĂ©clenche la transition. La probabilitĂ© pour que la grande image n'ait pas le temps d'ĂȘtre tĂ©lĂ©chargĂ©e est beaucoup plus faible, et la plupart des personnes n'auront pas de flash.
  • Ou bien, toujours commencer par afficher la page avec la petite image. Ainsi, pendant la transition, c'est exactement la mĂȘme image qui est affichĂ©e (la petite), et pendant que la transition se joue, vous commencez Ă  tĂ©lĂ©charger la grande image afin de l'afficher qu'Ă  partir du moment oĂč vous avez fini de la tĂ©lĂ©charger. Voici, ci-dessous, ce que ça donnerait :

    Album de The Strokes : First Impressions of Earth
    function selectItem(item) {
    	document.startViewTransition(async () => {
    		// La fonction goToPage doit bien faire attention
    		// Ă  afficher la page avec comme source la petite image
    		goToPage(item);
    
    		const src = await loadImage(`/image/item/${item}/big`)
            // Une fois seulement que la grande image a été téléchargée
            // (et donc mise en cache), alors on vient l'utiliser
            // dans la page.
            replacePageImageWith(src);
    	});
    }
    
    /**
     * Essaye de télécharger une image et résout la promesse
     * dÚs que celle-ci a fini de se télécharger
     */
    async function loadImage(src: string) {
        return new Promise((resolve, reject) => {
            const img = document.createElement('img');
            img.addEventListener('load', () => {
                resolve(src);
            });
            img.addEventListener('error', (error) => {
                reject(error);
            });
    
            img.src = src;
    
            // L'image peut avoir déjà été téléchargée.
            // Dans ce cas, l'event `load` n'est pas
            // déclenché. Il faut donc vérifier cela
            // manuellement.
            if (img.complete) {
                resolve(src);
            }
        });
    }
    

    Sauf si vous avez des yeux bioniques, je suis prĂȘt Ă  parier que dans une navigation normale, vous ne remarquerez pas la diffĂ©rence. Mais dans le cas d’un rĂ©seau lent, ça fera toute la diffĂ©rence.

Dans l'exemple de cet article le contenu ne change pas de scale. Il n'y a que la taille de la zone visible qui change. Comment feriez-vous si l'image que vous animez était légÚrement zoomée aprÚs la transition ? (Cliquez pour voir la réponse)

L’astuce ne sera plus d’unset les block-size et inline-size, mais de privilĂ©gier l’utilisation de object-fit et object-position. En effet, chaque screenshot est une image, donc toutes les propriĂ©tĂ©s qui vous aident d’ordinaire Ă  positionner des images vous seront aussi utiles ici.

Notez aussi que parfois des propriétés dynamiques comme inline-size: fit-content peuvent vous sauver la mise.

Une fois votre transition finie, vous voulez déclencher d'autres animations (ex: fade-in d'un texte) ou utiliser des méthodes de easing. Comment orchestrer ça ? (Cliquez pour voir la réponse)

Par dĂ©faut, je mentionnais le fait qu'une view-transition se joue avec un fade-in (opacitĂ© 0 Ă  1). C'est d'ailleurs pour ça qu'on avait fait un animation-name: unset; dans l'exemple de cet article. Donc si vous prĂ©fĂ©rez dĂ©caler l'animation d'un Ă©lĂ©ment, ça pourrait ĂȘtre en utilisant un animation-delay.

Si les séquences sont vraiment complexes et bien séparées, sachez aussi que vous pouvez récupérer une promesse quand votre premiÚre animation est terminée.

const viewTransition = document.startViewTransition(() => ...);

await viewTransition.updateCallbackDone;

Conclusion

Avec tout ça, vous devriez ĂȘtre parĂ© pour animer tout ce qui vous passe sous la main 😁

Cela dit, cette API reste particuliĂšrement adaptĂ©e quand vous voulez passer un Ă©lĂ©ment d’un point A Ă  un point B. C’est pour ça qu’on parle de transition. Elle permet notamment de gĂ©rer des cas complexes oĂč le DOM change complĂštement.

Mais si vous avez une solution simple en CSS que vous arrivez Ă  gĂ©rer avec des @keyframes classiques, des propriĂ©tĂ©s transition, qui restent performantes, let’s go ! N’oubliez pas tout ce que vous avez dĂ©jĂ  pu utiliser par le passĂ©.

Si par contre, vous vous demandez jusqu’oĂč on peut pousser cet API et vous avez envie de faire des tests et des expĂ©riences, simples ou compliquĂ©es, j’aimerais beaucoup en entendre parler.

La suite pour cette API est son activation dans un contexte de MPA (la page se recharge complĂštement). C’est aujourd’hui encore sous feature flag dans Chrome et j’ai personnellement Ă©tĂ© confrontĂ© Ă  quelques Ă©lĂ©ments bloquants. Mais dĂšs que ça se dĂ©bloque, je vous en parle 👀

En tout cas, si ce genre de contenus vous plait ou que vous aimez lire des trucs sur le web, la webperf ou le front de maniĂšre gĂ©nĂ©ral, n’hĂ©sitez pas Ă  me suivre sur les rĂ©seaux sociaux (Mastodon, LinkedIn ou Twitter) et Ă  me dire ce que je devrais amĂ©liorer pour la suite.

Prenez soin de vous, et Ă  trĂšs vite đŸ«¶


Si vous voulez suivre mes publications, il paraĂźt que j'ai un feed RSS, Mastodon et un Twitter.

Si vous pensez à d'autres méthodes que vous voudriez que je mette en place (pigeon voyageur, avion en papier, etc.), n'hésitez pas à me les proposer :)