Kurs JavaScript i TypeScript · Moduł 8: Zdarzenia i DOM

Wydarzenia i interakcje w JavaScript

6 min czytania
W tej lekcji3

"Witaj ponownie w Parku Jurajskim," pozdrawia Cię Ray Arnold, główny inżynier systemów. "Dzisiaj pokażę Ci, jak działa najbardziej krytyczny aspekt naszych systemów - interaktywność. Każdy system w parku, od bramek bezpieczeństwa po monitoring dinozaurów, musi reagować na różne sygnały w czasie rzeczywistym."

Ray prowadzi Cię do centralnego panelu kontrolnego, gdzie setki wskaźników migają i pulsują. "Zobacz," wskazuje na ekran, "te wszystkie systemy nasłuchują na wydarzenia - otwarcie bramy, ruch w zagrodzie, zmiana temperatury. W JavaScript działamy dokładnie tak samo."

Czym są wydarzenia w JavaScript?

Wydarzenia (events) to fundamentalny mechanizm komunikacji w przeglądarce. Każda interakcja użytkownika - kliknięcie, przesunięcie myszą, naciśnięcie klawisza - generuje wydarzenie, na które możemy zareagować.

1// Podobnie jak sensor ruchu w zagrodzie dinozaurów
2const motionSensor = document.getElementById('dino-enclosure');
3
4motionSensor.addEventListener('click', function() {
5  console.log('Wykryto aktywność w zagrodzie!');
6  checkDinosaurStatus();
7});

"W Parku Jurajskim," kontynuuje Ray, "mamy tysiące sensorów. Każdy wysyła sygnały do centrali. Gdybyśmy musieli stale sprawdzać każdy sensor, system by się zawalił. Zamiast tego, sensory wysyłają wydarzenia tylko wtedy, gdy coś się dzieje."

Model wydarzeń w przeglądarce

Przeglądarka implementuje tzw. Event-Driven Architecture - architekturę sterowaną wydarzeniami. To oznacza, że kod nie wykonuje się sekwencyjnie od góry do dołu, ale reaguje na wydarzenia zachodzące w systemie.

1// Kod rejestruje listenery i czeka na wydarzenia
2console.log('System aktywny - nasłuchuje...');
3
4document.addEventListener('click', () => {
5  console.log('Kliknięcie zarejestrowane!');
6});
7
8document.addEventListener('keydown', () => {
9  console.log('Naciśnięto klawisz!');
10});
11
12// Program nie kończy się - czeka na wydarzenia
13console.log('Wszystkie systemy gotowe');

Rodzaje wydarzeń

Ray pokazuje Ci listę na ekranie. "Mamy dziesiątki typów sensorów w parku. Podobnie JavaScript ma dziesiątki typów wydarzeń:"

Wydarzenia myszy:

  • click - kliknięcie (jak przycisk awaryjny)
  • dblclick - podwójne kliknięcie
  • mouseenter / mouseleave - najechanie/opuszczenie myszą
  • mousemove - ruch myszy (jak czujnik ruchu)
  • mousedown / mouseup - wciśnięcie/puszczenie przycisku myszy

Wydarzenia klawiatury:

  • keydown - wciśnięcie klawisza
  • keyup - puszczenie klawisza
  • keypress - przestarzałe, nie używaj (zamiast niego keydown albo input)

Wydarzenia formularzy:

  • submit - wysłanie formularza
  • change - zmiana wartości pola
  • input - wprowadzanie tekstu
  • focus / blur - aktywacja/dezaktywacja pola

Wydarzenia dokumentu:

  • DOMContentLoaded - DOM gotowy (jak system online)
  • load - wszystkie zasoby załadowane
  • scroll - przewijanie strony
  • resize - zmiana rozmiaru okna

Anatomia wydarzenia

"Każde wydarzenie niesie ze sobą informacje," wyjaśnia Ray, pokazując szczegółowy raport z sensora. "Podobnie każdy obiekt Event w JavaScript zawiera dane o tym, co się stało."

