Skip to content
← Todas las preguntas
Intermedio

¿Cómo funcionan Transition y TransitionGroup?

ComponentesAnimación

Vue incluye dos componentes integrados para animación. <Transition> anima un único elemento al entrar o salir. <TransitionGroup> anima elementos en una lista.

Transition

Envuelve un único elemento (o componente) que alterna con v-if o v-show. Vue añade clases CSS en cada etapa del ciclo de entrada y salida.

vue
<template>
  <button @click="show = !show">Alternar</button>

  <Transition name="fade">
    <p v-if="show">Hola</p>
  </Transition>
</template>

<style>
.fade-enter-active,
.fade-leave-active {
  transition: opacity 0.3s ease;
}

.fade-enter-from,
.fade-leave-to {
  opacity: 0;
}
</style>

El patrón de nombres de clase con un name de "fade":

.fade-enter-from   → .fade-enter-active → .fade-enter-to
.fade-leave-from   → .fade-leave-active → .fade-leave-to

Modos de Transition

Al intercambiar entre dos elementos, ambos son visibles al mismo tiempo por defecto. Usa mode="out-in" para animar la salida del elemento antiguo primero, y luego la entrada del nuevo.

vue
<template>
  <Transition name="fade" mode="out-in">
    <p v-if="isActive" key="active">Activo</p>
    <p v-else key="inactive">Inactivo</p>
  </Transition>
</template>

Añade key cuando intercambias elementos del mismo tipo (<p> por <p>), de lo contrario Vue reutiliza el nodo DOM y la transición no se activa.

Consejo de rendimiento

Usa solo transform y opacity. Estas propiedades están aceleradas por GPU y no desencadenan recálculos de layout.

css
.slide-enter-active,
.slide-leave-active {
  transition: transform 0.3s ease, opacity 0.3s ease;
}

.slide-enter-from {
  transform: translateX(-12px);
  opacity: 0;
}

.slide-leave-to {
  transform: translateX(12px);
  opacity: 0;
}

Evita animar height, width, margin o top, ya que provocan costosos recálculos de layout.

TransitionGroup

Para listas renderizadas con v-for. Cada hijo debe tener un :key único.

vue
<template>
  <TransitionGroup name="list" tag="ul">
    <li v-for="item in items" :key="item.id">
      {{ item.name }}
    </li>
  </TransitionGroup>
</template>

<style>
.list-enter-active,
.list-leave-active {
  transition: all 0.3s ease;
}

.list-enter-from,
.list-leave-to {
  opacity: 0;
  transform: translateX(-20px);
}

/* Anima los elementos restantes cuando se elimina uno */
.list-move {
  transition: transform 0.3s ease;
}

.list-leave-active {
  position: absolute;
}
</style>

La clase .list-move anima los elementos que cambian de posición cuando un hermano entra o sale. Establecer position: absolute en el elemento que sale permite que los restantes fluyan a su lugar de forma fluida.

Animaciones de lista escalonadas

Usa hooks de JavaScript con data-index para efectos en cascada:

vue
<template>
  <TransitionGroup tag="ul" :css="false" @before-enter="onBeforeEnter" @enter="onEnter">
    <li v-for="(item, index) in items" :key="item.id" :data-index="index">
      {{ item.name }}
    </li>
  </TransitionGroup>
</template>

<script setup>
function onBeforeEnter(el: HTMLElement) {
  el.style.opacity = '0'
  el.style.transform = 'translateY(12px)'
}

function onEnter(el: HTMLElement, done: () => void) {
  const delay = Number(el.dataset.index) * 80
  setTimeout(() => {
    el.style.transition = 'all 0.25s ease'
    el.style.opacity = '1'
    el.style.transform = 'translateY(0)'
    setTimeout(done, 250)
  }, delay)
}
</script>

Transition vs TransitionGroup

<Transition><TransitionGroup>
HijosUn elemento o componenteMúltiples (lista con v-for)
Prop modeCompatible (out-in, in-out)No compatible
Clase .moveNoSí (anima reordenaciones)
Prop tagNo (no renderiza un wrapper)Sí (renderiza un elemento wrapper)

Publicado bajo la licencia MIT.