00
:
00
:
00
:
00
Corso SEO AI - Usa SEOEMAIL al checkout per il 30% di sconto

Binding

Cos’è il Two-Way Binding

In Svelte, il binding bidirezionale (bind:) permette di sincronizzare automaticamente una variabile con un elemento del DOM. Quando l’utente modifica l’input, la variabile si aggiorna; quando la variabile cambia nel codice, l’input si aggiorna di conseguenza.

<script>
  let nome = $state('');
</script>

<!-- Two-way binding: nome e input sono sempre sincronizzati -->
<input bind:value={nome} />
<p>Ciao, {nome || 'sconosciuto'}!</p>

Senza bind:, dovresti gestire manualmente l’evento input:

<!-- Senza bind (one-way + handler manuale) -->
<input value={nome} oninput={(e) => nome = e.target.value} />

<!-- Con bind (molto più conciso) -->
<input bind:value={nome} />

bind:value per Input di Testo

Input text

<script>
  let nome = $state('');
  let email = $state('');
  let password = $state('');
</script>

<input type="text" bind:value={nome} placeholder="Nome" />
<input type="email" bind:value={email} placeholder="Email" />
<input type="password" bind:value={password} placeholder="Password" />

<p>Nome: {nome}</p>
<p>Email: {email}</p>

Textarea

<script>
  let messaggio = $state('');
</script>

<textarea bind:value={messaggio} rows="5" placeholder="Scrivi qui..."></textarea>
<p>Caratteri: {messaggio.length}</p>

Input numerici

Con type="number" e type="range", il valore viene automaticamente convertito in numero:

<script>
  let quantita = $state(1);
  let volume = $state(50);
</script>

<!-- bind:value restituisce un number, non una stringa -->
<input type="number" bind:value={quantita} min="0" max="100" />
<input type="range" bind:value={volume} min="0" max="100" />

<p>Quantità: {quantita} (tipo: {typeof quantita})</p>
<p>Volume: {volume}%</p>

Select

<script>
  let linguaggio = $state('');
  let linguaggi = ['JavaScript', 'TypeScript', 'Python', 'Rust', 'Go'];
</script>

