Temat 17 · 2 × 45 minINF.03
Stan aplikacji: zmienne globalne, indeksy, zawijanie
Kliknięcie trwa ułamek sekundy, ale strona musi pamiętać, co się stało: które zdjęcie galerii jest widoczne, ile razy kliknięto, która zakładka jest otwarta. Ta pamięć to stan aplikacji (state). W tej lekcji poznasz najprostszy – i na egzaminie w zupełności wystarczający – sposób jej przechowywania: zmienną zadeklarowaną poza funkcją. Na niej zbudujesz wzorce, które od 2024 r. wracają w arkuszach INF.03: licznik z limitem, galerię „<” / „>” z zawijaniem indeksu, rotację cytatów i obliczenia zależne od tego, co aktualnie widać na stronie.
Dlaczego funkcja niczego nie pamięta
Zmienna zadeklarowana wewnątrz funkcji jest lokalna: powstaje przy każdym wywołaniu od nowa i znika, gdy funkcja się kończy. Jeśli licznik kliknięć zadeklarujesz w środku funkcji obsługującej kliknięcie, każde kliknięcie zaczyna liczyć od zera.
Co wypisze ten kod?
function dodaj() {
let licznik = 0;
licznik++;
console.log(licznik);
}
dodaj();
dodaj();
dodaj();function dodaj() {
let licznik = 0; // powstaje od nowa przy KAŻDYM wywołaniu
licznik++;
console.log('lokalny:', licznik);
}
dodaj();
dodaj();
dodaj();
let liczbaKlikniec = 0; // zadeklarowane RAZ, poza funkcją
function dodajPoprawnie() {
liczbaKlikniec++; // funkcja tylko zmienia istniejącą zmienną
console.log('globalny:', liczbaKlikniec);
}
dodajPoprawnie();
dodajPoprawnie();
dodajPoprawnie();Zasada jest prosta: stan deklarujesz raz, poza funkcją, a funkcje go tylko odczytują i zmieniają. Taką zmienną nazywamy globalną (jest widoczna w całym skrypcie). W dużych aplikacjach zmiennych globalnych się unika, ale w skrypcie egzaminacyjnym – kilkadziesiąt linii, jeden plik – to najprostsze i całkowicie poprawne rozwiązanie.
Do stanu, który się zmienia, używaj let. Do odnośników do elementów strony (nie zmieniają się) – const. W „dialekcie egzaminacyjnym” zobaczysz var – dla naszych celów działa tak samo.
<button id="plus">+1</button>
<button id="reset">Zeruj</button>
<p>Kliknięto: <strong id="wynik">0</strong> razy</p>let liczbaKlikniec = 0; // stan – poza funkcjami
const wynik = document.getElementById('wynik');
function zwieksz() {
liczbaKlikniec++;
wynik.textContent = liczbaKlikniec; // każda zmiana stanu → odświeżenie DOM
}
function wyzeruj() {
liczbaKlikniec = 0;
wynik.textContent = liczbaKlikniec;
}
document.getElementById('plus').addEventListener('click', zwieksz);
document.getElementById('reset').addEventListener('click', wyzeruj);Licznik z granicą
Druga najczęstsza potrzeba: wartość ma rosnąć lub maleć, ale nie może przekroczyć limitu. W arkuszu z czerwca 2024 (INF.03-08-24.06) pasek postępu rósł o 12 % przy każdym opuszczeniu pola, a zadanie wymagało zabezpieczenia funkcji tak, by szerokość nigdy nie przekroczyła 100 %. Masz dwa równoważne zapisy:
let postep = 0;
// Wersja 1: if po zmianie
function zwiekszIf() {
postep += 12;
if (postep > 100) postep = 100;
}
// Wersja 2: Math.min wybiera mniejszą z dwu liczb
function zwiekszMin() {
postep = Math.min(postep + 12, 100);
}
for (let i = 0; i < 9; i++) zwiekszMin();
console.log('po 9 krokach:', postep); // 100, nie 108
// analogicznie dolna granica: Math.max(wartosc - 1, 0)
let temperatura = 2;
temperatura = Math.max(temperatura - 5, 0);
console.log('temperatura:', temperatura); // 0Pasek startuje od 16 %, każde zdarzenie dodaje 12 % z limitem 100 %. Jaką szerokość ma pasek po 7 zdarzeniach?
<p>Wypełnij pola i przechodź między nimi klawiszem Tab.</p>
<input placeholder="Imię"> <input placeholder="Nazwisko"> <input placeholder="E-mail"><br>
<input placeholder="Telefon"> <input placeholder="Miasto"> <input placeholder="Kod">
<div id="tlo"><div id="pasek">16%</div></div>#tlo { width: 100%; background: #ddd; margin-top: 12px; }
#pasek { width: 16%; background: SeaGreen; color: white; padding: 4px 0; text-align: center; }
input { width: 28%; margin: 4px 0; }let szerokosc = 16; // stan początkowy zgodny z CSS
const pasek = document.getElementById('pasek');
function zwiekszPostep() {
szerokosc = Math.min(szerokosc + 12, 100); // nigdy ponad 100
pasek.style.width = szerokosc + '%';
pasek.textContent = szerokosc + '%';
}
// jedno nasłuchiwanie dla każdego pola – zdarzenie blur = opuszczenie pola
const pola = document.querySelectorAll('input');
for (const pole of pola) {
pole.addEventListener('blur', zwiekszPostep);
}Zwróć uwagę na dwie rzeczy: stan początkowy zmiennej (16) zgadza się z tym, co widać w CSS, a jedna funkcja obsługuje wszystkie pola, bo pętla podpina ją do każdego z nich.
Indeks i zawijanie: galeria „<” i „>”
Galeria to licznik, który po ostatnim elemencie wraca do pierwszego, a przed pierwszym skacze na ostatni. Arkusz INF.03-09-24.06 opisywał to wprost: zdjęcia 1.jpg…7.jpg, po 7.jpg ma pokazać się 1.jpg, przed 1.jpg – 7.jpg. Zawijanie (wrap-around) zapiszesz na dwa sposoby.
Sposób 1: if po zmianie. Czytelny, działa dla numeracji od 1 (jak nazwy plików):
const LICZBA_ZDJEC = 7;
let numer = 1; // numer pliku: 1…7
function nastepne() {
numer++;
if (numer > LICZBA_ZDJEC) numer = 1; // po 7 wracamy na 1
}
function poprzednie() {
numer--;
if (numer < 1) numer = LICZBA_ZDJEC; // przed 1 skaczemy na 7
}
for (let i = 0; i < 8; i++) nastepne();
console.log('po 8 × „>” od 1:', numer); // 2
poprzednie(); poprzednie();
console.log('po 2 × „<”:', numer); // 7Sposób 2: operator % (reszta z dzielenia). Najkrótszy, ale wymaga numeracji od 0 (indeksy tablicy) i uwagi przy cofaniu:
const zdjecia = ['1.jpg', '2.jpg', '3.jpg', '4.jpg', '5.jpg', '6.jpg', '7.jpg'];
let indeks = 0; // indeks tablicy: 0…6
function nastepne() {
indeks = (indeks + 1) % zdjecia.length; // 6 + 1 = 7, 7 % 7 = 0
}
function poprzednie() {
// UWAGA: (0 - 1) % 7 daje -1, nie 6! Dodaj długość przed dzieleniem:
indeks = (indeks - 1 + zdjecia.length) % zdjecia.length;
}
console.log((0 - 1) % 7); // -1 ← pułapka
console.log((0 - 1 + 7) % 7); // 6 ← poprawnie
poprzednie();
console.log('plik:', zdjecia[indeks]); // 7.jpgGaleria ma 7 zdjęć, zmienna numer startuje od 1 i jest zawijana. Jaką wartość ma numer po ośmiu kliknięciach „>” i jednym kliknięciu „<”?
Co wypisze console.log((0 - 1) % 5);?
Teraz pełna galeria. W zadaniu egzaminacyjnym dostaniesz pliki 1.jpg…7.jpg; tutaj plac zabaw nie ma plików, więc obrazki rysujemy w locie jako SVG – ale nazwa pliku nadal trafia do src, więc kod galerii wygląda identycznie jak na egzaminie.
<div class="galeria">
<button id="wstecz"><</button>
<img id="zdjecie" alt="Zdjęcie 1">
<button id="dalej">></button>
</div>
<p id="licznik">1 / 7</p>.galeria { display: flex; align-items: center; gap: 12px; }
img { width: 260px; height: 140px; border-radius: 8px; }
button { font-size: 20px; padding: 8px 14px; }// Na egzaminie: obraz.src = numer + '.jpg'. Tu generujemy obrazek, bo plac zabaw nie ma plików.
function adresObrazka(numer) {
const svg = '<svg xmlns="http://www.w3.org/2000/svg" width="260" height="140">' +
'<rect width="260" height="140" fill="hsl(' + numer * 47 + ' 60% 45%)"/>' +
'<text x="130" y="85" font-size="36" fill="white" text-anchor="middle" font-family="system-ui">' +
numer + '.jpg</text></svg>';
return 'data:image/svg+xml;utf8,' + encodeURIComponent(svg);
}
const LICZBA_ZDJEC = 7;
let numer = 1; // stan galerii
const zdjecie = document.getElementById('zdjecie');
const licznik = document.getElementById('licznik');
function odswiez() {
zdjecie.src = adresObrazka(numer); // na egzaminie: numer + '.jpg'
zdjecie.alt = 'Zdjęcie ' + numer;
licznik.textContent = numer + ' / ' + LICZBA_ZDJEC;
}
function nastepne() {
numer++;
if (numer > LICZBA_ZDJEC) numer = 1;
odswiez();
}
function poprzednie() {
numer--;
if (numer < 1) numer = LICZBA_ZDJEC;
odswiez();
}
document.getElementById('dalej').addEventListener('click', nastepne);
document.getElementById('wstecz').addEventListener('click', poprzednie);
odswiez(); // stan początkowy też przechodzi przez tę samą funkcjęRotacja: 1 → 2 → 3 → 1
Odmiana zawijania bez przycisków: kliknięcie w widoczny element ukrywa go i pokazuje następny, a po ostatnim wraca pierwszy (INF.03-10-24.06: cytaty pracowników). Arkusz dopuszczał „jedną funkcję parametrową lub trzy osobne”. Trzy osobne funkcje są dozwolone, ale jedna z parametrem to mniej kodu i mniej miejsc na literówkę.
<blockquote id="cytat1" onclick="pokazNastepny(1)">„Najlepszy kod to ten, którego nie trzeba pisać.” – Ania</blockquote>
<blockquote id="cytat2" onclick="pokazNastepny(2)">„Testuj na dwóch zestawach danych.” – Bartek</blockquote>
<blockquote id="cytat3" onclick="pokazNastepny(3)">„Nazwy zmiennych czyta się częściej, niż pisze.” – Celina</blockquote>blockquote { cursor: pointer; background: #fdf3c4; padding: 16px; border-left: 6px solid #d9a800; margin: 0; }
#cytat2, #cytat3 { display: none; } /* na start widoczny tylko pierwszy */const LICZBA_CYTATOW = 3;
// Styl egzaminacyjny: atrybut onclick="pokazNastepny(2)" w HTML wywołuje tę funkcję
function pokazNastepny(numerKlikniety) {
let nastepny = numerKlikniety + 1;
if (nastepny > LICZBA_CYTATOW) nastepny = 1; // po 3 wraca 1
document.getElementById('cytat' + numerKlikniety).style.display = 'none';
document.getElementById('cytat' + nastepny).style.display = 'block';
}Zauważ sztuczkę 'cytat' + numer: identyfikatory z numerem pozwalają jednej funkcji dotrzeć do dowolnego elementu. Tu stan nie jest nawet w zmiennej – „pamięta” go DOM (to, który cytat jest widoczny), a funkcja dostaje go w parametrze.
W powyższym kodzie użytkownik widzi cytat 3 i klika go. Który cytat zobaczy?
Jedna funkcja dla wielu przycisków
Zakładki, przyciski nawigacji, miniatury – to zawsze ten sam mechanizm: najpierw wszystkim elementom ustaw stan „nieaktywny”, potem jednemu „aktywny”. Jeśli pominiesz pierwszy krok, po trzech kliknięciach wszystkie zakładki będą widoczne jednocześnie. Funkcja musi wiedzieć, który przycisk kliknięto. Są trzy sposoby przekazania tej informacji.
| Sposób | HTML | JS | Kiedy |
|---|---|---|---|
parametr w onclick | onclick="pokaz('adres')" | function pokaz(nazwa) {…} | styl arkuszy CKE; prosty i czytelny |
this w onclick | onclick="aktywuj(this)" | function aktywuj(przycisk) {…} | gdy potrzebujesz samego klikniętego elementu |
event.target | brak atrybutów | addEventListener('click', e => … e.target …) | gdy przyciski podpinasz pętlą |
<nav>
<button class="karta-btn" onclick="pokazKarte('klient', this)">Klient</button>
<button class="karta-btn" onclick="pokazKarte('adres', this)">Adres</button>
<button class="karta-btn" onclick="pokazKarte('kontakt', this)">Kontakt</button>
</nav>
<section id="klient" class="karta">Imię: <input> Nazwisko: <input></section>
<section id="adres" class="karta">Ulica: <input> Miasto: <input></section>
<section id="kontakt" class="karta">E-mail: <input> Telefon: <input></section>.karta { display: none; padding: 16px; border: 1px solid #ccc; margin-top: 8px; }
#klient { display: block; } /* stan początkowy: pierwsza karta widoczna */
.karta-btn { background: Crimson; color: white; border: 0; padding: 8px 16px; }function pokazKarte(idKarty, klikniety) {
// 1. wszystkim „nieaktywny”
const karty = document.querySelectorAll('.karta');
for (const karta of karty) karta.style.display = 'none';
const przyciski = document.querySelectorAll('.karta-btn');
for (const przycisk of przyciski) przycisk.style.backgroundColor = 'Crimson';
// 2. jednemu „aktywny”
document.getElementById(idKarty).style.display = 'block';
klikniety.style.backgroundColor = 'Salmon';
}
// Stan początkowy: pierwszy przycisk podświetlony tak samo, jak po kliknięciu
document.querySelector('.karta-btn').style.backgroundColor = 'Salmon';Ta sama logika z addEventListener i event.target – bez atrybutów onclick w HTML. Każdy przycisk zna swoją kartę przez atrybut data-karta, który odczytasz jako przycisk.dataset.karta:
<nav id="menu">
<button data-karta="opis">Opis</button>
<button data-karta="cennik">Cennik</button>
<button data-karta="galeria">Galeria</button>
</nav>
<section id="opis" class="karta aktywna">Opis produktu…</section>
<section id="cennik" class="karta">Cennik: 49 zł / 89 zł</section>
<section id="galeria" class="karta">[galeria zdjęć]</section>.karta { display: none; padding: 12px; background: #eef; margin-top: 8px; }
.karta.aktywna { display: block; }
#menu button.aktywny { font-weight: bold; text-decoration: underline; }const przyciski = document.querySelectorAll('#menu button');
function przelaczKarte(zdarzenie) {
const klikniety = zdarzenie.target; // element, który kliknięto
const idKarty = klikniety.dataset.karta; // z atrybutu data-karta
for (const p of przyciski) p.classList.remove('aktywny');
for (const k of document.querySelectorAll('.karta')) k.classList.remove('aktywna');
klikniety.classList.add('aktywny');
document.getElementById(idKarty).classList.add('aktywna');
}
for (const przycisk of przyciski) {
przycisk.addEventListener('click', przelaczKarte); // ta sama funkcja dla każdego
}
przyciski[0].classList.add('aktywny'); // stan początkowyOdczyt stanu z DOM
Nie każdy stan trzeba trzymać w zmiennej. Czasem wygodniej zapytać stronę, co aktualnie pokazuje. Arkusz INF.03-03-26.01 wymagał: miniatury zmieniają duży obraz (trójkąt albo prostokąt), a przycisk „Oblicz” liczy pole tej figury, która jest teraz wyświetlona – „obliczenia powinny też działać dla stanu początkowego witryny”. Masz kilka źródeł prawdy:
| Co sprawdzasz | Kod | Uwaga |
|---|---|---|
nazwa pliku w src | obraz.src.includes('trojkat') | src zwraca pełny adres, dlatego includes, nie === |
| tekst alternatywny | obraz.alt === 'trójkąt' | ustawiaj alt razem ze src |
atrybut data-* | obraz.dataset.figura === 'trojkat' | najczytelniejsze; w HTML data-figura="trojkat" |
| klasa | element.classList.contains('aktywny') | dobre dla zakładek i przycisków |
| widoczność | getComputedStyle(el).display === 'none' | patrz pułapka niżej |
<p>Kliknij miniaturę:</p>
<img class="miniatura" data-figura="trojkat" alt="trójkąt" onclick="wybierzFigure(this)">
<img class="miniatura" data-figura="prostokat" alt="prostokąt" onclick="wybierzFigure(this)">
<br>
<img id="duzy" data-figura="trojkat" alt="trójkąt">
<p>
<label>a (bok / podstawa): <input id="bokA" type="number" value="4"></label>
<label>b (bok / wysokość): <input id="bokB" type="number" value="3"></label>
<button onclick="obliczPole()">Oblicz</button>
</p>
<p id="wynik"></p>.miniatura { width: 60px; height: 40px; cursor: pointer; border: 2px solid transparent; margin-right: 6px; }
#duzy { width: 180px; height: 120px; display: block; margin: 8px 0; }
input[type=number] { width: 60px; }// Na egzaminie: pliki 1m.bmp / 2m.bmp (miniatury) i 1d.bmp / 2d.bmp (duże). Tu rysujemy figury w SVG.
function rysunek(figura, w, h) {
const ksztalt = figura === 'trojkat'
? '<polygon points="' + (w / 2) + ',5 5,' + (h - 5) + ' ' + (w - 5) + ',' + (h - 5) + '" fill="SteelBlue"/>'
: '<rect x="5" y="5" width="' + (w - 10) + '" height="' + (h - 10) + '" fill="DarkOrange"/>';
return 'data:image/svg+xml;utf8,' + encodeURIComponent(
'<svg xmlns="http://www.w3.org/2000/svg" width="' + w + '" height="' + h + '">' + ksztalt + '</svg>');
}
const duzy = document.getElementById('duzy');
const miniatury = document.querySelectorAll('.miniatura');
for (const m of miniatury) m.src = rysunek(m.dataset.figura, 60, 40);
duzy.src = rysunek('trojkat', 180, 120); // stan początkowy: trójkąt
function wybierzFigure(miniatura) {
const figura = miniatura.dataset.figura;
duzy.src = rysunek(figura, 180, 120); // na egzaminie: duzy.src = '2d.bmp'
duzy.dataset.figura = figura; // zapisujemy stan w DOM
duzy.alt = miniatura.alt;
}
function obliczPole() {
const a = parseFloat(document.getElementById('bokA').value);
const b = parseFloat(document.getElementById('bokB').value);
let pole;
// odczyt stanu: która figura jest TERAZ wyświetlona?
if (duzy.dataset.figura === 'trojkat') {
pole = 0.5 * a * b;
} else {
pole = a * b;
}
document.getElementById('wynik').textContent =
'Pole ' + duzy.alt + 'a: ' + pole; // np. „Pole trójkąta: 6”
}Gdybyś na egzaminie miał tylko pliki 1d.bmp i 2d.bmp, warunek wyglądałby tak: if (duzy.src.includes('1d')). Działa, bo src zawsze zawiera nazwę pliku – ale zwraca pełny adres (file:///C:/…/1d.bmp), więc porównanie duzy.src === '1d.bmp' byłoby zawsze fałszywe. Bezpieczniej: duzy.getAttribute('src') === '1d.bmp' zwraca dokładnie to, co wpisano w HTML.
// zakładamy: w CSS jest #panel { display: none; }
const panel = document.getElementById('panel');
console.log(panel.style.display); // "" – pusto! styl jest w CSS, nie w atrybucie
console.log(getComputedStyle(panel).display); // "none" – faktyczna wartość
// przełącznik pokaż/ukryj, który działa od pierwszego kliknięcia:
function przelacz() {
const ukryty = getComputedStyle(panel).display === 'none';
panel.style.display = ukryty ? 'block' : 'none';
}Stan w tablicy
Gdy elementów jest więcej albo mają treść (cytaty, podpisy, ceny), trzymaj je w tablicy, a stanem jest indeks. Zmiana stanu to zmiana indeksu, a odświeżenie DOM to odczyt tablica[indeks].
<p id="cytat"></p>
<button id="nastepny">Następny</button>
<button id="losowy">Losowy</button>
<small id="info"></small>const cytaty = [
'Najpierw spraw, by działało. Potem, by było ładne.',
'Każdy błąd w konsoli to wskazówka, nie wyrok.',
'Testuj na dwóch zestawach danych.',
'Nazwa zmiennej to komentarz, którego nie da się pominąć.'
];
let indeks = 0; // stan: który cytat jest widoczny
const cytat = document.getElementById('cytat');
const info = document.getElementById('info');
function odswiez() {
cytat.textContent = cytaty[indeks];
info.textContent = 'Cytat ' + (indeks + 1) + ' z ' + cytaty.length; // dla człowieka numerujemy od 1
}
document.getElementById('nastepny').addEventListener('click', () => {
indeks = (indeks + 1) % cytaty.length; // zawijanie przez modulo
odswiez();
});
document.getElementById('losowy').addEventListener('click', () => {
indeks = Math.floor(Math.random() * cytaty.length); // 0 … length-1
odswiez();
});
odswiez();Tablica ma 4 elementy, indeks wynosi 3. Co da indeks = (indeks + 1) % 4 i co pokaże cytaty[indeks]?
Na egzaminie
Ściąga
| Składnia | Co robi | Przykład |
|---|---|---|
let stan = 0; poza funkcją | zmienna żyje między kliknięciami | let numer = 1; |
stan = Math.min(stan + k, max) | wzrost z górnym limitem | postep = Math.min(postep + 12, 100) |
stan = Math.max(stan - k, min) | spadek z dolnym limitem | ilosc = Math.max(ilosc - 1, 0) |
i++; if (i > n) i = 1; | zawijanie w przód (numeracja od 1) | galeria 1…7 |
i--; if (i < 1) i = n; | zawijanie w tył (numeracja od 1) | galeria 1…7 |
i = (i + 1) % n | zawijanie w przód (indeksy 0…n−1) | indeks = (indeks + 1) % tab.length |
i = (i - 1 + n) % n | zawijanie w tył bez ujemnych | (0 - 1 + 7) % 7 === 6 |
'cytat' + numer | id z numerem – jedna funkcja, wiele elementów | getElementById('cytat' + 2) |
onclick="f('adres', this)" | parametr i kliknięty element | function f(id, przycisk) |
e.target, e.target.dataset.x | kto wywołał zdarzenie, atrybut data-x | addEventListener('click', e => …) |
img.src.includes('1d') | odczyt stanu z adresu obrazu | src to pełny adres – nie porównuj === |
el.getAttribute('src') | dokładnie to, co w HTML | === '1d.bmp' działa |
el.classList.contains('aktywny') | odczyt stanu z klasy | zakładki, przyciski |
getComputedStyle(el).display | faktyczna widoczność (także z CSS) | el.style.display bywa puste |
Math.floor(Math.random() * n) | losowy indeks 0…n−1 | losowy cytat |
Zadania
Licznik biletów 0–10
Zbuduj licznik liczby biletów z przyciskami „−” i „+” oraz akapitem <p id="ilosc">0</p>. Wymagania:
- wartość nie spada poniżej 0 i nie rośnie powyżej 10,
- gdy osiągnie granicę, odpowiedni przycisk zostaje wyłączony (
disabled = true), a po odejściu od granicy – włączony, - pod licznikiem akapit
<p id="cena"></p>pokazuje „Do zapłaty: X zł” przy cenie 24 zł za bilet.
Pokaż rozwiązanie
<button id="minus">−</button>
<span id="ilosc">0</span>
<button id="plus">+</button>
<p id="cena">Do zapłaty: 0 zł</p>const CENA_BILETU = 24;
const MAKSIMUM = 10;
let liczbaBiletow = 0; // stan
const minus = document.getElementById('minus');
const plus = document.getElementById('plus');
function odswiez() {
document.getElementById('ilosc').textContent = liczbaBiletow;
document.getElementById('cena').textContent = 'Do zapłaty: ' + liczbaBiletow * CENA_BILETU + ' zł';
minus.disabled = liczbaBiletow === 0; // na dolnej granicy wyłącz „−”
plus.disabled = liczbaBiletow === MAKSIMUM; // na górnej wyłącz „+”
}
plus.addEventListener('click', () => {
liczbaBiletow = Math.min(liczbaBiletow + 1, MAKSIMUM);
odswiez();
});
minus.addEventListener('click', () => {
liczbaBiletow = Math.max(liczbaBiletow - 1, 0);
odswiez();
});
odswiez(); // stan początkowy: „−” od razu wyłączonyKaruzela opinii jedną funkcją
Na stronie są cztery bloki <div class="opinia" id="opinia1">…id="opinia4", każdy z atrybutem onclick="nastepnaOpinia(N)", gdzie N to numer bloku. Na start widoczny jest tylko opinia1 (pozostałe ukryte w CSS). Napisz jedną funkcję nastepnaOpinia(numer), która ukrywa kliknięty blok i pokazuje następny; po czwartym ma wrócić pierwszy. Dodaj przycisk „Pokaż wszystkie”, który wyświetla wszystkie cztery bloki naraz.
Pokaż rozwiązanie
<div class="opinia" id="opinia1" onclick="nastepnaOpinia(1)">1/4 „Szybka dostawa.” – Ola</div>
<div class="opinia" id="opinia2" onclick="nastepnaOpinia(2)">2/4 „Dobra cena.” – Piotr</div>
<div class="opinia" id="opinia3" onclick="nastepnaOpinia(3)">3/4 „Polecam.” – Kasia</div>
<div class="opinia" id="opinia4" onclick="nastepnaOpinia(4)">4/4 „Wrócę.” – Marek</div>
<button onclick="pokazWszystkie()">Pokaż wszystkie</button>.opinia { display: none; padding: 10px; background: #e8f5e9; margin-bottom: 6px; cursor: pointer; }
#opinia1 { display: block; }const LICZBA_OPINII = 4;
function nastepnaOpinia(numer) {
let nastepny = numer + 1;
if (nastepny > LICZBA_OPINII) nastepny = 1; // zawijanie 4 → 1
document.getElementById('opinia' + numer).style.display = 'none';
document.getElementById('opinia' + nastepny).style.display = 'block';
}
function pokazWszystkie() {
const opinie = document.querySelectorAll('.opinia');
for (const opinia of opinie) opinia.style.display = 'block';
}Galeria z kropkami i klawiaturą
Rozbuduj galerię z lekcji: tablica zdjecia ma 5 elementów (użyj funkcji adresObrazka z lekcji albo własnych plików). Wymagania:
- przyciski „<” i „>” zawijają w obie strony (użyj operatora
%, indeks od 0), - pod zdjęciem jest 5 kropek
<span class="kropka">wygenerowanych pętlą; aktywna kropka ma klasęaktywna; kliknięcie kropki przeskakuje do danego zdjęcia, - strzałki klawiatury
ArrowLeft/ArrowRightdziałają jak przyciski (zdarzeniekeydownnadocument), - akapit pokazuje „Zdjęcie 3 z 5”.
Pokaż rozwiązanie
<button id="wstecz"><</button>
<img id="zdjecie" alt="">
<button id="dalej">></button>
<div id="kropki"></div>
<p id="opis"></p>img { width: 240px; height: 120px; vertical-align: middle; border-radius: 6px; }
.kropka { display: inline-block; width: 12px; height: 12px; border-radius: 50%; background: #bbb; margin: 8px 4px; cursor: pointer; }
.kropka.aktywna { background: #15171c; }function adresObrazka(numer) {
const svg = '<svg xmlns="http://www.w3.org/2000/svg" width="240" height="120">' +
'<rect width="240" height="120" fill="hsl(' + numer * 61 + ' 55% 45%)"/>' +
'<text x="120" y="75" font-size="34" fill="white" text-anchor="middle" font-family="system-ui">' +
numer + '.jpg</text></svg>';
return 'data:image/svg+xml;utf8,' + encodeURIComponent(svg);
}
const zdjecia = [1, 2, 3, 4, 5].map(adresObrazka); // 5 adresów
let indeks = 0; // stan
const zdjecie = document.getElementById('zdjecie');
const kropki = document.getElementById('kropki');
// kropki generowane pętlą – każda zna swój numer przez data-indeks
for (let i = 0; i < zdjecia.length; i++) {
const kropka = document.createElement('span');
kropka.className = 'kropka';
kropka.dataset.indeks = i;
kropka.addEventListener('click', (e) => {
indeks = Number(e.target.dataset.indeks); // dataset zwraca napis
odswiez();
});
kropki.appendChild(kropka);
}
function odswiez() {
zdjecie.src = zdjecia[indeks];
zdjecie.alt = 'Zdjęcie ' + (indeks + 1);
document.getElementById('opis').textContent = 'Zdjęcie ' + (indeks + 1) + ' z ' + zdjecia.length;
const wszystkie = document.querySelectorAll('.kropka');
for (const k of wszystkie) k.classList.remove('aktywna'); // wszystkim: nieaktywna
wszystkie[indeks].classList.add('aktywna'); // jednej: aktywna
}
function nastepne() {
indeks = (indeks + 1) % zdjecia.length;
odswiez();
}
function poprzednie() {
indeks = (indeks - 1 + zdjecia.length) % zdjecia.length;
odswiez();
}
document.getElementById('dalej').addEventListener('click', nastepne);
document.getElementById('wstecz').addEventListener('click', poprzednie);
document.addEventListener('keydown', (e) => {
if (e.key === 'ArrowRight') nastepne();
if (e.key === 'ArrowLeft') poprzednie();
});
odswiez();Kliknij najpierw w podgląd, żeby ramka miała fokus – dopiero wtedy odbiera klawisze.
Wypożyczalnia kajaków: cena zależna od wyświetlonego modelu
Strona cennik.html zawiera (HTML jest gotowy – twoje zadanie to tylko skrypt):
<section id="modele">
<img class="mini" src="kajak1m.jpg" alt="Kajak jednoosobowy" onclick="wybierzModel(1)">
<img class="mini" src="kajak2m.jpg" alt="Kajak dwuosobowy" onclick="wybierzModel(2)">
</section>
<img id="duzy" src="kajak1d.jpg" alt="Kajak jednoosobowy">
<label>Liczba godzin: <input type="number" id="godziny" min="1" value="1"></label>
<button onclick="obliczKoszt()">Oblicz</button>
<p id="koszt"></p>Skrypt, wykonywany po stronie klienta, działa tak:
- Kliknięcie miniatury zmienia obraz
duzynakajak1d.jpgalbokajak2d.jpgoraz jego tekst alternatywny. - Kliknięcie „Oblicz” pobiera liczbę godzin i sprawdza, który model jest aktualnie wyświetlony: jednoosobowy kosztuje 30 zł/h, dwuosobowy 45 zł/h. Obliczenie ma działać także dla stanu początkowego (jednoosobowy).
- W akapicie pod przyciskiem pojawia się tekst: „Kajak jednoosobowy, 3 h: 90 zł” (nazwa modelu z tekstu alternatywnego, liczba godzin, koszt).
- Stosuj znaczące nazwy zmiennych i funkcji w języku polskim lub angielskim.
Kryteria oceny w stylu „Zasad oceniania” (każde 0 lub 1):
- 5.1 Skrypt napisany w JavaScript, zawiera co najmniej jedną poprawną składniowo instrukcję.
- 5.2 Kliknięcie miniatury zmienia atrybut
srcdużego obrazu na właściwy plik…d.jpg. - 5.3 Skrypt pobiera liczbę godzin z pola edycyjnego.
- 5.4 Koszt liczony jest wg stawki modelu aktualnie wyświetlonego – sprawdzane dla obu modeli oraz dla stanu początkowego.
- 5.5 Wynik wyświetlony pod przyciskiem w akapicie, w formacie „<model>, <godziny> h: <koszt> zł” – sprawdzane dla dwóch różnych liczb godzin.
- 5.6 Użyto co najmniej jednej zmiennej lub funkcji o znaczącej nazwie.
Pokaż rozwiązanie
W placu zabaw brak plików graficznych, więc obrazy są rysowane funkcją rysunek; na egzaminie zamiast niej wpisujesz nazwy plików ('kajak' + numer + 'd.jpg'). Logika odczytu stanu jest identyczna.
<section id="modele">
<img class="mini" data-model="1" alt="Kajak jednoosobowy" onclick="wybierzModel(1)">
<img class="mini" data-model="2" alt="Kajak dwuosobowy" onclick="wybierzModel(2)">
</section>
<img id="duzy" data-model="1" alt="Kajak jednoosobowy">
<p><label>Liczba godzin: <input type="number" id="godziny" min="1" value="1"></label>
<button onclick="obliczKoszt()">Oblicz</button></p>
<p id="koszt"></p>.mini { width: 70px; height: 40px; cursor: pointer; margin-right: 6px; }
#duzy { display: block; width: 210px; height: 120px; margin: 8px 0; }// zamiennik plików kajak1m.jpg, kajak2m.jpg, kajak1d.jpg, kajak2d.jpg
function rysunek(numer, w, h) {
const kolor = numer === 1 ? 'SeaGreen' : 'SteelBlue';
const svg = '<svg xmlns="http://www.w3.org/2000/svg" width="' + w + '" height="' + h + '">' +
'<ellipse cx="' + w / 2 + '" cy="' + h / 2 + '" rx="' + (w / 2 - 6) + '" ry="' + (h / 4) + '" fill="' + kolor + '"/>' +
'<text x="' + w / 2 + '" y="' + (h / 2 + 6) + '" font-size="' + h / 4 + '" fill="white" text-anchor="middle" font-family="system-ui">kajak' + numer + '</text></svg>';
return 'data:image/svg+xml;utf8,' + encodeURIComponent(svg);
}
for (const mini of document.querySelectorAll('.mini')) mini.src = rysunek(Number(mini.dataset.model), 70, 40);
const STAWKI = { 1: 30, 2: 45 }; // zł za godzinę wg modelu
const NAZWY = { 1: 'Kajak jednoosobowy', 2: 'Kajak dwuosobowy' };
const duzy = document.getElementById('duzy');
duzy.src = rysunek(1, 210, 120); // stan początkowy: model 1
function wybierzModel(numer) {
duzy.src = rysunek(numer, 210, 120); // na egzaminie: 'kajak' + numer + 'd.jpg'
duzy.alt = NAZWY[numer];
duzy.dataset.model = numer; // stan zapisany w DOM
}
function obliczKoszt() {
const godziny = parseInt(document.getElementById('godziny').value);
const model = Number(duzy.dataset.model); // który model jest TERAZ wyświetlony
// wersja egzaminacyjna z nazwami plików: const model = duzy.src.includes('kajak2') ? 2 : 1;
const koszt = godziny * STAWKI[model];
document.getElementById('koszt').textContent =
duzy.alt + ', ' + godziny + ' h: ' + koszt + ' zł';
}Samoocena: kliknij „Oblicz” bez wybierania modelu (stan początkowy → 30 zł/h), potem wybierz model 2 i policz dla 2 oraz 5 godzin. Sprawdź tekst co do przecinka i spacji.
Test powtórkowy
Test powtórkowy
Format jak w części pisemnej INF.03: 4 odpowiedzi, jedna poprawna.
Co wypisze ten kod po trzech wywołaniach?
var licznik = 0;
function klik() {
licznik = licznik + 1;
document.write(licznik + ' ');
}
klik(); klik(); klik();Galeria ma zdjęcia 1.jpg–5.jpg. Który fragment poprawnie zawija numer po kliknięciu „>”?
Jaką wartość ma indeks po wykonaniu kodu?
var indeks = 0;
var n = 6;
indeks = (indeks - 1 + n) % n;Pasek postępu startuje od 0 % i przy każdym zdarzeniu blur wykonuje szerokosc = Math.min(szerokosc + 25, 100). Jaką szerokość ma po 6 zdarzeniach?
Który zapis w atrybucie HTML przekaże funkcji informację, który z trzech przycisków kliknięto?
Co jest prawdą o tym kodzie, jeśli w arkuszu CSS jest reguła #box { display: none; }, a w HTML brak atrybutu style?
var box = document.getElementById('box');
if (box.style.display == 'none') {
box.style.display = 'block';
}Tablica cytaty ma 3 elementy, i = 2. Po i = (i + 1) % cytaty.length; instrukcja p.innerHTML = cytaty[i]; wyświetli:
W zakładkach po trzech kliknięciach różnych przycisków widoczne są wszystkie trzy sekcje jednocześnie. Jaka jest najbardziej prawdopodobna przyczyna?
Duży obraz ma w HTML src='1d.bmp'. Który warunek niezawodnie sprawdzi, że właśnie on jest wyświetlony?
Po załadowaniu strony widać cytat 1. Użytkownik klika kolejno: cytat 1, potem widoczny cytat, potem znów widoczny cytat. Co zobaczy po trzech kliknięciach?
function dalej(nr) {
var nastepny = nr + 1;
if (nastepny > 3) nastepny = 1;
document.getElementById('c' + nr).style.display = 'none';
document.getElementById('c' + nastepny).style.display = 'block';
}