Capítulo 11 de 11

Tutorial VueJS 3: transiciones y animaciones con Transition

verificado el 2 septiembre 2026 · 5 min

Respuesta rápida

<Transition> anima un elemento único al aparecer y al desaparecer, <TransitionGroup> anima una lista. Vue aplica clases CSS, su hoja de estilos decide el efecto. Atención: en Vue 3, las clases de partida son -enter-from y -leave-from, no -enter y -leave como en Vue 2.

Al final de este capítulo, sabrá animar la aparición, la desaparición y el desplazamiento de elementos, sin biblioteca.

Un elemento que aparece de golpe produce un efecto de salto. Vue incluye dos componentes integrados, <Transition> y <TransitionGroup>, que aplican clases CSS en el momento adecuado para animar las entradas y las salidas. Ninguna biblioteca que instalar.

El principio

Envuelva en <Transition> un elemento cuya visualización dependa de una condición. Vue no lo anima por sí mismo: añade y quita clases, y es su CSS el que decide el efecto.

Seis clases cubren el ciclo completo.

css
/* entrada */
.v-enter-from     /* estado de partida, aplicado justo antes de la inserción */
.v-enter-active   /* durante toda la entrada: aquí es donde va transition */
.v-enter-to       /* estado de llegada */

/* salida */
.v-leave-from     /* estado de partida de la salida */
.v-leave-active   /* durante toda la salida */
.v-leave-to       /* estado final, justo antes de retirarlo del DOM */

El prefijo v- es el de una transición sin nombre. Con name="fade", las clases pasan a ser fade-enter-from, fade-enter-active, y así sucesivamente.

La trampa del código encontrado en la web

En Vue 2, las clases de partida se llamaban .fade-enter y .fade-leave, sin sufijo. Vue 3 las renombró como -enter-from y -leave-from. Un ejemplo copiado de un artículo antiguo no producirá, por tanto, ninguna animación de entrada, sin el menor mensaje de error. Si su transición no hace nada, compruebe primero este punto.

Animar la disponibilidad

En ProductDisplay.vue, el mensaje de stock alterna entre dos párrafos según la variante sobrevolada. Envuélvalos:

src/components/ProductDisplay.vue
<Transition name="fade" mode="out-in">
  <p v-if="inStock" key="in">En stock</p>
  <p v-else key="out">En rupture</p>
</Transition>

Después describa el efecto en la hoja de estilos, como en el capítulo sobre las clases y los estilos:

src/assets/boutique.css
.fade-enter-from,
.fade-leave-to {
  opacity: 0;
  transform: translateY(-10px);
}

.fade-enter-active,
.fade-leave-active {
  transition: opacity .3s ease, transform .3s ease;
}

Pase el ratón por los círculos de color: el mensaje se funde en lugar de saltar.

mode="out-in" espera a que el elemento antiguo haya salido antes de insertar el nuevo. Sin él, los dos párrafos coexisten una fracción de segundo y se superponen.

Lo que acepta Transition

Un solo elemento a la vez. Es la restricción principal: <Transition> anima un hijo único, no una lista.

Se activa en tres situaciones: un v-if, un v-show o un cambio de componente mediante <component :is>. Para forzar una transición entre dos elementos del mismo tipo, deles key diferentes, como más arriba.

Animar una lista

Para una lista hace falta <TransitionGroup>. Dos diferencias: acepta varios hijos, y renderiza él mismo un elemento contenedor si se lo pide con tag.

En ReviewList.vue, sustituya el <ul>:

src/components/ReviewList.vue
<template>
  <div class="review-container">
    <h3>Avis</h3>
    <TransitionGroup name="liste" tag="ul">
      <li v-for="review in reviews" :key="review.id">
        {{ review.name }} a mis {{ review.rating }} étoiles
        <br>
        « {{ review.review }} »
      </li>
    </TransitionGroup>
  </div>
</template>

Cada hijo necesita una key única y estable. El índice no sirve aquí: cambia en cada inserción, y Vue ya no sabría qué elemento es nuevo. Demos un identificador a cada opinión en el momento de añadirla:

