Modul 5: Styling

Transitions & Animationen

Flüssige Übergänge und Animationen, die deine UI zum Leben erwecken – eingebaut in Vue.

Warum Transitions in Vue?

Vue bietet ein eingebautes Transition-System, das automatisch CSS-Klassen hinzufügt und entfernt, wenn Elemente erscheinen, verschwinden oder sich ändern. Kein manuelles Klassen-Togglen, keine requestAnimationFrame-Hacks – einfach <Transition> drumwickeln und fertig.

Die Transition-Komponente

<Transition> umwickelt ein einzelnes Element und fügt automatisch CSS-Klassen für Enter- und Leave-Phasen hinzu:

FadeBeispiel.vue
<script setup>
import { ref } from 'vue'

const sichtbar = ref(true)
</script>

<template>
  <button @click="sichtbar = !sichtbar">Umschalten</button>

  <Transition name="fade">
    <p v-if="sichtbar">Hallo, ich fade ein und aus! 👋</p>
  </Transition>
</template>

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

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

Die 6 Transition-Klassen

Vue fügt automatisch diese Klassen hinzu:

Enter: *-enter-from*-enter-active*-enter-to

Leave: *-leave-from*-leave-active*-leave-to

Der name-Prop bestimmt das Prefix (hier: fade-).

Beliebte Transition-Patterns

Mit verschiedenen CSS-Properties und Timings erstellst du vielseitige Übergänge:

TransitionPatterns.vue
<style>
/* Slide-Down */
.slide-enter-active,
.slide-leave-active {
  transition: all 0.3s ease;
}
.slide-enter-from {
  opacity: 0;
  transform: translateY(-10px);
}
.slide-leave-to {
  opacity: 0;
  transform: translateY(10px);
}

/* Scale */
.scale-enter-active,
.scale-leave-active {
  transition: all 0.2s ease;
}
.scale-enter-from,
.scale-leave-to {
  opacity: 0;
  transform: scale(0.9);
}

/* Slide-Fade (kombiniert) */
.slide-fade-enter-active {
  transition: all 0.3s ease-out;
}
.slide-fade-leave-active {
  transition: all 0.2s ease-in;
}
.slide-fade-enter-from {
  opacity: 0;
  transform: translateX(20px);
}
.slide-fade-leave-to {
  opacity: 0;
  transform: translateX(-20px);
}
</style>

TransitionGroup für Listen

Für Listen mit dynamischen Einträgen gibt es <TransitionGroup>. Es animiert nicht nur Enter/Leave, sondern auch die Bewegung anderer Elemente mit der speziellen *-move-Klasse.

AnimierteListe.vue
<script setup>
import { ref } from 'vue'

const items = ref([
  { id: 1, text: 'Erstes Element' },
  { id: 2, text: 'Zweites Element' },
  { id: 3, text: 'Drittes Element' },
])

let nextId = 4

function hinzufuegen() {
  const index = Math.floor(Math.random() * (items.value.length + 1))
  items.value.splice(index, 0, {
    id: nextId++,
    text: 'Element ' + (nextId - 1),
  })
}

function entfernen(id: number) {
  items.value = items.value.filter(item => item.id !== id)
}
</script>

<template>
  <button @click="hinzufuegen">Hinzufügen</button>

  <TransitionGroup name="list" tag="ul">
    <li v-for="item in items" :key="item.id" @click="entfernen(item.id)">
      {{ item.text }}
    </li>
  </TransitionGroup>
</template>

<style>
.list-enter-active,
.list-leave-active {
  transition: all 0.4s ease;
}
.list-enter-from,
.list-leave-to {
  opacity: 0;
  transform: translateX(30px);
}
/* FLIP-Animation für verbleibende Elemente */
.list-move {
  transition: transform 0.4s ease;
}
/* Leave-Elemente aus dem Flow nehmen */
.list-leave-active {
  position: absolute;
}
</style>
💡

Der move-Trick

Die *-move-Klasse nutzt die FLIP-Technik (First, Last, Invert, Play). Vue berechnet automatisch die Position vor und nach der Änderung und animiert den Unterschied. Vergiss nicht, Leave-Elemente mit position: absolute aus dem Flow zu nehmen!

JavaScript-Hooks

Für komplexere Animationen kannst du JavaScript-Hooks verwenden – perfekt für berechnete Werte, gestaffelte Animationen oder die Web Animations API:

JsHooks.vue
<template>
  <TransitionGroup
    @before-enter="vorEintritt"
    @enter="eintritt"
    @leave="austritt"
    :css="false"
  >
    <div v-for="(item, i) in items" :key="item.id" :data-index="i">
      {{ item.text }}
    </div>
  </TransitionGroup>
