Progressiv förbättring
Din nav fungerar redan
Genom kursen har du skapat en navigation flera gånger. Du har byggt den med semantiska element, länkarna leder rätt, och den är tillgänglig för tangentbord och skärmläsare. På desktop ser den bra ut.
På mobil är problemet ett annat: en rad med fem nav-länkar tar för mycket plats. Du behöver en meny som kan fällas ihop.
Det är ett problem som JavaScript kan lösa åt oss, det första i kursen. Men vi låter inte JavaScript ta över: vi löser det med progressiv förbättring.
Vad är progressiv förbättring?
Tre lager, varje lager bygger på det föregående:
- HTML-grunden. Navigationen finns, länkarna fungerar, semantiken är korrekt. Utan CSS, utan JS.
- CSS-förbättring. Visuell stil, layout, responsivitet. Webbplatsen fungerar utan detta lager, men ser sämre ut.
- JS-förbättring. Toggle-beteende, animationer, interaktivitet. Webbplatsen fungerar utan detta lager, men är enklare.
Den praktiska konsekvensen: om JS av någon anledning inte laddas, nätverksfel, ett tillägg i webbläsaren, avsiktlig blockering, ska nav-länkarna fortfarande vara synliga. Annars har du en webbplats utan navigation.
Problemet med att gömma direkt i CSS
Vi har använt media queries sedan Media queries i Tema 3 för att anpassa layout till skärmbredd. Här dyker ett nytt problem upp med dem. Den frestande lösningen ser ut så här:
@media (max-width: 40rem) {
.nav-links {
display: none;
}
} Hoppa inte över det här som ett dåligt exempel du aldrig ska skriva, känn på felet: lägg regeln på navigationen i din slask-fil, smalna av fönstret och titta. Länkarna är borta, och det finns ingen knapp som tar tillbaka dem. Tanken är förstås att JavaScript strax ska sköta en sådan knapp, men om skriptet aldrig körs, nätverksfel, ett tillägg som blockerar, står mobilbesökaren utan navigation. Permanent.
Lösningen: visa att JS är aktivt
Lägg detta som första rad i din JavaScript-fil, innan allt annat:
document.documentElement.classList.add("js"); Det lägger till klassen js på <html>-elementet i samma ögonblick som skriptet körs. Nu kan CSS använda den klassen som villkor:
.nav-links {
display: flex;
gap: 1rem;
}
@media (max-width: 40rem) {
.js .nav-links {
display: none;
}
.js nav.open .nav-links {
display: flex;
flex-direction: column;
gap: 0.5rem;
}
} Basregeln överst gäller alla skärmar: länkarna ligger på rad och syns alltid, även utan JavaScript. Inne i mediefrågan krävs klassen js för att något alls ska döljas, ett bevis på att skriptet faktiskt kör, och när <nav> får klassen open visas länkarna igen, nu staplade på höjden i den utfällda menyn.
Två saker till att lägga märke till. Basregeln rör aldrig flex-direction, så ditt vanliga desktop-nav ligger kvar på rad precis som förut. Och här använder vi medvetet max-width, inte min-width: det här är ett “dölj på liten skärm”-fall, ett av de tillfällen där max-width läser sig naturligare än mobile-first. Resten av din layout bygger du fortfarande mobile-first.
Nu händer ingenting om JS inte laddas, nav-länkarna visas som vanligt på rad.
Varför en knapp och inte en länk?
I Hero-arkitektur gjorde du tvärtom: CTA:n var en länk klädd som en knapp. Så varför är hamburgaren en riktig <button>?
Regeln är enkel: länkar leder, knappar utför. CTA:n leder besökaren någonstans, till en annan sida eller sektion, alltså är den en <a href> oavsett hur den ser ut. Hamburgaren leder ingenstans. Den utför något på sidan där du står: öppnar och stänger menyn. Då är rätt element <button>.
Valet ger dig dessutom mycket gratis. En <button> är inbyggt:
- Fokuserbar med Tab-tangenten
- Aktiverbar med Enter och Space
- Annonseras som “knapp” av skärmläsare
- Tillgänglig utan en enda extra rad JavaScript
Om standardstilarna på knappen ställer till det kan du alltid nollställa dem i CSS. Beteendet följer med ändå.
Vad är ARIA?
Knappen du strax ska bygga bär två attribut som börjar med aria-. ARIA (Accessible Rich Internet Applications) är en webbstandard: en uppsättning HTML-attribut som berättar för hjälpmedel det ögat redan ser. En seende användare ser att menyn är utfälld. Skärmläsaren behöver aria-expanded="true" för att veta samma sak.
Semantiska element bär redan det mesta, en <button> presenterar sig själv som knapp, en <nav> som navigation. ARIA fyller i det som saknas: tillstånd och kopplingar som ändras medan sidan används. Därav ARIA:s gyllene regel: använd det inte när ett inbyggt element redan gör jobbet. Det är ännu ett skäl till att hamburgaren är en riktig <button> och inte något annat med ARIA-lappar påklistrade.
HTML: knappen som öppnar menyn
Lägg till en knapp inuti din <nav>, precis före länklistan:
<nav>
<button class="menu-toggle" aria-expanded="false" aria-controls="nav-links" type="button">
<span class="sr-only">Öppna menyn</span>
<span aria-hidden="true">☰</span>
</button>
<ul id="nav-links" class="nav-links">
<li><a href="/">Hem</a></li>
<li><a href="/om">Om oss</a></li>
<li><a href="/kontakt">Kontakt</a></li>
</ul>
</nav> Vad varje del gör:
<button>, fokuserbar, aktiveras med Tab + Enter/Space, annonseras som knapp av skärmläsare.aria-expanded="false", berättar för hjälpmedel om menyn är öppen. Uppdateras av JS.aria-controls="nav-links", kopplar knappen till det element den kontrollerar.class="sr-only", visuellt dold text för skärmläsare. Lägg till denna CSS-klass:
.sr-only {
position: absolute;
width: 1px;
height: 1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
} aria-hidden="true"på ikonen, ikonen är visuell dekor, inte information. Skärmläsaren läser den textuella beskrivningen i stället.
CSS: knappens synlighet
.menu-toggle {
display: none;
}
@media (max-width: 40rem) {
.js .menu-toggle {
display: block;
}
} Samma villkor som för länklistan: knappen visas bara på liten skärm, och bara när js-klassen bevisar att skriptet som ska sköta den faktiskt kör.
JavaScript: togglen
document.documentElement.classList.add("js");
const toggle = document.querySelector(".menu-toggle");
const nav = document.querySelector("nav");
toggle.addEventListener("click", () => {
const isOpen = nav.classList.toggle("open");
toggle.setAttribute("aria-expanded", String(isOpen));
}); Rad för rad:
classList.add('js'), aktiverar CSS-reglerna som beror på JS.classList.toggle('open'), lägger till klassenopenom den saknas, tar bort den om den finns. Returnerartrueom klassen nu finns.setAttribute('aria-expanded', ...), uppdaterar skärmläsar-informationen i realtid.
Det är allt. Fyra rader logik.
Hela receptet samlat, HTML, CSS och JavaScript i ett stycke, finns som Hamburgaren i Receptsamlingen.
Pröva i DevTools: stäng av JavaScript
- Öppna din webbplats och tryck F12.
- Öppna kommandopanelen med Ctrl+Shift+P (Mac: Cmd+Shift+P).
- Skriv
Disable JavaScriptoch tryck Enter. Ladda om sidan. - Visas nav-länkarna fortfarande? Med progressiv förbättring ja, men utan den, nej.
- Återaktivera med
Enable JavaScripti samma panel.
Uppgift: Hamburgermeny
Lägg till en fungerande hamburger-meny i ditt företagsprojekt.
- Lägg till en toggle-knapp i din
<nav>som annonserar för hjälpmedel om menyn är öppen eller stängd. Texten ska vara tillgänglig för skärmläsare men inte ta visuellt utrymme. - Bygg CSS:en så att grunden fungerar utan JavaScript: nav-länkarna ska alltid vara synliga och förbättras progressivt när JS är aktivt. Använd
.js-klassen som villkor och dölj bara länkarna på liten skärm. - Skriv JavaScript-filen: signalera att JS är aktivt som allra första rad, implementera toggle-logiken och uppdatera knappens tillstånd för hjälpmedel.
- Länka in JS-filen sist i
<body>.
Verifiera att det fungerar på tre sätt:
- Utan JS: öppna DevTools och fliken Sources, högerklicka på din JS-fil och välj “Block request URL”. Ladda om. Är nav-länkarna fortfarande synliga på mobil?
- Tangentbord: stäng av musen. Kan du tabba till knappen? Öppnas menyn med Enter? Kan du tabba till alla nav-länkar?
- WAVE: kör ett WAVE-test och kontrollera att
aria-expandedvisas korrekt i strukturrapporten.
Vad händer om…
Ta bort den allra första raden i din JavaScript-fil (document.documentElement.classList.add('js')) och ladda om sidan på mobil. Är hamburger-knappen synlig? Är nav-länkarna fortfarande tillgängliga? Det är skillnaden mellan progressiv förbättring och en meny som försvinner utan JavaScript.
Motivera & reflektera
Skriv i din README varför hamburgaren är en <button> och inte en länk, och vad aria-expanded faktiskt gör för en skärmläsaranvändare. Kunde du förklara det för en klasskamrat utan att titta på koden? Det är skillnaden mellan att kopiera ett recept och att förstå det.
Nästa nivå
En riktig meny stängs inte bara, den beter sig rätt. Tre steg som få gör och som märks direkt med tangentbord: (1) stäng menyn när användaren trycker Escape, och flytta då fokus tillbaka till hamburger-knappen så man inte tappar bort sig. (2) När menyn öppnas, flytta fokus till första länken. (3) Lägg en @media (prefers-reduced-motion: reduce) som stänger av eventuell öppna-animation för användare som blir illamående av rörelse. Testa allt med enbart tangentbord och en skärmläsare. Det är exakt den här sortens detaljer som skiljer proffsen från resten.