src/components/ProductDisplay.vue
<script setup>
function addReview(review) {
  reviews.value.push({ ...review, id: crypto.randomUUID() })
}
</script>

crypto.randomUUID() está disponible en todos los navegadores actualizados, en las páginas servidas por HTTPS o desde localhost.

El CSS añade una clase más, -move, que anima el desplazamiento de los elementos restantes cuando un vecino desaparece:

src/assets/boutique.css
.liste-enter-from,
.liste-leave-to {
  opacity: 0;
  transform: translateX(24px);
}

.liste-enter-active,
.liste-leave-active,
.liste-move {
  transition: all .3s ease;
}

Envíe varias opiniones: cada una entra deslizándose, y las demás se reorganizan con suavidad.

Salida y desplazamiento

Para que -move funcione al eliminar, el elemento que se va debe salir del flujo, si no, los demás solo se mueven al final de su animación. Añada position: absolute a la clase -leave-active cuando sus elementos puedan eliminarse.

Animar la primera visualización

Por defecto, nada se anima en el renderizado inicial. El atributo appear cambia eso:

html
<Transition name="fade" appear>
  <p>Visible dès le chargement, en fondu</p>
</Transition>

Las animaciones CSS y los hooks

transition no es la única opción: una animation CSS también funciona, aplicada sobre la clase -enter-active.

Para controlar una biblioteca de animación en JavaScript, <Transition> emite eventos en cada etapa:

html
<Transition
  :css="false"
  @before-enter="avantEntree"
  @enter="entree"
  @after-enter="apresEntree"
  @leave="sortie"
>
  <p v-if="visible">Contenu</p>
</Transition>

Con :css="false", Vue deja de gestionar las clases y le cede el control. Las funciones enter y leave reciben entonces el elemento y un callback done que hay que llamar cuando la animación termina:

javascript
function entree(el, done) {
  // animación con la biblioteca que prefiera
  done()
}

El movimiento no siempre es deseable

Algunas personas desactivan las animaciones a nivel del sistema, en particular para evitar las náuseas provocadas por el movimiento. Respete ese ajuste:

src/assets/boutique.css
@media (prefers-reduced-motion: reduce) {
  .fade-enter-active,
  .fade-leave-active,
  .liste-enter-active,
  .liste-leave-active,
  .liste-move {
    transition: none;
  }
}

La interfaz sigue siendo funcional, solo desaparece la animación.

Es el final del tutorial

Su aplicación muestra un producto y sus variantes, gestiona un carrito, valida un formulario y anima sus cambios de estado. Ha cubierto la reactividad, las directivas, los componentes, las props, los eventos y las transiciones: suficiente para leer y escribir Vue a diario.

Para continuar, dos direcciones. Compartir estado entre componentes alejados con Pinia, cuando propagar eventos hacia arriba se vuelve pesado. Y el enrutamiento con Vue Router, en cuanto la aplicación tiene varias páginas.

En Options API

<Transition> y <TransitionGroup> son componentes integrados: se escriben exactamente igual. Solo las funciones de los hooks JavaScript cambian de sitio, a la opción methods.

EjercicioAñada una transición sobre la imagen del producto para que se funda al cambiar de variante. Necesitará una key ligada a la variante seleccionada.

Errores frecuentes

Usar las clases de Vue 2 .fade-enter y .fade-leave se renombraron como -enter-from y -leave-from. Nada se anima y no aparece ningún error.
Olvidar mode="out-in" Sin él, el elemento que entra y el que sale coexisten una fracción de segundo y se superponen.
Usar el índice como clave en un TransitionGroup Vue ya no puede distinguir el elemento nuevo de los demás y anima la entrada equivocada.
Probar un componente animado con Vue Test Utils Las transiciones se sustituyen por stubs por defecto. Pase global: { stubs: { 'transition-group': false } } para observar el renderizado real.
Newsletter

Las nuevas pruebas, tutoriales y proyectos, por correo.

Pruebas reproducibles, código versionado, resultados fechados. Nunca spam.