1const enclosureMap = document.getElementById('park-map');
2
3enclosureMap.addEventListener('click', function(event) {
4  // Obiekt event zawiera szczegółowe informacje
5
6  console.log('Typ wydarzenia:', event.type); // 'click'
7  console.log('Element docelowy:', event.target); // kliknięty element
8  console.log('Pozycja X:', event.clientX); // współrzędna X
9  console.log('Pozycja Y:', event.clientY); // współrzędna Y
10  console.log('Przycisk myszy:', event.button); // 0=lewy, 1=środkowy, 2=prawy
11  console.log('Ctrl wciśnięty?', event.ctrlKey); // true/false
12  console.log('Shift wciśnięty?', event.shiftKey); // true/false
13
14  // Możemy użyć tych informacji do precyzyjnej reakcji
15  if (event.ctrlKey) {
16    selectMultipleDinosaurs(event.target);
17  } else {
18    selectSingleDinosaur(event.target);
19  }
20});

Dlaczego wydarzenia są ważne?

Ray wskazuje na wielki ekran pokazujący mapy wszystkich zagród. "Bez systemu wydarzeń, musielibyśmy co sekundę sprawdzać każdy sensor. To byłoby nieefektywne i obciążające. Wydarzenia pozwalają systemom reagować natychmiastowo i tylko wtedy, gdy to potrzebne."

W aplikacjach webowych wydarzenia umożliwiają:

  1. Interaktywność - reagowanie na działania użytkownika
  2. Wydajność - kod wykonuje się tylko gdy potrzebny
  3. Asynchroniczność - różne części aplikacji mogą działać niezależnie
  4. Modularność - różne komponenty mogą komunikować się przez wydarzenia

Pierwszy praktyczny przykład

"Zobaczmy to w akcji," mówi Ray, otwierając panel kodu. "Stwórzmy prosty system monitoringu zagrody."

1// System monitoringu zagrody T-Rexa
2const trexEnclosure = {
3  temperature: 28,
4  humidity: 65,
5  feedingSchedule: '14:00',
6  lastActivity: null,
7
8  initialize() {
9    // Rejestracja listenerów dla różnych systemów
10    const tempSensor = document.getElementById('temp-sensor');
11    const feedButton = document.getElementById('feed-button');
12    const activityMonitor = document.getElementById('activity-monitor');
13
14    // Monitoring temperatury
15    tempSensor.addEventListener('change', (e) => {
16      this.temperature = parseFloat(e.target.value);
17      this.checkConditions();
18    });
19
20    // System karmienia
21    feedButton.addEventListener('click', () => {
22      this.feedDinosaur();
23    });
24
25    // Detekcja aktywności
26    activityMonitor.addEventListener('motion', (e) => {
27      this.lastActivity = new Date();
28      this.logActivity(e.detail);
29    });
30
31    console.log('System monitoringu T-Rexa aktywny');
32  },
33
34  checkConditions() {
35    if (this.temperature > 35) {
36      this.triggerAlert('Temperatura zbyt wysoka!', 'warning');
37    } else if (this.temperature < 20) {
38      this.triggerAlert('Temperatura zbyt niska!', 'warning');
39    } else {
40      console.log('Warunki optymalne');
41    }
42  },
43
44  feedDinosaur() {
45    console.log('Rozpoczynam procedurę karmienia T-Rexa...');
46    this.triggerAlert('Karmienie w toku', 'info');
47
48    setTimeout(() => {
49      console.log('Karmienie zakończone pomyślnie');
50      this.triggerAlert('Karmienie zakończone', 'success');
51    }, 3000);
52  },
53
54  logActivity(details) {
55    console.log(`Aktywność zarejestrowana: ${details.type} o ${this.lastActivity.toLocaleTimeString()}`);
56  },
57
58  triggerAlert(message, level) {
59    const alertEvent = new CustomEvent('parkAlert', {
60      detail: { message, level, timestamp: new Date() }
61    });
62    document.dispatchEvent(alertEvent);
63  }
64};
65
66// Inicjalizacja systemu
67document.addEventListener('DOMContentLoaded', () => {
68  trexEnclosure.initialize();
69});
70
71// Centralny system alertów
72document.addEventListener('parkAlert', (e) => {
73  const { message, level, timestamp } = e.detail;
74  console.log(`[${timestamp.toLocaleTimeString()}] ${level.toUpperCase()}: ${message}`);
75});

Dobre praktyki - zasady Raya Arnolda

"Przez lata pracy nad systemami Parku Jurajskiego," mówi Ray, "nauczyłem się kilku kluczowych zasad:"

