<audio>, <video> und <img> haben in Svelte eingebaute Bindings für ihre wichtigsten Eigenschaften. Damit lassen sich Custom-Player ohne großen Aufwand bauen — Wiedergabe steuern, aktuelle Position lesen, Lautstärke synchronisieren oder native Bildmaße ermitteln. Dieser Artikel zeigt alle verfügbaren Media-Bindings und ein vollständiges Mini-Player-Beispiel.
Two-Way-Bindings für Audio und Video
| Binding | Beschreibung |
|---|---|
bind:currentTime | Aktuelle Wiedergabe-Position (Sek.) |
bind:playbackRate | Geschwindigkeit (1 = normal) |
bind:paused | true wenn pausiert |
bind:volume | Lautstärke 0–1 |
bind:muted | Stumm |
Diese Werte lassen sich lesen und schreiben: Wer paused = false setzt, startet die Wiedergabe.
<script>
let paused = $state(true);
let currentTime = $state(0);
let duration = $state(0);
let volume = $state(1);
</script>
<audio
src="/audio/sample.mp3"
bind:paused
bind:currentTime
bind:duration
bind:volume
></audio>
<button onclick={() => paused = !paused}>
{paused ? 'Play' : 'Pause'}
</button>
<input type="range" bind:value={currentTime} min="0" max={duration} step="0.1" />
<span>{currentTime.toFixed(1)} / {duration.toFixed(1)} s</span>
<input type="range" bind:value={volume} min="0" max="1" step="0.01" />Beachten: volume = 1.5 setzt natürlich nichts — der Browser cappt automatisch auf 1.
Read-only-Bindings
Diese Werte werden vom Browser bestimmt und können nur gelesen werden:
| Binding | Beschreibung |
|---|---|
bind:duration | Gesamtdauer in Sekunden |
bind:buffered | TimeRanges der gepufferten Bereiche |
bind:seekable | TimeRanges der seekbaren Bereiche |
bind:seeking | true während eines Seek |
bind:ended | true wenn Ende erreicht |
bind:readyState | 0–4 (HAVE_NOTHING–HAVE_ENOUGH_DATA) |
bind:played | TimeRanges der bereits abgespielten Teile |
Diese sind trotzdem reaktiv: Wenn der Browser sie ändert (z. B. weil mehr Daten geladen sind), aktualisiert sich die Variable automatisch.
Video-spezifische Bindings
Bei <video> zusätzlich:
| Binding | Beschreibung |
|---|---|
bind:videoWidth | Native Videobreite (px) |
bind:videoHeight | Native Videohöhe (px) |
Vor dem ersten Frame sind beide 0. Sobald Metadaten geladen sind, stehen die Werte zur Verfügung.
<script>
let videoWidth = $state(0);
let videoHeight = $state(0);
</script>
<video
src="/clip.mp4"
controls
bind:videoWidth
bind:videoHeight
></video>
{#if videoWidth > 0}
<p>Auflösung: {videoWidth} × {videoHeight}</p>
{/if}Bilder: naturalWidth und naturalHeight
Bei <img> lassen sich die nativen Bildmaße auslesen — die unskalierten Original-Pixel-Werte:
<script>
let naturalWidth = $state(0);
let naturalHeight = $state(0);
</script>
<img
src="/photo.jpg"
alt=""
style="width: 200px"
bind:naturalWidth
bind:naturalHeight
/>
{#if naturalWidth > 0}
<p>Original: {naturalWidth} × {naturalHeight} px</p>
{/if}naturalWidth ist 0, bis das Bild geladen ist — dann reaktiv aktualisiert.
Vollständiger Custom-Audio-Player
<script>
let { src } = $props();
let paused = $state(true);
let currentTime = $state(0);
let duration = $state(0);
let volume = $state(1);
let muted = $state(false);
function format(seconds) {
const m = Math.floor(seconds / 60);
const s = Math.floor(seconds % 60).toString().padStart(2, '0');
return `${m}:${s}`;
}
</script>
<audio
{src}
bind:paused
bind:currentTime
bind:duration
bind:volume
bind:muted
></audio>
<div class="player">
<button onclick={() => paused = !paused} aria-label={paused ? 'Play' : 'Pause'}>
{paused ? '>' : '||'}
</button>
<input
type="range"
bind:value={currentTime}
min="0"
max={duration}
step="0.1"
aria-label="Position"
/>
<span>{format(currentTime)} / {format(duration)}</span>
<button onclick={() => muted = !muted} aria-label={muted ? 'Unmute' : 'Mute'}>
{muted ? 'off' : 'on'}
</button>
<input
type="range"
bind:value={volume}
min="0"
max="1"
step="0.01"
aria-label="Lautstärke"
/>
</div>
<style>
.player { display: flex; gap: 0.5em; align-items: center; }
</style>Kein einziger addEventListener-Aufruf, keine eigene State-Logik — Svelte macht alles über die Bindings.
Beispiel: Auto-Play wenn sichtbar
<script>
let paused = $state(true);
let video;
$effect(() => {
if (!video) return;
const observer = new IntersectionObserver(([entry]) => {
paused = !entry.isIntersecting;
});
observer.observe(video);
return () => observer.disconnect();
});
</script>
<video bind:this={video} bind:paused src="/clip.mp4" muted loop></video>bind:paused und bind:this zusammen — das Video startet, sobald es im Viewport ist, und pausiert bei Ausscrollen.
Häufige Stolperfallen
duration = 0 direkt nach Mount.
Der Wert ist erst gesetzt, wenn die Metadaten geladen sind. Mit {#if duration > 0}...{/if} schützen.
paused = false ohne User-Interaction.
Browser blockieren oft Auto-Play von Audio. Stummgeschaltetes Video (muted) funktioniert meist; Audio braucht Nutzer-Geste.
currentTime als Float ungenau anzeigen.
{currentTime} zeigt z. B. 12.345678901. Mit currentTime.toFixed(1) formatieren.
Bindings funktionieren nicht beim ersten Render.
Erst nachdem das Element im DOM ist und Metadaten geladen sind. Bei kritischer Logik im $effect arbeiten und Werte prüfen.
bind:duration schreiben.
Geht nicht — Read-only-Binding. Compiler zeigt eine Warnung.