Vue.js - Das Progressive JavaScript-Framework
Einführung in Vue.js
Was ist Vue.js?
Vue.js (gesprochen "View") ist:
- Ein modernes JavaScript-Framework für den Aufbau von Benutzeroberflächen
- Progressiv - du kannst klein anfangen und bei Bedarf erweitern
- Reaktiv - die Benutzeroberfläche aktualisiert sich automatisch bei Datenänderungen
- Einfacher zu lernen als React oder Angular
- Perfekt für Single-Page Applications (SPAs)
Wichtig: Vue.js macht es einfach, interaktive Web-Apps zu erstellen, ohne sich um DOM-Manipulation kümmern zu müssen!
Warum Vue.js?
Vorteile gegenüber "Vanilla JavaScript":
Mit Vanilla JS (Counter-Beispiel):
let count = 0;
let element = document.querySelector("#count");
document.querySelector("#plus").addEventListener("click", () => {
count++;
element.textContent = count; // Manuelles DOM-Update
});Mit Vue.js:
<template>
<div>
<p>{{ count }}</p>
<button @click="count++">+</button>
</div>
</template>
<script>
export default {
data() {
return { count: 0 }
}
}
</script>Vue übernimmt automatisch:
- DOM-Updates wenn sich Daten ändern
- Event-Handling
- Komponenten-Struktur
Single File Components (SFC)
Was sind Single File Components?
Eine Single File Component ist eine .vue-Datei, die drei Bereiche enthält:
- Template - Das HTML (die Ansicht)
- Script - Die Logik (JavaScript)
- Style - Das CSS (die Styles)
Alles für eine Komponente in einer Datei!
Grundstruktur einer .vue-Datei
<template>
<!-- HTML-Code hier -->
<div>
<h1>Hallo Vue!</h1>
</div>
</template>
<script>
// JavaScript-Code hier
export default {
name: 'MeineKomponente'
}
</script>
<style>
/* CSS-Code hier */
h1 {
color: blue;
}
</style>Wichtig: Jeder Bereich ist optional, aber normalerweise brauchst du mindestens Template und Script.
Template-Bereich
Der Template-Bereich enthält das HTML der Komponente:
<template>
<div class="container">
<h1>Willkommen</h1>
<p>Dies ist eine Vue-Komponente</p>
<button>Klick mich</button>
</div>
</template>Wichtig:
- In Vue 3 kann das Template mehrere Root-Elemente haben
- In Vue 2 musste es genau ein Root-Element geben
Script-Bereich
Der Script-Bereich enthält die Logik der Komponente:
<script>
export default {
name: 'MeineKomponente',
data() {
return {
nachricht: 'Hallo Vue!',
zähler: 0
}
},
methods: {
klickHandler() {
console.log('Button geklickt!');
}
}
}
</script>Style-Bereich
Der Style-Bereich enthält das CSS:
<style>
.container {
padding: 20px;
background-color: #f0f0f0;
}
h1 {
color: blue;
}
</style>Scoped Styles (nur für diese Komponente):
<style scoped>
h1 {
color: blue; /* Gilt nur für <h1> in dieser Komponente */
}
</style>Data - Reaktive Daten
Das data() Property
Die data()-Funktion gibt ein Objekt mit allen reaktiven Daten zurück:
<template>
<div>
<p>Name: {{ name }}</p>
<p>Alter: {{ alter }}</p>
<p>Ist Student: {{ istStudent }}</p>
</div>
</template>
<script>
export default {
data() {
return {
name: 'Max',
alter: 25,
istStudent: true
}
}
}
</script>Wichtig:
- Alles in
data()ist reaktiv - Änderungen aktualisieren automatisch das Template - Immer eine Funktion verwenden, die ein Objekt zurückgibt
Daten im Template verwenden
Text-Interpolation mit :
<template>
<div>
<h1>{{ titel }}</h1>
<p>{{ nachricht }}</p>
<p>2 + 2 = {{ 2 + 2 }}</p>
<p>{{ vorname + ' ' + nachname }}</p>
</div>
</template>
<script>
export default {
data() {
return {
titel: 'Meine App',
nachricht: 'Willkommen bei Vue!',
vorname: 'Max',
nachname: 'Mustermann'
}
}
}
</script>Was funktioniert in :
- Variablen:
{{ name }} - Ausdrücke:
{{ zahl + 5 }} - Ternärer Operator:
{{ alter >= 18 ? 'Volljährig' : 'Minderjährig' }} - Methoden-Aufrufe:
{{ getName() }}
Was nicht funktioniert:
- Keine if/else-Statements
- Keine Schleifen
- Keine Zuweisungen
Template-Syntax
v-bind - Attribute binden
v-bind bindet Daten an HTML-Attribute:
Syntax: v-bind:attribut="wert" oder Kurzform :attribut="wert"
<template>
<div>
<img v-bind:src="bildUrl" v-bind:alt="bildBeschreibung">
<!-- Kurzform: -->
<img :src="bildUrl" :alt="bildBeschreibung">
<a :href="linkUrl">{{ linkText }}</a>
<div :class="klassenName">Inhalt</div>
<button :disabled="istDeaktiviert">Klick mich</button>
</div>
</template>
<script>
export default {
data() {
return {
bildUrl: 'foto.jpg',
bildBeschreibung: 'Mein Foto',
linkUrl: 'https://vuejs.org',
linkText: 'Vue.js Website',
klassenName: 'highlight',
istDeaktiviert: false
}
}
}
</script>v-bind für Klassen
String-Syntax:
<div :class="aktivKlasse"></div>Object-Syntax (dynamische Klassen):
<template>
<div :class="{ aktiv: istAktiv, highlight: istHighlighted }">
Inhalt
</div>
</template>
<script>
export default {
data() {
return {
istAktiv: true,
istHighlighted: false
}
}
}
</script>Ergebnis: <div class="aktiv">Inhalt</div>
Array-Syntax:
<div :class="[baseClass, aktivClass]"></div>v-bind für Styles
Object-Syntax:
<template>
<div :style="{ color: textColor, fontSize: fontSize + 'px' }">
Text
</div>
</template>
<script>
export default {
data() {
return {
textColor: 'red',
fontSize: 20
}
}
}
</script>Mit data-Objekt:
<template>
<div :style="styleObject">Text</div>
</template>
<script>
export default {
data() {
return {
styleObject: {
color: 'red',
fontSize: '20px',
fontWeight: 'bold'
}
}
}
}
</script>v-on - Event-Handling
v-on fügt Event-Listener hinzu:
Syntax: v-on:event="handler" oder Kurzform @event="handler"
<template>
<div>
<button v-on:click="count++">{{ count }}</button>
<!-- Kurzform: -->
<button @click="count++">{{ count }}</button>
<button @click="handleClick">Klick mich</button>
<input @input="handleInput" :value="text">
<form @submit.prevent="handleSubmit">
<button type="submit">Absenden</button>
</form>
</div>
</template>
<script>
export default {
data() {
return {
count: 0,
text: ''
}
},
methods: {
handleClick() {
console.log('Button geklickt!');
},
handleInput(event) {
this.text = event.target.value;
},
handleSubmit() {
console.log('Formular abgesendet');
}
}
}
</script>Event-Modifiers:
.prevent-event.preventDefault().stop-event.stopPropagation().once- Event nur einmal auslösen
v-model - Two-Way Data Binding
v-model erstellt eine bidirektionale Datenbindung (besonders für Formular-Inputs):
<template>
<div>
<input v-model="name" type="text" placeholder="Name">
<p>Hallo, {{ name }}!</p>
<textarea v-model="nachricht"></textarea>
<p>{{ nachricht }}</p>
<input v-model="istAktiv" type="checkbox">
<p>Checkbox: {{ istAktiv }}</p>
<select v-model="ausgewählt">
<option>Option 1</option>
<option>Option 2</option>
<option>Option 3</option>
</select>
<p>Ausgewählt: {{ ausgewählt }}</p>
</div>
</template>
<script>
export default {
data() {
return {
name: '',
nachricht: '',
istAktiv: false,
ausgewählt: ''
}
}
}
</script>Was macht v-model?
- Synchronisiert automatisch Input-Wert mit data
- Änderungen im Input aktualisieren data
- Änderungen in data aktualisieren Input
v-if, v-else-if, v-else - Bedingte Darstellung
Zeigt Elemente nur unter bestimmten Bedingungen:
<template>
<div>
<p v-if="istAngemeldet">Willkommen zurück!</p>
<p v-else>Bitte melde dich an.</p>
<div v-if="status === 'loading'">Lädt...</div>
<div v-else-if="status === 'success'">Erfolgreich!</div>
<div v-else-if="status === 'error'">Fehler aufgetreten</div>
<div v-else>Bereit</div>
<button @click="istAngemeldet = !istAngemeldet">
Toggle
</button>
</div>
</template>
<script>
export default {
data() {
return {
istAngemeldet: false,
status: 'loading'
}
}
}
</script>Wichtig: v-if entfernt Elemente vollständig aus dem DOM (nicht nur verstecken).
v-show - Element verstecken
v-show versteckt Elemente mit CSS (display: none):
<template>
<div>
<p v-show="istSichtbar">Dieser Text kann versteckt werden</p>
<button @click="istSichtbar = !istSichtbar">Toggle</button>
</div>
</template>
<script>
export default {
data() {
return {
istSichtbar: true
}
}
}
</script>v-if vs v-show:
v-if: Element wird aus DOM entfernt/hinzugefügt (höhere Umschaltkosten)v-show: Element bleibt im DOM, wird nur versteckt (höhere initiale Kosten)- Nutze
v-showfür häufiges Umschalten - Nutze
v-iffür seltene Bedingungsänderungen
v-for - Listen rendern
v-for wiederholt Elemente für jedes Element in einem Array:
Array durchlaufen:
<template>
<div>
<ul>
<li v-for="item in liste" :key="item">
{{ item }}
</li>
</ul>
</div>
</template>
<script>
export default {
data() {
return {
liste: ['Äpfel', 'Bananen', 'Orangen']
}
}
}
</script>Mit Index:
<template>
<ul>
<li v-for="(item, index) in liste" :key="index">
{{ index + 1 }}. {{ item }}
</li>
</ul>
</template>Array von Objekten:
<template>
<div>
<div v-for="person in personen" :key="person.id">
<h3>{{ person.name }}</h3>
<p>Alter: {{ person.alter }}</p>
</div>
</div>
</template>
<script>
export default {
data() {
return {
personen: [
{ id: 1, name: 'Max', alter: 25 },
{ id: 2, name: 'Anna', alter: 30 },
{ id: 3, name: 'Tom', alter: 22 }
]
}
}
}
</script>Wichtig: Immer :key verwenden für eindeutige Identifizierung!
v-for mit v-if kombinieren
<template>
<div>
<div v-for="person in personen" :key="person.id">
<div v-if="person.alter >= 18">
{{ person.name }} ({{ person.alter }})
</div>
</div>
</div>
</template>Best Practice: Lieber im Script filtern:
<template>
<div>
<div v-for="person in volljährigePersonen" :key="person.id">
{{ person.name }} ({{ person.alter }})
</div>
</div>
</template>
<script>
export default {
data() {
return {
personen: [
{ id: 1, name: 'Max', alter: 25 },
{ id: 2, name: 'Anna', alter: 16 },
{ id: 3, name: 'Tom', alter: 30 }
]
}
},
computed: {
volljährigePersonen() {
return this.personen.filter(p => p.alter >= 18);
}
}
}
</script>Methods - Methoden
Methoden definieren
Methoden sind Funktionen, die in der Komponente verwendet werden:
<template>
<div>
<button @click="grüßen">Grüßen</button>
<button @click="addieren(5, 3)">5 + 3</button>
<p>{{ getFullName() }}</p>
</div>
</template>
<script>
export default {
data() {
return {
vorname: 'Max',
nachname: 'Mustermann'
}
},
methods: {
grüßen() {
alert('Hallo!');
},
addieren(a, b) {
console.log(a + b);
},
getFullName() {
return this.vorname + ' ' + this.nachname;
}
}
}
</script>Wichtig: Auf data mit this zugreifen!
Methoden mit Parametern
<template>
<div>
<button @click="grüßePerson('Max')">Max grüßen</button>
<button @click="grüßePerson('Anna')">Anna grüßen</button>
<input @input="logInput($event)">
</div>
</template>
<script>
export default {
methods: {
grüßePerson(name) {
alert(`Hallo, ${name}!`);
},
logInput(event) {
console.log(event.target.value);
}
}
}
</script>Computed Properties
Was sind Computed Properties?
Computed Properties sind berechnete Eigenschaften, die:
- Von anderen Daten abhängen
- Gecacht werden (nur neu berechnet bei Datenänderung)
- Wie normale Properties verwendet werden (kein Funktionsaufruf)
<template>
<div>
<p>Vorname: {{ vorname }}</p>
<p>Nachname: {{ nachname }}</p>
<p>Vollständiger Name: {{ vollständigerName }}</p>
</div>
</template>
<script>
export default {
data() {
return {
vorname: 'Max',
nachname: 'Mustermann'
}
},
computed: {
vollständigerName() {
return this.vorname + ' ' + this.nachname;
}
}
}
</script>Computed vs Methods
Mit Method:
<p>{{ getFullName() }}</p> <!-- Wird bei jedem Render ausgeführt -->Mit Computed:
<p>{{ fullName }}</p> <!-- Wird nur bei Datenänderung neu berechnet -->Wann was verwenden:
- Computed: Für berechnete Werte, die von anderen Daten abhängen
- Methods: Für Aktionen und Event-Handler
Praktisches Beispiel
<template>
<div>
<h2>Einkaufsliste</h2>
<input v-model="neuerArtikel" @keyup.enter="hinzufügen">
<button @click="hinzufügen">Hinzufügen</button>
<p>Anzahl Artikel: {{ artikelAnzahl }}</p>
<ul>
<li v-for="(artikel, index) in artikel" :key="index">
{{ artikel }}
<button @click="entfernen(index)">X</button>
</li>
</ul>
<p v-if="istLeer">Liste ist leer</p>
</div>
</template>
<script>
export default {
data() {
return {
neuerArtikel: '',
artikel: []
}
},
computed: {
artikelAnzahl() {
return this.artikel.length;
},
istLeer() {
return this.artikel.length === 0;
}
},
methods: {
hinzufügen() {
if (this.neuerArtikel.trim() !== '') {
this.artikel.push(this.neuerArtikel);
this.neuerArtikel = '';
}
},
entfernen(index) {
this.artikel.splice(index, 1);
}
}
}
</script>
<style scoped>
li {
margin: 10px 0;
}
button {
margin-left: 10px;
}
</style>Komplettes Beispiel: Counter-App
<template>
<div class="counter">
<h1>Counter App</h1>
<div class="display">
<h2 :class="{ negative: count < 0, positive: count > 0 }">
{{ count }}
</h2>
</div>
<div class="controls">
<button @click="decrement">-</button>
<button @click="reset">Reset</button>
<button @click="increment">+</button>
</div>
<div class="input-group">
<input
v-model.number="step"
type="number"
placeholder="Schrittweite"
>
<button @click="addCustom">Hinzufügen</button>
</div>
<p>Status: {{ status }}</p>
</div>
</template>
<script>
export default {
name: 'CounterApp',
data() {
return {
count: 0,
step: 1
}
},
computed: {
status() {
if (this.count === 0) return 'Neutral';
if (this.count > 0) return 'Positiv';
return 'Negativ';
}
},
methods: {
increment() {
this.count++;
},
decrement() {
this.count--;
},
reset() {
this.count = 0;
},
addCustom() {
this.count += this.step;
}
}
}
</script>
<style scoped>
.counter {
max-width: 400px;
margin: 50px auto;
padding: 30px;
background-color: #f8f9fa;
border-radius: 10px;
text-align: center;
}
.display {
margin: 30px 0;
}
.display h2 {
font-size: 72px;
margin: 0;
transition: color 0.3s;
}
.negative {
color: #dc3545;
}
.positive {
color: #28a745;
}
.controls {
display: flex;
gap: 10px;
justify-content: center;
margin-bottom: 20px;
}
button {
padding: 10px 20px;
font-size: 18px;
border: none;
border-radius: 5px;
background-color: #007bff;
color: white;
cursor: pointer;
transition: background-color 0.3s;
}
button:hover {
background-color: #0056b3;
}
.input-group {
display: flex;
gap: 10px;
justify-content: center;
margin-top: 20px;
}
input {
padding: 10px;
font-size: 16px;
border: 1px solid #ccc;
border-radius: 5px;
width: 150px;
}
</style>Komplettes Beispiel: Todo-Liste
<template>
<div class="todo-app">
<h1>Todo Liste</h1>
<div class="input-section">
<input
v-model="neueAufgabe"
@keyup.enter="hinzufügen"
type="text"
placeholder="Neue Aufgabe..."
>
<button @click="hinzufügen">Hinzufügen</button>
</div>
<div class="filter-section">
<button
:class="{ active: filter === 'alle' }"
@click="filter = 'alle'"
>
Alle ({{ todos.length }})
</button>
<button
:class="{ active: filter === 'offen' }"
@click="filter = 'offen'"
>
Offen ({{ offenAnzahl }})
</button>
<button
:class="{ active: filter === 'erledigt' }"
@click="filter = 'erledigt'"
>
Erledigt ({{ erledigtAnzahl }})
</button>
</div>
<ul class="todo-list">
<li
v-for="todo in gefilterteTodos"
:key="todo.id"
:class="{ completed: todo.erledigt }"
>
<input
type="checkbox"
v-model="todo.erledigt"
>
<span>{{ todo.text }}</span>
<button @click="entfernen(todo.id)" class="delete">
✕
</button>
</li>
</ul>
<p v-if="todos.length === 0" class="empty">
Keine Aufgaben vorhanden. Füge eine hinzu!
</p>
</div>
</template>
<script>
export default {
name: 'TodoApp',
data() {
return {
neueAufgabe: '',
filter: 'alle',
nextId: 1,
todos: []
}
},
computed: {
gefilterteTodos() {
if (this.filter === 'offen') {
return this.todos.filter(t => !t.erledigt);
}
if (this.filter === 'erledigt') {
return this.todos.filter(t => t.erledigt);
}
return this.todos;
},
offenAnzahl() {
return this.todos.filter(t => !t.erledigt).length;
},
erledigtAnzahl() {
return this.todos.filter(t => t.erledigt).length;
}
},
methods: {
hinzufügen() {
const text = this.neueAufgabe.trim();
if (text !== '') {
this.todos.push({
id: this.nextId++,
text: text,
erledigt: false
});
this.neueAufgabe = '';
}
},
entfernen(id) {
this.todos = this.todos.filter(t => t.id !== id);
}
}
}
</script>
<style scoped>
.todo-app {
max-width: 600px;
margin: 50px auto;
padding: 30px;
background-color: white;
border-radius: 10px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
h1 {
text-align: center;
color: #333;
}
.input-section {
display: flex;
gap: 10px;
margin-bottom: 20px;
}
.input-section input {
flex: 1;
padding: 12px;
font-size: 16px;
border: 1px solid #ddd;
border-radius: 5px;
}
.input-section button {
padding: 12px 24px;
background-color: #007bff;
color: white;
border: none;
border-radius: 5px;
cursor: pointer;
font-size: 16px;
}
.input-section button:hover {
background-color: #0056b3;
}
.filter-section {
display: flex;
gap: 10px;
margin-bottom: 20px;
}
.filter-section button {
flex: 1;
padding: 10px;
background-color: #f0f0f0;
border: 1px solid #ddd;
border-radius: 5px;
cursor: pointer;
}
.filter-section button.active {
background-color: #007bff;
color: white;
border-color: #007bff;
}
.todo-list {
list-style: none;
padding: 0;
}
.todo-list li {
display: flex;
align-items: center;
gap: 10px;
padding: 12px;
border: 1px solid #ddd;
border-radius: 5px;
margin-bottom: 10px;
transition: background-color 0.3s;
}
.todo-list li:hover {
background-color: #f8f9fa;
}
.todo-list li.completed span {
text-decoration: line-through;
color: #999;
}
.todo-list input[type="checkbox"] {
cursor: pointer;
width: 20px;
height: 20px;
}
.todo-list span {
flex: 1;
}
.delete {
background-color: #dc3545;
color: white;
border: none;
border-radius: 3px;
padding: 5px 10px;
cursor: pointer;
}
.delete:hover {
background-color: #c82333;
}
.empty {
text-align: center;
color: #999;
margin-top: 20px;
}
</style>Wichtige Konzepte zusammengefasst
Reaktivität
Vue überwacht automatisch Änderungen in data():
data() {
return {
count: 0 // Reaktiv!
}
}Bei Änderung wird das Template automatisch aktualisiert:
this.count++; // Template wird neu gerendertthis-Keyword
In Methods und Computed Properties bezieht sich this auf die Komponenten-Instanz:
export default {
data() {
return {
name: 'Max'
}
},
methods: {
grüßen() {
console.log(this.name); // "Max"
}
}
}Vorsicht bei Arrow Functions:
methods: {
// Gut
grüßen() {
console.log(this.name); // Funktioniert
},
// Schlecht
grüßen: () => {
console.log(this.name); // this ist undefined!
}
}Lifecycle Hooks (Lebenszyklus)
Vue-Komponenten haben einen Lebenszyklus mit Hooks:
<script>
export default {
data() {
return {
daten: null
}
},
created() {
// Wird aufgerufen, wenn Komponente erstellt wurde
console.log('Komponente erstellt');
},
mounted() {
// Wird aufgerufen, wenn Komponente ins DOM eingefügt wurde
console.log('Komponente gemountet');
// Ideal für API-Calls
this.ladeDaten();
},
methods: {
ladeDaten() {
// Daten laden...
}
}
}
</script>Wichtigste Hooks:
created()- Nach Erstellung der Komponentemounted()- Nach Einfügen ins DOMupdated()- Nach jedem Updateunmounted()- Vor Entfernung aus DOM
Best Practices
1. Aussagekräftige Komponentennamen
// Gut
export default {
name: 'TodoList'
}
// Schlecht
export default {
name: 'Comp1'
}2. Data immer als Funktion
// Richtig
data() {
return {
count: 0
}
}
// Falsch
data: {
count: 0
}3. Key bei v-for verwenden
<!-- Gut -->
<div v-for="item in items" :key="item.id">
<!-- Schlecht -->
<div v-for="item in items">4. Computed für berechnete Werte
// Gut
computed: {
fullName() {
return this.firstName + ' ' + this.lastName;
}
}Schlecht (im Template): {{ firstName + ' ' + lastName }}
5. Scoped Styles verwenden
<style scoped>
/* Gilt nur für diese Komponente */
.button {
color: red;
}
</style>Debugging-Tipps
Vue DevTools
Installiere die Vue DevTools Browser-Extension:
- Chrome: Vue.js devtools
- Firefox: Vue.js devtools
Features:
- Komponenten-Baum ansehen
- Data und Props inspizieren
- Events verfolgen
- Vuex-State ansehen (fortgeschritten)
console.log() in Methods
methods: {
handleClick() {
console.log('Count:', this.count);
console.log('Data:', this.$data);
}
}Template mit
<template>
<div>
<p>Debug: {{ $data }}</p>
<p>Count: {{ count }}</p>
</div>
</template>Häufige Fehler
1. this vergessen
// Falsch
methods: {
increment() {
count++; // Fehler!
}
}
// Richtig
methods: {
increment() {
this.count++;
}
}2. Arrow Functions in methods/computed
// Falsch
methods: {
greet: () => {
console.log(this.name); // this ist undefined!
}
}
// Richtig
methods: {
greet() {
console.log(this.name);
}
}3. Computed Properties mit ()
```vue
<!-- Falsch -->
<p>{{ fullName() }}</p>
<!-- Richtig -->
<p>{{ fullName }}</p>
---
### 4. v-for ohne :key
```vue
<!-- Falsch -->
<div v-for="item in items">
<!-- Richtig -->
<div v-for="item in items" :key="item.id">Ressourcen zum Weiterlernen
Offizielle Dokumentation:
Interaktive Kurse:
Video-Tutorials:
Community:
Cheat Sheet
| Konzept | Syntax | Beispiel |
|---|---|---|
| Interpolation | | {{ name }} |
| Bind Attribute | :attr | :src="bildUrl" |
| Event | @event | @click="handler" |
| Two-Way Binding | v-model | v-model="text" |
| Conditional | v-if | v-if="isActive" |
| Loop | v-for | v-for="item in items" :key="item.id" |
| Computed | computed: {} | computed: { fullName() {} } |
| Method | methods: {} | methods: { greet() {} } |
HTML-Templates zum Kopieren
Einfaches Starter-Template
Dieses Template kannst du als Ausgangspunkt für Vue.js-Projekte verwenden:
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Vue.js App</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: Arial, sans-serif;
padding: 20px;
background-color: #f5f5f5;
}
#app {
max-width: 800px;
margin: 0 auto;
background-color: white;
padding: 30px;
border-radius: 10px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
h1 {
color: #42b883;
margin-bottom: 20px;
}
button {
background-color: #42b883;
color: white;
border: none;
padding: 10px 20px;
border-radius: 5px;
cursor: pointer;
font-size: 16px;
}
button:hover {
background-color: #35495e;
}
</style>
</head>
<body>
<div id="app">
<h1>{{ title }}</h1>
<p>{{ message }}</p>
<button @click="handleClick">Klick mich!</button>
</div>
<!-- Vue.js über CDN einbinden -->
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
<script>
const { createApp } = Vue;
createApp({
data() {
return {
title: 'Meine Vue.js App',
message: 'Hallo Vue!'
}
},
methods: {
handleClick() {
this.message = 'Button wurde geklickt!';
}
}
}).mount('#app');
</script>
</body>
</html>Counter-App Template
Ein vollständiges Counter-Beispiel:
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Vue Counter App</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: Arial, sans-serif;
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
}
#app {
background-color: white;
padding: 40px;
border-radius: 20px;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.3);
text-align: center;
min-width: 300px;
}
h1 {
color: #333;
margin-bottom: 30px;
}
.counter-display {
font-size: 72px;
font-weight: bold;
margin: 30px 0;
transition: color 0.3s;
}
.counter-display.positive {
color: #28a745;
}
.counter-display.negative {
color: #dc3545;
}
.counter-display.zero {
color: #333;
}
.button-group {
display: flex;
gap: 10px;
justify-content: center;
margin-bottom: 20px;
}
button {
background-color: #667eea;
color: white;
border: none;
padding: 12px 24px;
border-radius: 8px;
cursor: pointer;
font-size: 18px;
font-weight: bold;
transition: all 0.3s;
}
button:hover {
background-color: #764ba2;
transform: translateY(-2px);
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
}
button:active {
transform: translateY(0);
}
.status {
margin-top: 20px;
font-size: 18px;
color: #666;
}
</style>
</head>
<body>
<div id="app">
<h1>Counter App</h1>
<div
class="counter-display"
:class="{
positive: count > 0,
negative: count < 0,
zero: count === 0
}"
>
{{ count }}
</div>
<div class="button-group">
<button @click="decrement">-</button>
<button @click="reset">Reset</button>
<button @click="increment">+</button>
</div>
<div class="status">
Status: {{ status }}
</div>
</div>
<!-- Vue.js über CDN einbinden -->
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
<script>
const { createApp } = Vue;
createApp({
data() {
return {
count: 0
}
},
computed: {
status() {
if (this.count > 0) return 'Positiv';
if (this.count < 0) return 'Negativ';
return 'Neutral';
}
},
methods: {
increment() {
this.count++;
},
decrement() {
this.count--;
},
reset() {
this.count = 0;
}
}
}).mount('#app');
</script>
</body>
</html>Todo-Liste Template
Ein vollständiges Todo-Listen-Beispiel:
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Vue Todo Liste</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: Arial, sans-serif;
background-color: #f5f5f5;
padding: 20px;
}
#app {
max-width: 600px;
margin: 0 auto;
background-color: white;
padding: 30px;
border-radius: 10px;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
}
h1 {
text-align: center;
color: #333;
margin-bottom: 30px;
}
.input-group {
display: flex;
gap: 10px;
margin-bottom: 20px;
}
input[type="text"] {
flex: 1;
padding: 12px;
border: 2px solid #ddd;
border-radius: 5px;
font-size: 16px;
}
input[type="text"]:focus {
outline: none;
border-color: #42b883;
}
.btn {
padding: 12px 24px;
border: none;
border-radius: 5px;
cursor: pointer;
font-size: 16px;
font-weight: bold;
transition: all 0.3s;
}
.btn-add {
background-color: #42b883;
color: white;
}
.btn-add:hover {
background-color: #35495e;
}
.todo-list {
list-style: none;
}
.todo-item {
display: flex;
align-items: center;
gap: 10px;
padding: 12px;
border: 1px solid #ddd;
border-radius: 5px;
margin-bottom: 10px;
transition: background-color 0.3s;
}
.todo-item:hover {
background-color: #f8f9fa;
}
.todo-item.completed {
opacity: 0.6;
}
.todo-item.completed .todo-text {
text-decoration: line-through;
color: #999;
}
input[type="checkbox"] {
width: 20px;
height: 20px;
cursor: pointer;
}
.todo-text {
flex: 1;
font-size: 16px;
}
.btn-delete {
background-color: #dc3545;
color: white;
padding: 6px 12px;
border: none;
border-radius: 3px;
cursor: pointer;
font-size: 14px;
}
.btn-delete:hover {
background-color: #c82333;
}
.stats {
margin-top: 20px;
padding-top: 20px;
border-top: 2px solid #ddd;
text-align: center;
color: #666;
}
.empty-state {
text-align: center;
color: #999;
padding: 40px;
font-style: italic;
}
</style>
</head>
<body>
<div id="app">
<h1>📝 Todo Liste</h1>
<div class="input-group">
<input
v-model="newTodo"
@keyup.enter="addTodo"
type="text"
placeholder="Neue Aufgabe eingeben..."
>
<button @click="addTodo" class="btn btn-add">
Hinzufügen
</button>
</div>
<ul class="todo-list" v-if="todos.length > 0">
<li
v-for="todo in todos"
:key="todo.id"
class="todo-item"
:class="{ completed: todo.completed }"
>
<input
type="checkbox"
v-model="todo.completed"
>
<span class="todo-text">{{ todo.text }}</span>
<button
@click="deleteTodo(todo.id)"
class="btn-delete"
>
✕
</button>
</li>
</ul>
<div v-else class="empty-state">
Keine Aufgaben vorhanden. Füge deine erste Aufgabe hinzu!
</div>
<div class="stats" v-if="todos.length > 0">
<strong>{{ remainingCount }}</strong> von <strong>{{ totalCount }}</strong> Aufgaben offen
</div>
</div>
<!-- Vue.js über CDN einbinden -->
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
<script>
const { createApp } = Vue;
createApp({
data() {
return {
newTodo: '',
todos: [],
nextId: 1
}
},
computed: {
totalCount() {
return this.todos.length;
},
remainingCount() {
return this.todos.filter(t => !t.completed).length;
}
},
methods: {
addTodo() {
const text = this.newTodo.trim();
if (text !== '') {
this.todos.push({
id: this.nextId++,
text: text,
completed: false
});
this.newTodo = '';
}
},
deleteTodo(id) {
this.todos = this.todos.filter(t => t.id !== id);
}
}
}).mount('#app');
</script>
</body>
</html>Leeres Template zum Experimentieren
Ein minimales Template zum Ausprobieren:
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Vue.js Playground</title>
<style>
body {
font-family: Arial, sans-serif;
padding: 40px;
max-width: 800px;
margin: 0 auto;
}
h1 {
color: #42b883;
}
/* Deine Styles hier */
</style>
</head>
<body>
<div id="app">
<h1>{{ title }}</h1>
<!-- Dein HTML hier -->
</div>
<!-- Vue.js über CDN einbinden -->
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
<script>
const { createApp } = Vue;
createApp({
data() {
return {
title: 'Vue.js Playground'
// Deine Daten hier
}
},
computed: {
// Deine Computed Properties hier
},
methods: {
// Deine Methoden hier
}
}).mount('#app');
</script>
</body>
</html>Hinweise zur Verwendung
CDN vs. Build-Tools
Diese Templates verwenden Vue.js per CDN:
- ✓ Perfekt zum Lernen und Experimentieren
- ✓ Keine Installation nötig
- ✓ Alles in einer Datei
- ✗ Nicht für große Produktions-Apps geeignet
- ✗ Keine Single File Components (.vue Dateien)
Für größere Projekte:
- Verwende Vue CLI oder Vite
- Nutze Single File Components
- Profitiere von Build-Optimierung
So verwendest du die Templates:
- Kopiere den gewünschten Template-Code
- Erstelle eine neue
.htmlDatei - Füge den Code ein
- Öffne die Datei im Browser
- Beginne zu experimentieren!
Wichtige CDN-Links:
<!-- Vue 3 (neueste Version) -->
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
<!-- Vue 3 (spezifische Version) -->
<script src="https://unpkg.com/vue@3.3.4/dist/vue.global.js"></script>
<!-- Vue 3 (Production Build - minified) -->
<script src="https://unpkg.com/vue@3/dist/vue.global.prod.js"></script>Viel Erfolg beim Erstellen deiner Vue.js Apps!