1. Zawsze sprawdzaj czy element istnieje

1const emergencyButton = document.getElementById('emergency');
2
3if (emergencyButton) {
4  emergencyButton.addEventListener('click', evacuatePark);
5} else {
6  console.error('KRYTYCZNY BŁĄD: Nie znaleziono przycisku awaryjnego!');
7}

2. Usuwaj listenery gdy nie są potrzebne

1function setupTemporaryMonitoring() {
2  const sensor = document.getElementById('temp-sensor');
3
4  function handleReading(e) {
5    console.log('Odczyt:', e.target.value);
6  }
7
8  sensor.addEventListener('change', handleReading);
9
10  // Po 1 godzinie wyłącz monitoring
11  setTimeout(() => {
12    sensor.removeEventListener('change', handleReading);
13    console.log('Monitoring zakończony');
14  }, 3600000);
15}

3. Używaj nazwanych funkcji dla czytelności

Nazwa funkcji od razu mówi, co robi listener, a gdy coś pójdzie nie tak, zobaczysz ją w stosie wywołań w narzędziach deweloperskich.

1// Lepiej
2function handleEmergencyClick(event) {
3  console.log('Aktywacja protokołu awaryjnego');
4  evacuatePark();
5}
6
7emergencyButton.addEventListener('click', handleEmergencyClick);
8
9// Niż
10emergencyButton.addEventListener('click', function(e) {
11  console.log('Aktywacja protokołu awaryjnego');
12  evacuatePark();
13});

4. Grupuj podobne wydarzenia

1class EnclosureMonitor {
2  constructor(enclosureId) {
3    this.enclosure = document.getElementById(enclosureId);
4    this.initializeEvents();
5  }
6
7  initializeEvents() {
8    // Wszystkie event listenery w jednym miejscu
9    this.enclosure.addEventListener('click', (e) => this.handleClick(e));
10    this.enclosure.addEventListener('mouseenter', (e) => this.handleMouseEnter(e));
11    this.enclosure.addEventListener('mouseleave', (e) => this.handleMouseLeave(e));
12  }
13
14  handleClick(event) {
15    console.log('Kliknięto zagrodę');
16  }
17
18  handleMouseEnter(event) {
19    this.enclosure.classList.add('highlighted');
20  }
21
22  handleMouseLeave(event) {
23    this.enclosure.classList.remove('highlighted');
24  }
25}

Podsumowanie

"Wydarzenia to rdzeń każdego interaktywnego systemu," podsumowuje Ray. "Bez nich nasze aplikacje byłyby statyczne i bezużyteczne - jak park bez systemów bezpieczeństwa."

Kluczowe koncepcje:

  • Wydarzenia są generowane przez przeglądarkę w odpowiedzi na działania użytkownika
  • addEventListener() pozwala nasłuchiwać na wydarzenia
  • Obiekt event zawiera szczegółowe informacje o wydarzeniu
  • Właściwe zarządzanie listenerami jest kluczowe dla wydajności

W następnych lekcjach zagłębimy się w zaawansowane techniki obsługi wydarzeń, poznamy propagację, delegację i własne wydarzenia.