<select bind:value={linguaggio}>
  <option value="">-- Seleziona --</option>
  {#each linguaggi as lang}
    <option value={lang}>{lang}</option>
  {/each}
</select>

<p>Hai scelto: {linguaggio || 'nessuno'}</p>

Select multiplo

<script>
  let selezionati = $state([]);
  let opzioni = ['Calcio', 'Tennis', 'Basket', 'Nuoto', 'Ciclismo'];
</script>

<!-- Con multiple, il valore è un array -->
<select bind:value={selezionati} multiple>
  {#each opzioni as sport}
    <option value={sport}>{sport}</option>
  {/each}
</select>

<p>Sport selezionati: {selezionati.join(', ') || 'nessuno'}</p>

bind:checked per Checkbox

<script>
  let accetto = $state(false);
  let notifiche = $state(true);
  let newsletter = $state(false);
</script>

<label>
  <input type="checkbox" bind:checked={accetto} />
  Accetto i termini e condizioni
</label>

<label>
  <input type="checkbox" bind:checked={notifiche} />
  Attiva notifiche
</label>

<label>
  <input type="checkbox" bind:checked={newsletter} />
  Iscriviti alla newsletter
</label>

<button disabled={!accetto}>Invia</button>

<p>Notifiche: {notifiche ? 'Attive' : 'Disattive'}</p>

bind:group per Radio e Checkbox Raggruppati

Radio buttons

bind:group collega più input radio alla stessa variabile:

<script>
  let colore = $state('rosso');
</script>

<fieldset>
  <legend>Scegli un colore:</legend>

  <label>
    <input type="radio" bind:group={colore} value="rosso" />
    Rosso
  </label>

  <label>
    <input type="radio" bind:group={colore} value="verde" />
    Verde
  </label>

  <label>
    <input type="radio" bind:group={colore} value="blu" />
    Blu
  </label>
</fieldset>

<p>Colore selezionato: {colore}</p>
<div style="width: 50px; height: 50px; background: {colore}"></div>

Checkbox raggruppate

Con le checkbox, bind:group gestisce un array di valori selezionati:

<script>
  let ingredienti = $state([]);
  let opzioni = ['Mozzarella', 'Pomodoro', 'Basilico', 'Prosciutto', 'Funghi', 'Olive'];
</script>

<fieldset>
  <legend>Scegli gli ingredienti:</legend>

  {#each opzioni as ingrediente}
    <label>
      <input type="checkbox" bind:group={ingredienti} value={ingrediente} />
      {ingrediente}
    </label>
  {/each}
</fieldset>

<p>Selezionati ({ingredienti.length}): {ingredienti.join(', ') || 'nessuno'}</p>

bind:this per Riferimento al DOM

bind:this salva un riferimento diretto all’elemento DOM in una variabile:

<script>
  let inputElement;
  let canvasElement;

  function focusSuInput() {
    inputElement.focus();
  }

  $effect(() => {
    if (canvasElement) {
      const ctx = canvasElement.getContext('2d');
      ctx.fillStyle = '#ff3e00';
      ctx.fillRect(10, 10, 100, 50);
    }
  });
</script>

<input bind:this={inputElement} placeholder="Clicca il bottone per il focus" />
<button onclick={focusSuInput}>Focus sull'input</button>

<canvas bind:this={canvasElement} width="200" height="100"></canvas>

bind:this con componenti

bind:this su un componente restituisce l’istanza del componente, permettendo di accedere alle proprietà e metodi esportati:

<!-- Figlio.svelte -->
<script>
  export function saluta() {
    alert('Ciao dal componente figlio!');
  }

  export function reset() {
    // logica di reset
  }
</script>

<!-- Genitore.svelte -->
<script>
  import Figlio from './Figlio.svelte';
  let figlioRef;
</script>

<Figlio bind:this={figlioRef} />
<button onclick={() => figlioRef.saluta()}>Chiama metodo del figlio</button>

Binding per Dimensioni

Svelte permette di leggere (ma non scrivere) le dimensioni degli elementi:

<script>
  let larghezza = $state(0);
  let altezza = $state(0);
  let offsetWidth = $state(0);
  let offsetHeight = $state(0);
</script>

<div bind:clientWidth={larghezza} bind:clientHeight={altezza}>
  <p>Questo div è largo {larghezza}px e alto {altezza}px</p>
  <p>Ridimensiona la finestra per vedere i valori cambiare.</p>
</div>

<!-- Anche offsetWidth e offsetHeight sono disponibili -->
<div bind:offsetWidth={offsetWidth} bind:offsetHeight={offsetHeight}>
  Offset: {offsetWidth} x {offsetHeight}
</div>

<style>
  div {
    border: 2px solid #ccc;
    padding: 1rem;
    resize: both;
    overflow: auto;
  }
</style>

I binding dimensionali disponibili sono:

  • bind:clientWidth / bind:clientHeight - dimensioni interne (padding incluso)
  • bind:offsetWidth / bind:offsetHeight - dimensioni esterne (border incluso)

Binding per Elementi Media

Audio e Video

<script>
  let currentTime = $state(0);
  let duration = $state(0);
  let paused = $state(true);
  let volume = $state(1);
  let muted = $state(false);

  let formattato = $derived(
    `${Math.floor(currentTime / 60)}:${String(Math.floor(currentTime % 60)).padStart(2, '0')}`
  );
</script>

<video
  bind:currentTime
  bind:duration
  bind:paused
  bind:volume
  bind:muted
  src="/video/demo.mp4"
  width="640"
>
  <track kind="captions" />
</video>

<div class="controlli">
  <button onclick={() => paused = !paused}>
    {paused ? 'Play' : 'Pausa'}
  </button>

  <span>{formattato} / {Math.floor(duration / 60)}:{String(Math.floor(duration % 60)).padStart(2, '0')}</span>

  <input type="range" bind:value={currentTime} min="0" max={duration} step="0.1" />

  <label>
    Volume:
    <input type="range" bind:value={volume} min="0" max="1" step="0.01" />
  </label>

  <label>
    <input type="checkbox" bind:checked={muted} />
    Muto
  </label>
</div>

I binding media disponibili sono:

  • bind:currentTime (lettura/scrittura)
  • bind:duration (solo lettura)
  • bind:paused (lettura/scrittura)
  • bind:volume (lettura/scrittura)
  • bind:muted (lettura/scrittura)
  • bind:playbackRate (lettura/scrittura)
  • bind:seeking (solo lettura)
  • bind:ended (solo lettura)
  • bind:buffered (solo lettura)

Binding per Contenteditable

<script>
  let html = $state('<b>Testo</b> <i>editabile</i>');
  let testo = $state('Solo testo');
</script>

<!-- bind:innerHTML per contenuto HTML -->
<div contenteditable="true" bind:innerHTML={html}></div>
<p>HTML: {html}</p>

<!-- bind:textContent per solo testo -->
<div contenteditable="true" bind:textContent={testo}></div>
<p>Testo: {testo}</p>

<style>
  div[contenteditable] {
    border: 1px solid #ccc;
    padding: 0.5rem;
    min-height: 2rem;
  }
</style>

Binding con Oggetti Select

I <select> in Svelte possono fare binding a valori non-stringa:

<script>
  let persone = $state([
    { id: 1, nome: 'Mario', eta: 30 },
    { id: 2, nome: 'Lucia', eta: 25 },
    { id: 3, nome: 'Giovanni', eta: 35 }
  ]);

  let selezionato = $state(persone[0]);
</script>

<select bind:value={selezionato}>
  {#each persone as persona}
    <!-- Il valore è l'intero oggetto, non solo una stringa -->
    <option value={persona}>{persona.nome}</option>
  {/each}
</select>

{#if selezionato}
  <p>{selezionato.nome} ha {selezionato.eta} anni (ID: {selezionato.id})</p>
{/if}

Esempio Pratico: Form Completo

<script>
  let form = $state({
    nome: '',
    email: '',
    tipo: 'personale',
    interessi: [],
    bio: '',
    accetto: false
  });

  let sommario = $derived.by(() => {
    return `${form.nome} (${form.email}) - ${form.tipo} - ${form.interessi.length} interessi`;
  });

  function invia() {
    console.log('Dati form:', $state.snapshot(form));
  }
</script>

<form onsubmit={(e) => { e.preventDefault(); invia(); }}>
  <input bind:value={form.nome} placeholder="Nome" required />
  <input type="email" bind:value={form.email} placeholder="Email" required />

  <select bind:value={form.tipo}>
    <option value="personale">Personale</option>
    <option value="lavoro">Lavoro</option>
  </select>

  {#each ['Sport', 'Musica', 'Cinema', 'Viaggi'] as interesse}
    <label>
      <input type="checkbox" bind:group={form.interessi} value={interesse} />
      {interesse}
    </label>
  {/each}

  <textarea bind:value={form.bio} placeholder="Biografia"></textarea>

  <label>
    <input type="checkbox" bind:checked={form.accetto} />
    Accetto i termini
  </label>

  <button type="submit" disabled={!form.accetto}>Invia</button>
</form>

<p>{sommario}</p>

Best Practice

  1. Usa bind:value per input di testo, select e textarea – è più conciso di gestire gli eventi manualmente
  2. Preferisci il one-way data flow quando possibile: il binding bidirezionale può rendere il flusso dati meno prevedibile
  3. Usa bind:this con parsimonia: accedere direttamente al DOM è spesso non necessario in Svelte
  4. I binding dimensionali hanno un costo: usano ResizeObserver internamente, evita di usarli su centinaia di elementi
  5. Attenzione a bind:innerHTML: come {@html}, è vulnerabile a XSS con input non fidati