Kurs Svelte i SvelteKit · Moduł 1: Wprowadzenie do Svelte

Obsługa zdarzeń

4 min czytania ·
W tej lekcji7

W Svelte 5 obsługujemy zdarzenia atrybutami zdarzeń: onclick, oninput, onkeydown, onsubmit... To zwykłe atrybuty z funkcją w nawiasach klamrowych. To jak reagowanie na sygnały z rogu wojennego!

Podstawowa składnia

<script>
  function handleClick() {
    alert('Wiking zaatakował!');
  }
</script>

<button onclick={handleClick}>
  Atakuj!
</button>

Nazwa atrybutu to on + nazwa zdarzenia, małymi literami: onclick nasłuchuje zdarzenia click. Wielkość liter ma znaczenie - onClick to coś innego!

Funkcje inline

Możesz też pisać funkcje bezpośrednio:

<button onclick={() => alert('SKÅL!')}>
  Toast!
</button>

Skrót: {onclick}

Gdy funkcja nazywa się tak samo jak atrybut, możesz użyć skrótu:

<script>
  function onclick() {
    alert('Róg wojenny zagrzmiał!');
  }
</script>

<button {onclick}>Zadmij w róg</button>

Obiekt zdarzenia

Funkcja otrzymuje obiekt zdarzenia jako argument:

<script>
  function handleClick(event) {
    console.log('Kliknięto w:', event.target);
    console.log('Pozycja X:', event.clientX);
    console.log('Pozycja Y:', event.clientY);
  }
</script>

<button onclick={handleClick} class="battlefield">
  Kliknij na pole bitwy
</button>

Popularne zdarzenia

<script>
  let message = $state('');
  let isHovering = $state(false);

  function handleSubmit(event) {
    event.preventDefault(); // formularz nie przeładuje strony
    message = 'Formularz wysłany!';
  }
</script>

<!-- Zdarzenia myszy -->
<button onclick={() => message = 'Kliknięto!'}>Kliknij</button>
<button ondblclick={() => message = 'Podwójne kliknięcie!'}>Podwójne</button>

<button
  onmouseenter={() => isHovering = true}
  onmouseleave={() => isHovering = false}
  class:hovering={isHovering}
>
  {isHovering ? 'Najechano!' : 'Najedź na mnie'}
</button>

<!-- Zdarzenia klawiatury -->
<input
  onkeydown={(e) => message = `Naciśnięto: ${e.key}`}
  onfocus={() => message = 'Input aktywny'}
  onblur={() => message = 'Input nieaktywny'}
  placeholder="Wpisz coś..."
>

<!-- Zdarzenia formularza -->
<form onsubmit={handleSubmit}>
  <button type="submit">Wyślij</button>
</form>

<p>{message}</p>

Zamiast modyfikatorów - zwykły JavaScript

W Svelte 4 do zdarzeń dopisywało się modyfikatory, np. on:submit|preventDefault. Atrybuty zdarzeń w Svelte 5 nie mają modyfikatorów - całą logikę piszesz w funkcji obsługi, w jednym miejscu:

<script>
  let log = $state([]);

  function handleSubmit(event) {
    event.preventDefault(); // zamiast |preventDefault
    log.push('Wysłano rozkaz');
  }

  function handleShield(event) {
    event.stopPropagation(); // zamiast |stopPropagation
    log.push('Tarcza zatrzymała cios');
  }

  function handleBoard(event) {
    if (event.target !== event.currentTarget) return; // zamiast |self
    log.push('Kliknięto pokład');
  }

  // zamiast |once - funkcja, która wywoła handler tylko raz
  function once(fn) {
    return function (event) {
      if (fn) fn.call(this, event);
      fn = null;
    };
  }
</script>

<form onsubmit={handleSubmit}>
  <button type="submit">Wyślij rozkaz</button>
</form>

<button onclick={once(() => log.push('Róg zadął'))}>Tylko raz!</button>

<!-- capture: dopisz "capture" na końcu nazwy zdarzenia -->
<div onclickcapture={() => log.push('Faza capture')}>
  <button onclick={handleBoard}>Pokład</button>
  <button onclick={handleShield}>Tarcza</button>