</template>

<script setup>
function vorEintritt(el) {
  el.style.opacity = 0
  el.style.transform = 'translateY(20px)'
}

function eintritt(el, done) {
  // Gestaffelte Animation basierend auf Index
  const delay = el.dataset.index * 100

  el.animate([
    { opacity: 0, transform: 'translateY(20px)' },
    { opacity: 1, transform: 'translateY(0)' },
  ], {
    duration: 400,
    delay,
    easing: 'ease-out',
    fill: 'forwards',
  }).onfinish = done
}

function austritt(el, done) {
  el.animate([
    { opacity: 1, transform: 'scale(1)' },
    { opacity: 0, transform: 'scale(0.8)' },
  ], {
    duration: 200,
    easing: 'ease-in',
  }).onfinish = done
}
</script>
ℹ️

:css='false'

Setze :css="false", wenn du Animationen komplett in JavaScript steuerst. Das verhindert, dass Vue nach CSS-Klassen sucht, und gibt dir volle Kontrolle über Timing und done()-Callbacks.

Animation beim ersten Laden

Mit dem appear-Prop animierst du Elemente auch beim initialen Rendering:

AppearTransition.vue
<Transition name="fade" appear>
  <div class="hero-section">
    <h1>Willkommen!</h1>
  </div>
</Transition>

<!-- Eigene Appear-Klassen -->
<Transition
  appear
  appear-from-class="opacity-0 scale-90"
  appear-active-class="transition duration-500 ease-out"
  appear-to-class="opacity-100 scale-100"
>
  <div>Animiert beim Laden</div>
</Transition>

State-getriebene Animationen

Nicht alle Animationen brauchen <Transition>. Für kontinuierliche, datengesteuerte Animationen nutze CSS-Transitions direkt mit reaktiven Werten:

StateAnimation.vue
<script setup>
import { ref, computed } from 'vue'

const fortschritt = ref(0)

const balkenStyle = computed(() => ({
  width: fortschritt.value + '%',
  transition: 'width 0.5s ease',
}))
</script>

<template>
  <input type="range" v-model.number="fortschritt" min="0" max="100" />

  <div class="h-4 bg-gray-200 rounded-full overflow-hidden">
    <div class="h-full bg-emerald-500 rounded-full" :style="balkenStyle" />
  </div>
</template>

Rails-Vergleich: Turbo Morphing vs. Vue Transitions

🛤️

Von Turbo zu Vue

In Rails 7+ nutzt Turbo Morphing idiomorph, um DOM-Änderungen zu minimieren. Das ist clever, aber es gibt dir kaum Kontrolle über wie die Änderung visuell passiert.

Vue's Transition-System ist grundlegend anders: Du definierst explizit, wie Elemente erscheinen, verschwinden und sich bewegen. Statt „der DOM ändert sich möglichst unsichtbar" sagst du „zeige dem User die Änderung mit dieser Animation".

Turbo: Unsichtbare DOM-Diffs · Vue: Sichtbare, kontrollierte Übergänge

Third-Party Animations

Vue's Transition-Klassen lassen sich hervorragend mit Bibliotheken wie Animate.css oder Motion One kombinieren:

AnimateCSS.vue
<!-- Mit Animate.css -->
<Transition
  enter-active-class="animate__animated animate__fadeInUp"
  leave-active-class="animate__animated animate__fadeOutDown"
>
  <div v-if="sichtbar">Animiert mit Animate.css</div>
</Transition>

<!-- Mit @vueuse/motion -->
<script setup>
import { useMotion } from '@vueuse/motion'

const zielRef = ref(null)
useMotion(zielRef, {
  initial: { opacity: 0, y: 50 },
  enter: { opacity: 1, y: 0, transition: { duration: 500 } },
})
</script>

<template>
  <div ref="zielRef">Smooth entrance!</div>
</template>
💡

Empfehlung

Für einfache Transitions reichen Vue's eingebaute CSS-Klassen völlig aus. Greife nur zu Bibliotheken, wenn du komplexe Choreographien, physikbasierte Animationen oder Spring-Dynamics brauchst.

Live-Demo: Animierte To-Do-Liste

TransitionGroup Demo
Interaktiv
  • 🛒 Einkaufen gehen
  • 📧 Emails beantworten
  • 💻 Vue Tutorial durcharbeiten

3 Items · Klicke ✕ zum Entfernen oder nutze die Buttons oben.