Primeros pasos usando View Transitions API
Este artículo es para aquellos que están interesados en la View Transitions API pero todavía no la han probado. Veremos los primeros pasos de la manera más sencilla.
Si ya estás enterado de esta API seguramente has visto ejemplos geniales y, si no, te comento que he estado viendo varias demos de la comunidad, y estoy entusiasmado por el futuro de esta característica.
La mayoría de casos de uso que he estado viendo son de navegación entre páginas. Actualmente la API soporta single-page applications (SPA). Y espero que en poco tiempo también funcione para multi-page applications (MPA).
Es importante tener en cuenta que esta API no solo se aplica cuando navegamos entre páginas. La View Transitions API puede aplicarse a diferentes casos de uso donde se necesite realizar una transición animada cuando actualizamos el DOM.
Antes de iniciar ten en cuenta que esta API está soportada en Chrome 111+.
Iniciemos con un hola mundo
Para el primer hola mundo con View Transitions API usaremos el siguiente código.
Donde se espera que al dar click en el botón se muestre o se oculte el texto Hola Mundo. Es decir, actualizamos el DOM con un simple texto.
<!doctype html>
<html>
<head>
<title>Hola Mundo con View Transitions API</title>
</head>
<body>
<button id="toggleMessage">Mostrar mensaje</button>
<div id="content"></div>
<script>
const button = document.getElementById('toggleMessage')
const content = document.getElementById('content')
button.addEventListener('click', () => {
updateTheDOMSomehow()
})
function updateTheDOMSomehow() {
if (content.innerHTML) {
content.innerHTML = ''
} else {
content.innerHTML = '<h1>Hola Mundo</h1>'
}
}
</script>
</body>
</html>
Ahora para el primer caso de uso de View Transitions API tenemos que llamar a startViewTransition. Este método recibe un callback donde especificamos la función que realiza la actualización del DOM.
Si pruebas el código notarás una animación al momento de mostrar o ocultar el texto Hola Mundo, esta animación se denomina cross-fade.
button.addEventListener('click', () => {
document.startViewTransition(() => updateTheDOMSomehow())
})
Para el primer ejemplo o cualquier caso de uso, debemos considerar validar que el navegador que usemos tenga soporte de esta API. En caso de no tener el soporte debemos mantener el funcionamiento normal. Eso lo logramos en el siguiente código.
button.addEventListener('click', () => {
if (!document.startViewTransition) {
updateTheDOMSomehow()
return
}
document.startViewTransition(() => updateTheDOMSomehow())
})
Un cuadrado en movimiento
Ya hemos probado la API actualizando el DOM al agregar y eliminar un elemento. Se podría también de otras formas, por ejemplo alternando clases de CSS.
En el siguiente código tenemos un cuadrado que moveremos a la derecha. Donde mantenemos la estructura similar al ejemplo anterior. Y tenemos la misma animación cross-fade.
<html>
<head>
<title>Un cuadrado en movimiento</title>
<style>
.square {
width: 100px;
height: 100px;
background: red;
}
.motion {
transform: translateX(500px);
}
</style>
</head>
<body>
<div class="square"></div>
<script>
const square = document.querySelector('.square')
square.addEventListener('click', () => {
if (!document.startViewTransition) {
updateTheDOMSomehow()
return
}
document.startViewTransition(() => updateTheDOMSomehow())
})
function updateTheDOMSomehow() {
square.classList.toggle('motion') // <---
}
</script>
</body>
</html>
Ahora usaremos la propiedad view-transition-name que permite identificar un elemento en la transición. El resultado visualmente es un movimiento del cuadrado y ya no una animación cross-fade.
.square {
width: 100px;
height: 100px;
background: red;
view-transition-name: square;
}
Varios elementos en una transición
Para entender mejor la propiedad view-transition-name vamos a utilizar varios elementos.
Siguiendo el ejemplo anterior, en este caso tenemos 4 cuadrados:
- Cuadrado 1 rojo: Tiene el comportamiento de movimiento a la derecha del ejemplo anterior.
- Cuadrado 2 verde: Tiene el comportamiento del cuadrado 1 pero en lugar de aplicar rotación reducimos su tamaño con scale.
- Cuadrado 3 azul: Tiene el comportamiento por defecto con la animación cross-fade ya que no tiene la propiedad view-transition-name.
- Cuadrado 4 gris: Es un elemento que no tiene una animación en la transición.
<html>
<head>
<title>4 cuadrados</title>
<style>
.square {
width: 100px;
height: 100px;
}
.square1 {
background: red;
view-transition-name: square-1;
}
.square2 {
background: green;
view-transition-name: square2;
}
.square3 {
background: blue;
}
.square4 {
background: gray;
}
.motion1 {
transform: translateX(500px) rotate(180deg);
}
.motion2 {
transform: translateX(500px) scale(0.5);
}
.motion3 {
transform: translateX(500px);
}
</style>
</head>
<body>
<button id="run">ejecutar</button>
<div class="square square1" id="square1"></div>
<div class="square square2" id="square2"></div>
<div class="square square3" id="square3"></div>
<div class="square square4" id="square4"></div>
<script>
const button = document.getElementById('run')
const square1 = document.getElementById('square1')
const square2 = document.getElementById('square2')
const square3 = document.getElementById('square3')
button.addEventListener('click', () => {
if (!document.startViewTransition) {
updateTheDOMSomehow()
return
}
document.startViewTransition(() => updateTheDOMSomehow())
})
function updateTheDOMSomehow() {
square1.classList.toggle('motion1')
square2.classList.toggle('motion2')
square3.classList.toggle('motion3')
}
</script>
</body>
</html>
Cómo funciona las transiciones
Aprovechando los ejemplos anteriores, en pasos sencillos:
- Cuando se ejecuta startViewTransition, la API hace una foto del estado actual de la página.
- Luego se ejecuta el callback donde tenemos el método updateTheDOMSomehow para actualizar el DOM.
- Al terminar de actualizar el DOM, la API hace otra foto del nuevo estado de la página.
- Finalmente, la API aplica la animación o transición usando CSS en ambas fotos.
Estas fotos representan el estado antiguo y nuevo de la página. Se crean mediante pseudoelementos de CSS, que son una manera de tener contenido virtual que no existe como elementos en el documento HTML.
Si recordamos el primer ejemplo con el texto hola mundo, podemos entender que:
- la primera foto no tiene el texto hola mundo
- la segunda foto tiene el texto hola mundo
Ambas fotos se ubican encima de la página con el DOM actualizado. La animación por defecto es cross-fade. Ello se logra haciendo que la primera foto pase de una opacidad de 1 a 0 y la segunda foto de 0 a 1. Luego de terminar la animación estas fotos desaparecen, revelando el DOM real.
Personalizando con CSS
Lo primero que podemos probar es ajustar la duración de la animación con la propiedad animation-duration. Si lo pruebas con el ejemplo de los 4 cuadrados podrás observar que se afecta visualmente al cuadrado 3 porque no tiene especificado la propiedad view-transition-name.
Además, si tenemos presente cómo funcionan las transiciones. Se entiende que durante el proceso de la animación no se puede ejecutar otro evento hasta haber culminado y tener el DOM real.
<html>
<head>
<title>Personalizando con CSS</title>
<style>
.square {
width: 100px;
height: 100px;
}
.square1 {
background: red;
view-transition-name: square-1;
}
.square2 {
background: green;
view-transition-name: square2;
}
.square3 {
background: blue;
}
.square4 {
background: gray;
}
.motion1 {
transform: translateX(500px) rotate(180deg);
}
.motion2 {
transform: translateX(500px) scale(0.5);
}
.motion3 {
transform: translateX(500px);
}
::view-transition-old(root),
::view-transition-new(root) {
animation-duration: 5s; // <---
}
</style>
</head>
<body>
<button id="run">ejecutar</button>
<div class="square square1" id="square1"></div>
<div class="square square2" id="square2"></div>
<div class="square square3" id="square3"></div>
<div class="square square4" id="square4"></div>
<script>
const button = document.getElementById('run')
const square1 = document.getElementById('square1')
const square2 = document.getElementById('square2')
const square3 = document.getElementById('square3')
button.addEventListener('click', () => {
if (!document.startViewTransition) {
updateTheDOMSomehow()
return
}
document.startViewTransition(() => updateTheDOMSomehow())
})
function updateTheDOMSomehow() {
square1.classList.toggle('motion1')
square2.classList.toggle('motion2')
square3.classList.toggle('motion3')
}
</script>
</body>
</html>
Para el último ejemplo tenemos una animación donde hacemos que la actualización del DOM tenga un efecto de movimiento de derecha a izquierda. Aquí notaremos explicitamente que el cuadrado 4 también es parte de la transición, gracias a la animación aplicada al root.
@keyframes slide-from-right {
from {
transform: translateX(200px);
}
}
::view-transition-new(root) {
animation: 2s slide-from-right;
}
Y seguramente notarás que el botón ejecutar al igual que el cuadrado 4 también es parte de la animación. Una forma de evitar ello podría ser aplicando lo siguiente:
- Usar la propiedad
view-transition-name: btn-run
para identificar el botón. - Luego evitamos la animación aplicando
animation: none
al pseudoelemento identificado en lugar de al root.
#run {
view-transition-name: btn-run;
}
::view-transition-new(btn-run) {
animation: none;
}
¡Gracias por leer!
Si deseas profundizar más sobre esta API, puedes revisar el siguiente artículo del blog de Chrome: Smooth and simple transitions with the View Transitions API