</div>

<ul>
  {#each log as entry}
    <li>{entry}</li>
  {/each}
</ul>

Ściąga - starsza składnia (Svelte 4) i jej odpowiednik w Svelte 5:

  • on:click={fn} → onclick={fn}
  • modyfikator preventDefault → event.preventDefault() w funkcji
  • modyfikator stopPropagation → event.stopPropagation() w funkcji
  • modyfikator once → funkcja-opakowanie, np. once(fn)
  • modyfikator self → if (event.target === event.currentTarget)
  • modyfikator capture → onclickcapture={fn}

Dyrektywę on: zobaczysz jeszcze w starszym kodzie - w Svelte 5 jest przestarzała i nie wolno mieszać jej z atrybutami zdarzeń w jednym komponencie.

Przykład: Interaktywny wojownik

<script>
  let health = $state(100);
  let energy = $state(100);
  let log = $state([]);

  function attack() {
    if (energy >= 20) {
      energy -= 20;
      log.push('Atak! -20 energii');
    }
  }

  function defend() {
    if (energy >= 10) {
      energy -= 10;
      health = Math.min(100, health + 5);
      log.push('Obrona! +5 zdrowia');
    }
  }

  function rest() {
    energy = Math.min(100, energy + 30);
    log.push('Odpoczynek! +30 energii');
  }
</script>

<div class="warrior-panel">
  <h2>Panel Wojownika</h2>

  <div class="stats">
    <div class="stat">
      <span>Zdrowie:</span>
      <progress value={health} max="100"></progress>
      <span>{health}/100</span>
    </div>
    <div class="stat">
      <span>Energia:</span>
      <progress value={energy} max="100"></progress>
      <span>{energy}/100</span>
    </div>
  </div>

  <div class="actions">
    <button onclick={attack} disabled={energy < 20}>Atak (20 energii)</button>
    <button onclick={defend} disabled={energy < 10}>Obrona (10 energii)</button>
    <button onclick={rest}>Odpoczynek</button>
  </div>

  <div class="log">
    <h3>Dziennik:</h3>
    {#each log.slice(-5) as entry}
      <p>{entry}</p>
    {/each}
  </div>
</div>

To podstawy zdarzeń w Svelte. Proste, prawda? Jak dobrze naostrzony topór!

Kod do tej lekcji: App.svelte
<script>
  // Exercise: Event handling with event attributes (onclick, onkeydown)

  let health = $state(100);
  let energy = $state(100);
  let log = $state([]);
  let warriorName = 'Ragnar';

  function attack() {
    if (energy >= 20) {
      energy -= 20;
      addLog('Attack! -20 energy');
    }
  }

  function defend() {
    if (energy >= 10) {
      energy -= 10;
      health = Math.min(100, health + 10);
      addLog('Defend! +10 health');
    }
  }

  function rest() {
    energy = Math.min(100, energy + 30);
    addLog('Rest! +30 energy');
  }

  function handleKeydown(event) {
    if (event.key === 'a') attack();
    if (event.key === 'd') defend();
    if (event.key === 'r') rest();
  }

  function addLog(message) {
    log = [...log.slice(-4), message];
  }
</script>

<svelte:window onkeydown={handleKeydown} />

<main class="battle-panel">
  <h1>Battle Panel: {warriorName}</h1>

  <div class="bars">
    <div class="bar">
      <span class="bar-label">Health</span>
      <div class="bar-track">
        <div class="bar-fill health" style="width: {health}%"></div>
      </div>
      <span>{health}/100</span>
    </div>

    <div class="bar">
      <span class="bar-label">Energy</span>
      <div class="bar-track">
        <div class="bar-fill energy" style="width: {energy}%"></div>
      </div>
      <span>{energy}/100</span>
    </div>
  </div>

  <div class="actions">
    <button
      onclick={attack}
      disabled={energy < 20}
      class="attack"
    >
      Attack (A) - 20 energy
    </button>

    <button
      onclick={defend}
      disabled={energy < 10}
      class="defend"
    >
      Defend (D) - 10 energy
    </button>

    <button
      onclick={rest}
      class="rest"
    >
      Rest (R)
    </button>
  </div>

  <div class="log">
    <h3>Log:</h3>
    {#each log as entry}
      <p>{entry}</p>
    {:else}
      <p class="empty">No actions...</p>
    {/each}
  </div>

  <p class="hint">Use keys A, D, R to control!</p>
</main>

<style>
  .battle-panel {
    font-family: 'Georgia', serif;
    max-width: 500px;
    margin: 0 auto;
    padding: 2rem;
    background: linear-gradient(135deg, #1a0f0a 0%, #2c1810 100%);
    color: #e8d5b7;
    min-height: 100vh;
  }

  h1 {
    color: #d4af37;
    text-align: center;
    margin-bottom: 2rem;
  }

  .bars {
    margin-bottom: 2rem;
  }

  .bar {
    margin-bottom: 1rem;
  }

  .bar .bar-label {
    display: block;
    margin-bottom: 0.25rem;
    font-size: 1rem;
    color: inherit;
  }

  .bar-track {
    height: 24px;
    background: rgba(0,0,0,0.5);
    border-radius: 4px;
    overflow: hidden;
    border: 1px solid #3d2914;
  }

  .bar-fill {
    height: 100%;
    transition: width 0.3s;
  }

  .bar-fill.health {
    background: linear-gradient(90deg, #8b0000, #ff4444);
  }

  .bar-fill.energy {
    background: linear-gradient(90deg, #1e3a5f, #4a90d9);
  }

  .bar span {
    font-size: 0.8rem;
    color: #8b7355;
  }

  .actions {
    display: flex;
    flex-direction: column;
    gap: 0.75rem;
    margin-bottom: 2rem;
  }

  button {
    padding: 1rem;
    font-size: 1rem;
    border: 2px solid #d4af37;
    border-radius: 4px;
    cursor: pointer;
    transition: all 0.2s;
    color: white;
  }

  button.attack { background: #8b0000; }
  button.defend { background: #1e3a5f; }
  button.rest { background: #2d5016; }

  button:hover:not(:disabled) {
    transform: scale(1.02);
    filter: brightness(1.2);
  }

  button:disabled {
    opacity: 0.5;
    cursor: not-allowed;
  }

  .log {
    background: rgba(0,0,0,0.3);
    border: 1px solid #3d2914;
    border-radius: 4px;
    padding: 1rem;
  }

  .log h3 {
    color: #d4af37;
    margin: 0 0 0.5rem 0;
  }

  .log p {
    margin: 0.25rem 0;
    padding: 0.25rem 0.5rem;
    background: rgba(212, 175, 55, 0.1);
    border-radius: 2px;
  }

  .log .empty {
    color: #8b7355;
    font-style: italic;
  }

  .hint {
    text-align: center;
    margin-top: 1rem;
    color: #8b7355;
    font-size: 0.9rem;
  }
</style>

Hasła z dokumentacji w tej lekcji

Widzisz błąd w tej lekcji?

Sprawdź się

Odpowiedz na pytania z tej lekcji. Wybierz odpowiedź, a od razu zobaczysz, czy jest poprawna.

  1. 1. Jak w Svelte 5 nasłuchujemy kliknięcia przycisku?

  2. 2. Jak w Svelte 5 zapobiec domyślnej akcji przeglądarki, np. przeładowaniu strony po wysłaniu formularza?

  3. 3. Jak w Svelte 5 jednocześnie zatrzymać domyślną akcję i propagację zdarzenia?

Zadania praktyczne w grze

  • Edytor kodu

    Uzupełnij kod, aby kliknięcie przycisku wyświetliło alert 'SKÅL!'

  • Klikanie w kolejności

    Kliknij zamienniki dawnych modyfikatorów w Svelte 5 w kolejności: zapobieganie domyślnej akcji, zatrzymanie propagacji, nasłuchiwanie w fazie capture

Przydatne artykuły