Kod do tej lekcji: index.html
1<!DOCTYPE html>
2<html lang="pl">
3<head>
4    <meta charset="UTF-8">
5    <meta name="viewport" content="width=device-width, initial-scale=1.0">
6    <title>Park Jurajski - Panel Alarmowy</title>
7    <style>
8        body {
9            font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
10            background: linear-gradient(135deg, #2c3e50 0%, #34495e 100%);
11            margin: 0;
12            padding: 20px;
13            color: white;
14        }
15
16        .emergency-panel {
17            max-width: 400px;
18            margin: 50px auto;
19            background: rgba(0, 0, 0, 0.8);
20            border-radius: 15px;
21            padding: 30px;
22            box-shadow: 0 10px 30px rgba(0, 0, 0, 0.5);
23            border: 2px solid #e74c3c;
24        }
25
26        .emergency-button {
27            width: 100%;
28            height: 100px;
29            background: linear-gradient(45deg, #e74c3c, #c0392b);
30            border: none;
31            border-radius: 10px;
32            color: white;
33            font-size: 24px;
34            font-weight: bold;
35            cursor: pointer;
36            transition: all 0.3s ease;
37            margin-bottom: 20px;
38            box-shadow: 0 5px 15px rgba(231, 76, 60, 0.4);
39        }
40
41        .emergency-button:hover {
42            transform: translateY(-2px);
43            box-shadow: 0 10px 25px rgba(231, 76, 60, 0.6);
44            background: linear-gradient(45deg, #c0392b, #a93226);
45        }
46
47        .emergency-button:active {
48            transform: translateY(0);
49            box-shadow: 0 5px 15px rgba(231, 76, 60, 0.4);
50        }
51
52        .status-display {
53            background: rgba(255, 255, 255, 0.1);
54            border-radius: 8px;
55            padding: 15px;
56            margin-top: 20px;
57            border: 1px solid rgba(255, 255, 255, 0.2);
58        }
59
60        .alert-message {
61            display: none;
62            background: #e74c3c;
63            color: white;
64            padding: 15px;
65            border-radius: 8px;
66            margin-top: 15px;
67            text-align: center;
68            font-weight: bold;
69            animation: pulse 1s infinite;
70        }
71
72        @keyframes pulse {
73            0% { opacity: 1; }
74            50% { opacity: 0.7; }
75            100% { opacity: 1; }
76        }
77
78        h1 {
79            text-align: center;
80            margin-bottom: 30px;
81            color: #ecf0f1;
82        }
83
84        .last-activation {
85            color: #bdc3c7;
86            font-size: 14px;
87        }
88    </style>
89</head>
90<body>
91    <div class="emergency-panel">
92        <h1>PANEL ALARMOWY T-REX</h1>
93        <button id="emergency-button" class="emergency-button">
94            AKTYWUJ ALARM AWARYJNY
95        </button>
96
97        <div class="status-display">
98            <div>Status systemu: <span id="system-status">GOTOWY</span></div>
99            <div class="last-activation">Ostatnia aktywacja: <span id="last-activation">NIGDY</span></div>
100        </div>
101
102        <div id="alert-message" class="alert-message">
103            ALARM! T-REX UCIEKŁ Z WYBIEGU!
104        </div>
105    </div>
106
107    <script>
108        // Pobieramy referencje do elementów DOM
109        const emergencyButton = document.getElementById('emergency-button');
110        const systemStatus = document.getElementById('system-status');
111        const lastActivation = document.getElementById('last-activation');
112        const alertMessage = document.getElementById('alert-message');
113
114        // Dodajemy event listener do przycisku
115        emergencyButton.addEventListener('click', function() {
116            // Wyświetlamy alert
117            alert('ALARM AWARYJNY AKTYWOWANY!\n\nT-Rex uciekł z wybiegu!\nEwakuacja w toku...');
118
119            // Aktualizujemy status systemu
120            systemStatus.textContent = 'ALARM AKTYWNY';
121            systemStatus.style.color = '#e74c3c';
122
123            // Zapisujemy czas ostatniej aktywacji
124            const now = new Date();
125            lastActivation.textContent = now.toLocaleTimeString('pl-PL');
126
127            // Pokazujemy komunikat alarmowy
128            alertMessage.style.display = 'block';
129
130            // Zmieniamy tekst przycisku
131            emergencyButton.textContent = 'ALARM AKTYWNY';
132            emergencyButton.style.background = 'linear-gradient(45deg, #27ae60, #2ecc71)';
133
134            // Po 5 sekundach resetujemy system
135            setTimeout(function() {
136                systemStatus.textContent = 'GOTOWY';
137                systemStatus.style.color = '#2ecc71';
138                alertMessage.style.display = 'none';
139                emergencyButton.textContent = 'AKTYWUJ ALARM AWARYJNY';
140                emergencyButton.style.background = 'linear-gradient(45deg, #e74c3c, #c0392b)';
141            }, 5000);
142        });
143
144        // Dodajemy obsługę najechania myszą
145        emergencyButton.addEventListener('mouseenter', function() {
146            console.log('Kursor nad przyciskiem awaryjnym - UWAGA!');
147        });
148
149        emergencyButton.addEventListener('mouseleave', function() {
150            console.log('Kursor opuścił przycisk awaryjny');
151        });
152    </script>
153</body>
154</html>

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. Która metoda jest główną metodą do obsługi wydarzeń w JavaScript?

Przydatne artykuły