Kurs Svelte i SvelteKit · Moduł 1: Wprowadzenie do Svelte
Obsługa zdarzeń
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. Jak w Svelte 5 nasłuchujemy kliknięcia przycisku?
2. Jak w Svelte 5 zapobiec domyślnej akcji przeglądarki, np. przeładowaniu strony po wysłaniu formularza?
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