Skip links en inhoudsopgave

Toegankelijk responsief dropdownmenu

Laatst aangepast: .

Dropdownmenu in smal en breed venster.

Korte omschrijving

In smallere browservensters wordt door een aanraking, een klik of het toetsenbord een verticale menubalk met knoppen zichtbaar. In bredere vensters zijn deze knoppen altijd zichtbaar. Onder elk van de knoppen zit een submenu met links, dat door een aanraking, met de muis of met het toetsenbord geopend kan worden.

BELANGRIJK

Deze uitleg hoort bij het voorbeeld dat in de download zit. Het voorbeeld uit de download verschilt iets van het voorbeeld hier op de site. In de download ontbreekt bijvoorbeeld de navigatie voor de site. Ook in de kopregels zit vaak wat verschil. Daarnaast kunnen er nog andere (meestal kleine) verschillen zijn.

Als je deze uitleg leest naast de broncode van het voorbeeld op de site, kan het dus bijvoorbeeld zijn dat 'n <h1> uit de css bij 'n <h2> uit de html hoort. Maar het gaat niet om hele grote, fundamentele afwijkingen.

Als je dit lastig vindt, kun je bovenaan de pagina de hele handel downloaden. In de download zit 'n voorbeeld dat wel naadloos aansluit op de uitleg in de download.

Als je deze handleiding graag uitprint (zonde van het bos), gebruik dan de pdf in de download. Deze pagina is niet geoptimaliseerd voor printen, de pdf kan wel makkelijk worden geprint.

Alles op deze site kan vrij worden gebruikt, met drie beperkingen:

* Je gebruikt het materiaal op deze site volledig op eigen risico. Het kan prima zijn dat er fouten in de hier verstrekte info zitten. Voor eventuele schade die door gebruik van materiaal van deze site ontstaat, in welke vorm dan ook, zijn www.css-voorbeelden.nl en medewerkers daarvan op geen enkele manier verantwoordelijk.

* Deze uitleg wordt min of meer regelmatig bijgewerkt. Het is daarom niet toegestaan deze uitleg op welke manier dan ook te verspreiden, zonder daarbij duidelijk te vermelden dat de uitleg afkomstig is van www.css-voorbeelden.nl en dat daar altijd de nieuwste versie is te vinden. Dit is om te voorkomen dat er verouderde versies worden verspreid.

* Het kan zijn dat materiaal is gebruikt dat van anderen afkomstig is. Dat materiaal kan onder een bepaalde licentie vallen, waardoor het mogelijk niet onbeperkt gebruikt mag worden. Als dat zo is, wordt dat vermeld onder Inhoud van de download en licenties.

Een link naar www.css-voorbeelden.nl wordt trouwens altijd op prijs gesteld.

Alle code is geschreven in een afwijkende lettersoort en -kleur. De code die te maken heeft met de basis van dit voorbeeld (essentiële code), is in de hele uitleg onderstippeld blauw. Alle niet-essentiële code is bruin. (In de inhoudsopgave staat alles vanwege de leesbaarheid in een gewone letter.)

Opmerkingen

Links in deze uitleg, vooral links naar andere sites, kunnen verouderd zijn. Op de pagina met links vind je steeds de meest recente links.

Dit voorbeeld is gemaakt op een systeem met Linux (KDE neon). Daarbij is vooral gebruik gemaakt van Visual Studio Code, GIMP en Firefox met extensies. De pdf-bestanden zijn gemaakt met LibreOffice.

Vragen of opmerkingen? Fout gevonden? Ga naar het forum.

Achterliggend idee

De vorige versie van dit dropdownmenu was meer dan tien jaar oud. Sinds die tijd is er enorm veel veranderd in html en css en bij browsers en schermlezers. Daarom is de code volledig herschreven.

Naar dit soort menu's is tamelijk veel vraag. Meestal werken ze alleen, als JavaScript wordt gebruikt. De dropdownmenu's waarbij alleen html en css worden gebruikt, werken meestal weer niet in schermlezers. of met toetsenbordnavigatie.

Het idee van dit menu is dat het ook zonder JavaScript moet werken. JavaScript wordt alleen gebruikt om het beter te laten werken, vooral in schermlezers en bij gebruik van het toetsenbord.

Hieronder wordt eerst de werking zonder JavaScript beschreven, daarna de werking met JavaScript.

In bredere browservensters staat bovenaan de pagina een menubalk met zes <button>'s. Door het aanraken of -klikken van een knop opent een submenu. Dat submenu is een gewone ongeordende lijst <ul> met daarin een serie links.

In smallere browservensters is er geen ruimte voor een menubalk. Hier staat bovenin het venster één <button>. Bij aanraken of -klikken van die <button> verschijnt een verticale menubalk met een serie <button>'s, dezelfde <button>'s die in een breder venster altijd zichtbaar zijn. Bij aanraken of -klikken van een van deze <button>'s opent onder de aangeraakte of -geklikte <button> een submenu met links. Eventueel daaronder zittende <button>'s met andere submenu's schuiven omlaag. Als het geheel te hoog wordt voor het venster, kan het hele dropdownmenu worden gescrold.

Zonder JavaScript wordt het openen en sluiten van de menu's geregeld met selectors als :focus en :focus-within. Omdat een <button> de focus kan krijgen, kan met behulp van selectors als button:focus + ul het tonen en verbergen van de submenu's worden geregeld. Dat is te zeggen: een submenu kan wel worden geopend door een aanraking of klik, maar niet worden gesloten. Sluiten van een submenu kan alleen door een ander submenu te openen, of door een aanraking of klik buiten het menu.

Zo zou het dus horen te zijn. Maar in diens onnavolgbare wijsheid heeft Apple besloten dat een <button> geen focus kan krijgen. De reden schijnt iets te zijn van dat knoppen in de besturingssystemen iOS, iPadOS en OS X ook geen focus kunnen krijgen, en daarom laten ze <button>'s op websites ook maar geen focus krijgen. Er zijn mensen voor minder op een gesloten afdeling terecht gekomen. Maar goed, dit is een gegeven, dus we hebben het er maar mee te doen. Dit geldt voor Safari op OS X en – omdat Apple browsers op iOS en iPadOS verplicht de weergave-machine van Safari te gebruiken – voor álle browsers op iOS en iPadOS.

Gelukkig blijkt in deze besturingssystemen :hover ongeveer hetzelfde te werken als :focus. Bijkomend voordeel is dat op de desktop nu ook bij hoveren over de menubalk de submenu's geopend kunnen worden.

Bij gebruik van de Tab-toets om door het menu te lopen, krijgt de <button> de focus en kunnen met behulp van selectors als :focus en :focus-within de submenu's worden getoond. Vreemd genoeg werkt dit wel op iOS, iPadOS en OS X. Je moet echter álle submenu's doorlopen, in één keer naar een volgende knop gaan kan niet. Als je dus het genoegen hebt de laatste link van het zesde submenu te moeten volgen, moet je 42 keer de Tab-toets indrukken. Er zijn interessantere vormen van lichaamsbeweging te bedenken.

Als je met Shift+Tab achteruit door de menubalk loopt, hoef je niet door alle submenu's te lopen: nu loop je alleen door de zes knoppen.

Door gebruik te maken van :focus-visible is zichtbaar, welke link of knop de focus heeft. Dat is alleen van belang voor gebruikers van de Tab-toets, bij gebruik van de muis of een touchscreen is de (foeilelijke) focus onzichtbaar.

In schermlezers werkt het dropdownmenu ook zonder JavaScript volledig. Afhankelijk van de schermlezer kan een submenu worden geopend met iets als Enter of (een combinatie van toetsen met) de Spatiebalk. Alleen wordt niet aangegeven of het submenu is geopend of gesloten, waardoor het geheel uiterst verwarrend wordt. Alleen door naar een volgend element te gaan kan worden vastgesteld, of een submenu is geopend of niet: als dat volgende element een link is, is er kennelijk een submenu geopend. Sluiten van een submenu kan alleen door een ander submenu te openen, waardoor je ook met een schermlezer soms door één of meerdere submenu's moet lopen om bij het gewenste submenu aan te komen.

Alle geteste schermlezers kunnen wel probleemloos op de gebruikelijke manier van <button> naar <button> en/of van <ul> naar <ul> springen.

Met JavaScript werkt een aantal dingen veel beter, vooral voor schermlezers en gebruikers van het toetsenbord. Maar omdat JavaScript toch wordt gebruikt, worden ook voor andere gebruikers wat kleine verbeteringen toegevoegd.

Bij het openen van de pagina worden vrijwel alleen classes gebruikt om de css aan de html te koppelen. Met behulp van JavaScript wordt aan een aantal elementen een id toegevoegd. En omdat een id áltijd meer specificiteit, meer gewicht, heeft dan een of meer classes en/of elementen, 'winnen' de selectors met een id van de selectors met alleen een of meer classes en/of elementen. Hierdoor kun je op een vrij simpele manier selectors maken, die alleen werken als JavaScript wordt uitgevoerd. Zonder dat die de css hinderen, die zonder JavaScript wordt gebruikt.

Het openen en sluiten van submenu's wordt bij gebruik van JavaScript fundamenteel anders geregeld dan zonder JavaScript. Bij openen van de pagina krijgen alle <button>'s de WAI-ARIA-code aria-expanded="false". Zodra een submenu wordt geopend, wordt dit veranderd in aria-expanded="true". Het tonen en verbergen van de submenu's gebeurt nu met behulp van selectors als button[aria-expanded="true"]+ ul.

Submenu's kunnen nu ook worden gesloten door het aanraken of -klikken van een <button>, omdat dan met behulp van JavaScript aria-expanded="true" wordt veranderd in aria-expanded="false". Schermlezers hoeven dus niet meer door een al geopend submenu heen te gaan om een volgende submenu te gebruiken, ze kunnen het geopende submenu gewoon sluiten. Hetzelfde geldt voor gebruikers van de Tab-toets: met behulp van Enter of Spatiebalk kunnen submenu's worden geopend en gesloten.

Als een submenu is geopend, sluit Escape dat en zet de focus terug op de bij dat submenu horende <button>. In smallere browservensters sluit een tweede Escape vervolgens de menubalk met de zes <button>'s met submenu's.

In elke <button> staat een open/sluitteken. Dat is een gewoon karakter: «. Door dit te draaien, verandert het in een open- of sluitteken. Dit is niet nodig, want je ziet zo ook wel dat een submenu is geopend, maar het was gewoon leuk om dit te doen.

Dan is er nog een groot aantal kleinere aanpassingen nodig. Als je bijvoorbeeld met de muis in bredere browservensters horizontaal over de menubalk beweegt, vliegen de submenu's je om de oren. Om dat te voorkomen is bij hoveren een vertraging van 0,3 seconde ingebouwd, voordat een submenu wordt getoond. Dat is een nauwelijks merkbare vertraging, maar voorkomt wel het wild rondvliegen van voornoemde submenu's. Helaas is op mobieltjes die korte vertraging toch storend, daarom moest dat daar weer worden uitgeschakeld. Met behulp van @media (pointer: fine) wordt geregeld, dat alleen bij gebruik van een muis wordt vertraagd.

Er is een groot aantal kleine aanpassingen nodig om dubbele lijntjes tussen <button>'s en links te voorkomen.

Op sommige plaatsen is een kleine vertraging nodig, omdat :focus-within anders niet goed werkt (bij een klik of aanraking verdwijnt de focus, voordat het aangeraakte of ‑geklikte element de focus heeft gekregen). Door die noodzakelijke kleine vertraging kan display: none; niet worden gebruikt om de submenu's te verbergen, want dat kan niet worden vertraagd. Daardoor staan de submenu's eigenlijk altijd gewoon open, maar dat zie je niet. Maar hierdoor kon je weer de tekst onder die onzichtbare submenu's niet meer kopiëren. Waardoor weer pointer-events: none; nodig was. Maar niet altijd, want dan werkten de links niet meer.

Kortom: echt een leuke serie puzzels. Vooral door al die kleine dingetjes is de code toch nog tamelijk uitgebreid geworden.

Kort overzicht van problemen die met JavaScript worden opgelost

Het menu werkt zonder JavaScript nog steeds, maar met allerlei kleinere en grotere onhandigheden.

Hieronder staat een kort lijstje met dingen die zonder JavaScript niet of niet goed werken. Bij Zonder JavaScript is deze zelfde lijst te vinden, maar dan met veel meer toelichting, bijbehorende oplossingen, en dergelijke. Omdat dat daar nogal 'n lang verhaal is, staat dezelfde lijst hier ook, maar dan veel beknopter.

Voorwaarden van het JavaScript waaraan html en css moeten voldoen

Voor dit menu wordt gebruik gemaakt van JavaScript. Zonder JavaScript werkt dit menu wel, maar niet echt lekker. (Een volledig overzicht van wat niet of maar gedeeltelijk werkt, is gelijk hierboven te vinden bij Kort overzicht van problemen die met JavaScript worden opgelost.)

Om dit script goed te laten werken, moeten html en css aan een aantal voorwaarden voldoen.

Semantische elementen en WAI-ARIA

Deze twee onderwerpen zijn samengevoegd, omdat ze veel met elkaar te maken hebben.

Semantische elementen

De meeste elementen die in html worden gebruikt, hebben een semantische betekenis. Dat wil zeggen dat je aan de gebruikte tag al (enigszins) kunt zien, wat voor soort inhoud er in het element staat. In een <h1> staat een belangrijke kop. In een <h2> staat een iets minder belangrijke kop. In een <p> staat een alinea. In een <table> staat een tabel (en geen lay-out, als het goed is!). Enzovoort.

Door het op de goede manier gebruiken van semantische elementen, kunnen zoekmachines, schermlezers, enzovoort de structuur van een pagina begrijpen. De spider van een zoekmachine is redelijk te vergelijken met een blinde. Het is dus ook in je eigen belang om semantische elementen zo goed mogelijk te gebruiken. Een site die toegankelijk is voor mensen met een handicap, is in de regel ook goed te verwerken door een zoekmachine en maakt dus een grotere kans gevonden en bezocht te worden.

Als het goed is, wordt het uiterlijk van de pagina bepaald met behulp van css. Het uiterlijk staat hierdoor (vrijwel) los van de semantische inhoud van de pagina. Met behulp van css kun je een <h1> heel klein weergeven en een <h6> heel groot, terwijl schermlezers, zoekmachines, en dergelijke nog steeds weten dat de <h1> een belangrijke kop is.

Slechts enkele elementen, zoals <div> en <span>, hebben geen semantische betekenis. Daardoor zijn deze elementen uitstekend geschikt om met behulp van css het uiterlijk van de pagina aan te passen: de semantische betekenis verandert niet, maar het uiterlijk wel. Voor een schermlezer of zoekmachine verandert er (vrijwel) niets, voor de gemiddelde bezoeker krijgt het door de css een heel ander uiterlijk.

(De derde laag, naast html voor de inhoud en css voor het uiterlijk, is JavaScript. Die zorgt voor de interactie tussen site en bezoeker. De min of meer strikte scheiding tussen css en html aan de ene kant en JavaScript aan de andere kant is met de komst van css3 en html5 veel vager geworden. Je kunt nu bijvoorbeeld ook met css dingen langzaam verplaatsen en met html deels de invoer in formulieren controleren.)

Html5 heeft een aantal nieuwe elementen, die speciaal zijn bedoeld om de opbouw van een pagina aan te geven. In dit voorbeeld worden hiervan alleen <nav> en <main> gebruikt. Beide gedragen zich als een gewone <div>, maar dan een <div> met een semantische betekenis. Hierdoor kunnen schermlezers, zoekmachines, en dergelijke beter zien, hoe de pagina is samengesteld. De meeste schermlezers kunnen dit soort elementen ook gebruiken om snel over de pagina te navigeren.

<main>

Hierbinnen staat de belangrijkste inhoud van de pagina (in dit voorbeeld is dat voornamelijk Latijnse flauwekultekst)

<nav>

Ook <nav> gedraagt zich als een gewone <div>, maar dan een <div> met een semantische betekenis: navigatie. Hierdoor kunnen schermlezers, zoekmachines, en dergelijke gelijk zien dat hierin links zijn ondergebracht, waarmee je naar andere pagina's en dergelijke kunt gaan.

Omdat <nav> alleen aangeeft dat hierin een of andere vorm van navigatie is ondergebracht, maar niet wat voor navigatie, staat bovenin de <nav> 'Toegankelijk responsief dropdownmenu' in een <h2>. Deze <h2> staat onzichtbaar buiten het scherm geparkeerd, maar wordt door schermlezers toch gewoon voorgelezen.

In het voorbeeld staat maar één soort navigatie, maar bijvoorbeeld op de site staan niet alleen de links van het voorbeeld, maar ook de navigatie voor de site zelf. Door op de site beide <nav>'s van een verborgen kop te voorzien, kunnen schermlezers en dergelijke achterhalen, om welke navigatie het gaat.

Met behulp van dit soort nieuwe semantische elementen kan bijvoorbeeld een schermlezer in één keer een heel menu passeren en gelijk naar de echte inhoud gaan.

WAI-ARIA-codes

WAI-ARIA wordt vaak ingekort tot ARIA. Voluit betekent het Web Accessibility Initiative - Accessible Rich Internet Applications.

Er worden in dit voorbeeld drie WAI-ARIA-codes gebruikt: aria-hidden, aria-controls en aria-expanded.

aria-hidden

Met behulp van aria-hidden="true" kan een deel van de code worden verborgen voor schermlezers en dergelijke, zodat dit niet wordt voorgelezen. Op de normale weergave op het scherm heeft dit verder geen enkele invloed.

Bovenaan het menu staat een <button>, waarmee in smallere browservensters de zes <button>'s met submenu's kunnen worden getoond of verborgen:

<button type="button" class="voor-smal"><span>Menu&nbsp;</span><span aria-hidden="true">«</span></button>

Het teken '«' in de tweede <span> wordt met behulp van aria-hidden="true" voor schermlezers verborgen. Dit teken is alleen bedoeld om te laten zien of een submenu is geopend of niet. Bij schermlezers gebeurt dat al met behulp van het attribuut aria-expanded. Bovendien is dit teken een gewoon leesteken, waardoor het in sommige schermlezers wordt voorgelezen.

(Het zou mooi zijn als ook in de zes knoppen met submenu's dit teken voor schermlezers verborgen zou kunnen worden. Helaas kan het daar niet in een aparte <span> worden neergezet, omdat het dan niet goed is te positioneren. In op weergave-machine Blink gebaseerde browsers zoals Google Chrome en Edge wordt het daar verborgen met content: "«" / "";.)

aria-controls

Boven elke <ul> met een submenu staat een <button>, waarmee dat submenu getoond of verborgen kan worden. Om duidelijk te maken dat die <button> bij die <ul> hoort, krijgt de <ul> een id, waarnaar het attribuut aria-controls in de <button> verwijst:

<button type="button" aria-controls="menu-1">Menu 1</button>

<ul id="menu-1">

aria-expanded

Dit attribuut wordt volledig door JavaScript aangestuurd.

Bij opening van de pagina wordt aan alle <button>'s het attribuut aria-expanded="false" toegevoegd. Zodra het bij de <button> horende submenu is geopend, wordt dit met behulp van JavaScript veranderd in aria-expanded="true". Als het submenu weer wordt gesloten, wordt dit weer veranderd in aria-expanded="false".

In smallere browservensters gebeurt dit ook bij de <button> bovenaan de pagina, waarmee de menubalk met de zes <button>'s met de submenu's getoond en verborgen kan worden.

(Als je deze veranderingen wilt zien, moet je niet naar de gewone code, maar naar de Gegenereerde code kijken.)

Toetsenbordnavigatie en tabindex

Op een desktopcomputer gebruiken de meeste mensen een muis om over een pagina te navigeren, links te volgen, en dergelijke. Soms gebruiken mensen hier echter een toetsenbord voor, omdat ze geen muis kunnen of willen gebruiken. Navigeren, links volgen, en dergelijke gebeurt dan met behulp van de Tab-toets, Enter, Spatiebalk, pijltjestoetsen, en dergelijke. (Overigens wordt ook bij touchscreens soms een toetsenbord en/of muis gebruikt.)

Als je alleen simpele html zou gebruiken, werkt deze toetsenbordnavigatie fantastisch. Maar soms kan een bepaalde constructie in de html of in de css de werking ervan verstoren. Bij het maken van een website moet je er vooral op letten dat alles bereikbaar is met de Tab-toets. Voor andere toetsen zijn dan meestal geen aanpassingen nodig.

Links, invoervelden in formulieren, en dergelijke kunnen met behulp van de Tab-toets één voor één worden bezocht, in de volgorde waarin ze in de html voorkomen. Shift+Tab-toets keert de volgorde van de Tab-toets om. Dit is een belangrijk hulpmiddel voor mensen die om een of andere reden de muis niet kunnen of willen gebruiken. (En het is vaak ook veel sneller dan de muis, vooral in formulieren.)

In sommige browsers en/of besturingssystemen is dit vreemd genoeg standaard uitgeschakeld en is een zoektocht in de instellingen nodig om dit aan te zetten. Maar gebruikers van de Tab-toets zullen dit al hebben gedaan.

Als je met behulp van de Tab-toets een element hebt bereikt, heeft dit focus: als het een link is en je drukt op Enter, wordt de link gevolgd. Bij een tekstveld kun je tekst gaan invoeren. Enzovoort.

De Tab-toets volgt normaal genomen de volgorde van de elementen in de html. Het maakt niet uit, in welke volgorde ze op het scherm staan. Als je met behulp van css de elementen van plaats verwisselt op het scherm, wordt toch gewoon de volgorde in de html gevolgd.

De volgorde van de Tab-toets kan worden veranderd met behulp van het tabindex-attribuut: <div tabindex="3">. Deze <div> zal nu als derde worden bezocht, ook al krijgt een simpele <div> normaal genomen nooit bezoek van de Tab-toets.

Normaal genomen is het gebruik van een tabindex niet nodig. Het is zeker niet bedoeld om de bezoeker als een kangoeroe op een hindernisbaan van onder via links over rechts naar boven te laten springen. Maar soms kan het handig zijn voor kleinere correcties, als de normale volgorde in de html niet optimaal is. Of om een element bereikbaar te maken voor de Tab-toets, zoals de hierboven genoemde <div>.

Schermlezers blijven altijd de volgorde van de html volgen, dus als de tabindex sterk afwijkt van de volgorde in de html, kan dat behoorlijk verwarrend zijn.

De tabindex kan drie verschillende waarden hebben: -1, 0 of een positief getal.

In principe is de volgorde bij gebruik van de Tab-toets als volgt: eerst worden alle positieve getallen in volgorde afgewerkt. Als twee tabindexen dezelfde waarde hebben, wordt de volgorde in de html aangehouden. Een waarde van '0' wordt, afhankelijk van browser en besturingssysteem, verschillend behandeld, waarover iets hieronder bij Tabindex="0" meer.

tabindex="-1"

Een negatieve waarde van -1 zorgt ervoor dat een element, dat normaal genomen door het gebruik van de Tab-toets de focus kan krijgen, volledig wordt genegeerd door de Tab-toets. Zelfs een link met een negatieve tabindex wordt volledig genegeerd.

Ook kan aan een element met een negatieve tabindex dat normaal genomen geen focus kan krijgen, toch de focus worden gegeven met behulp van JavaScript, een klik of een aanraking. Waarbij gebruikers van de Tab-toets daar geen last van hebben, omdat tabindex="-1" wordt genegeerd door de Tab-toets.

In dit voorbeeld wordt tabindex="-1" voor iets anders gebruikt.

Op iPadOS wordt het submenu niet gesloten, als het scherm buiten het menu wordt aangeraakt. Door aan <html> een negatieve tabindex te geven, gebeurt dat wel:

<html lang="nl" tabindex="-1">

Op oudere versies van iOS is dit niet voldoende, daar moet ook nog aan <main> een negatieve tabindex worden gegeven:

<main id="content" tabindex="-1">

tabindex="0"

Volgens de specificatie van html 4.01 moest een tabindex="0" pas worden bezocht, nadat tabindexen met een positief nummer waren bezocht. Sommige browsers hielden zich hier echter niet aan: een tabindex="0" werd gewoon tussen de positieve tabindexen gezet.

In html5 is de situatie nog fijner. Nu staat er alleen dat, wat betreft de volgorde, de browser mag doen wat die wil. Oftewel: doe maar raak. Maar hoe dan ook: als je tabindex="0" gebruikt, kan een element focus krijgen met behulp van de Tab-toets. Ook als dat element normaal genomen geen focus kan krijgen.

(In de praktijk blijkt trouwens dat alle browsers de volgorde van de html aanhouden bij tabindex="0", nadat eerst alle tabindexen met een positief nummer zijn bezocht.)

In dit voorbeeld wordt tabindex="0" niet gebruikt.

tabindex="..."

Op de plaats van de puntjes moet een positief getal worden ingevuld: het volgnummer. Een element met een positieve tabindex wordt altijd bezocht bij gebruik van de Tab-toets, ook als dit element normaal genomen zou worden genegeerd. Elementen met een tabindex met een positief volgnummer worden altijd als eerste bezocht, voor elementen als een link of tekstveld zonder tabindex, en ook voor elementen met tabindex="0".

In dit voorbeeld wordt geen positieve tabindex gebruikt.

Muis, toetsenbord, touchpad en touchscreen

Vroeger, toen het leven nog mooi was en alles beter, waren er alleen monitors. Omdat kinderen daar niet af konden blijven met hun tengels, besloten fabrikanten dan maar touchscreens te gaan maken, omdat je die mag aanraken. Het bleek makkelijker te zijn om volwassenen ook te leren hoe je 'n scherm vies maakt, dan om kinderen te leren met hun vingers van de monitor af te blijven.

Zo ontstonden touchscreens en kreeg het begrip ellende een geheel nieuwe lading. In de perfecte wereld van vroeger, waarin alleen desktops bestonden, werkten dingen als hoveren, klikken en slepen gewoon. Zonder dat je eerst 'n cursus hogere magie op Zweinstein hoefde te volgen. Zelfs in JavaScript was het nog wel te behappen, ook voor mensen zoals ik die toevallig niet Einstein heten.

Op dit moment kun je computerschermen ruwweg in twee soorten indelen: schermen die worden aangeraakt, en schermen die worden bediend met hulpmiddelen als een toetsenbord, muis of touchpad. Omdat ook computerschermen zich kennelijk vermengen, bestaan er inmiddels ook schermen die zowel van aanraken als van muizen houden.

Hieronder staat een lijstje met dingen die zijn aangepast voor de verschillende soorten schermen, zodat dit voorbeeld overal werkt. Een touchpad werkt ongeveer hetzelfde als een muis. Als hieronder iets over een muis staat, geldt dat ook voor een touchpad.

:hover

Omdat :hover mogelijk niet werkt, als css uitstaat, ontbreekt of onvolledig is geïmplementeerd, moet je nooit belangrijke informatie alleen met behulp van :hover tonen.

Je hovert over een element, als je met behulp van muis of touchpad de cursor boven dat element brengt. Hoveren kan over álle elementen. Het wordt veel gebruikt om iets van uiterlijk te laten veranderen, een pop-up te laten verschijnen, en dergelijke.

Op een touchscreen wordt hoveren vaak hetzelfde afgehandeld als een aanraking.

Bij gebruik van een muis is er een verschil tussen hoveren en klikken, maar op een touchscreen is dat verschil er niet: je raakt een touchscreen aan of niet. Dat levert vooral soms problemen op, als bij een element :hover én klikken worden gebruikt, zoals bij een link die bij hoveren erover verkleurt.

In Safari op OS X en in alle browser op iOS en iPadOS kan een <button> geen focus krijgen. Bezopen, maar Apple wil dat zo. Het menu zou hierdoor zonder JavaScript niet werken in Safari op OS X en in alle browsers op iOS en iPadOS. (Omdat Apple alle browsers op iOS en iPadOS verplicht te werken met de weergave-machine van Safari, heeft Apple álle browsers opgezadeld met dit onzalige gedoe.)

Zonder JavaScript wordt het tonen en verbergen geregeld met behulp van :focus en :focus-within. Maar dat werkt dus niet in bovenstaande browsers.

Door :hover te gebruiken werkt het toch in Safari op OS X, omdat hoveren over een <button> daar gewoon op de normale manier wordt afgehandeld: bij hoveren over een <button>, wordt het bijbehorende submenu geopend. Op iOS en iPadOS werkt het ook, omdat daar :hover hetzelfde wordt afgehandeld als een aanraking.

Met JavaScript is het een ander verhaal, want dan wordt het tonen en verbergen van de submenu's geregeld met behulp van de WAI-ARIA-code aria-expanded. Bij het aanraken van een <button> wordt het submenu getoond, en bij het nogmaals aanraken wordt het weer gesloten. Maar als hoveren dan ook zou werken, zouden submenu's niet kunnen worden gesloten op iOS en iPadOS, omdat een aanraking hetzelfde werkt als :hover. Daarom wordt, als JavaScript aanstaat, hoveren met behulp van @media (pointer: fine) beperkt tot apparaten, waarvan de belangrijkste aanwijzer een nauwkeurige is, zoals een muis.

:focus

Omdat :focus mogelijk niet werkt, als css uitstaat, ontbreekt of onvolledig is geïmplementeerd, moet je nooit belangrijke informatie alleen met behulp van :focus tonen.

De meeste mensen gaan met een muis naar een link, invoerveld, en dergelijke. Waarna ze vervolgens klikken om de link te volgen, tekst in te voeren, of wat ze ook maar willen doen. (Dit geldt uiteraard niet voor touchscreens.)

Er is echter 'n tweede manier om naar links, invoervelden, en dergelijke te gaan: met behulp van de Tab-toets kun je naar links, invoervelden, en dergelijke 'springen'. (Over het gebruik van de Tab-toets staat meer bij Toetsenbordnavigatie en tabindex.)

Waar je staat, wordt door alle browsers aangegeven met een of ander kadertje. De link en dergelijke met het kadertje eromheen 'heeft focus'. Dat wil zeggen dat je die link volgt, als je op de Enter-toets drukt, of dat je in dat veld tekst kunt gaan invoeren, enzovoort.

Het kadertje dat de focus aangeeft, moet nooit zonder meer worden weggehaald. Gebruikers van de Tab-toets hebben dan geen idee meer, waar ze zijn.

Bij gebruik van de muis of een aanraakscherm is het kadertje niet nodig, want je mag toch hopen dat iemand weet, waar iemand iets heeft heeft aangeraakt of -geklikt. Als je dat niet meer weet, valt te vrezen dat een kadertje ook geen redding meer biedt.

In het verleden was dat vaak een probleem, omdat dat kadertje nou niet bepaald erg mooi is. En dus eigenlijk alleen bij gebruik van de Tab-toets nodig is. Dit is opgelost met de relatief nieuwe pseudo-class :focus-within: de focus wordt alleen nog aangegeven, als een toetsenbord wordt gebruikt. Bij een klik of een aanraking zie je het kadertje niet meer.

Inmiddels is :focus-within in alle iets nieuwere browsers de standaard, dus eigenlijk hoef je :focus en/of :focus-within alleen nog te gebruiken, als je bijvoorbeeld het uiterlijk van het kadertje wilt aanpassen.

In dit voorbeeld verkleurt de rand van de <button> of de link die de focus heeft, maar alleen als de Tab-toets wordt gebruikt.

:active

Omdat :active mogelijk niet werkt, als css uitstaat, ontbreekt of onvolledig is geïmplementeerd, moet je nooit belangrijke informatie alleen met behulp van :active tonen.

Een element is actief, als de muis wordt ingedrukt boven dat element. Op sommige touchscreens is het element actief, als het wordt aangeraakt.

:active wordt niet gebruikt in dit voorbeeld.

Gegenereerde code

Het onderstaande geldt alleen voor desktopbrowsers. In browsers op mobiele systemen is het vaak ook mogelijk gegenereerde code te bekijken, maar dat is veel ingewikkelder. Bovendien verandert de manier, waarop dat kan, nogal snel.

Als je html schrijft, kan dat (hopelijk) in de browser worden weergegeven. Vanuit de browser kun je die html bekijken, precies zoals je de code hebt ingevoerd. Alsof je het in een editor ziet. In Firefox bijvoorbeeld kan dat door in het menu te kiezen voor Extra → Browserhulpmiddelen → Paginabron. (Of door de veel snellere sneltoets Ctrl+U.) Elke browser heeft dit soort mogelijkheden.

Wat je dan te zien krijgt, is exact de code, zoals jij die hebt ingetypt. Inclusief alle fouten, hoofd- en kleine letters, noem maar op. Als je zo slordig bent om een <p> niet af te sluiten, zit er niet plotsklaps een afsluitende </p> in de code. Als er css wordt gebruikt, html wordt ingevoegd via JavaScript, noem maar op, zie je daar niets van, want dat heb jij niet ingetypt.

Daar heb je dus eigenlijk vrij weinig aan, want die code kende je al. Die heb je zelf fluitend of met bloed, zweet en tranen zitten intypen.

Wat de browser daadwerkelijk gebruikt, is iets totaal anders: de gegenereerde code. En die is veel interessanter, want die code blijkt (fors) af te wijken, van wat jij heb ingetypt. De browser gebruikt een tijdelijke kopie van de door jou geschreven code, die zo is aangepast dat er voor de browser mee is te werken.

Elke browser heeft inmiddels mogelijkheden om de gegenereerde code te bekijken. In Firefox bijvoorbeeld in het menu Extra → Browserhulpmiddelen → Webontwikkelaarshulpmiddelen. In Google Chrome in het menu onder Meer hulpprogramma's → Hulpprogramma's voor ontwikkelaars. In Edge open je dit door op F12 te drukken, en het kan vast ook via het menu.

Houdt er wel rekening mee dat elke browser de door jou ingetypte code iets zal aanpassen. In Firefox bijvoorbeeld wordt een <P> veranderd in een <p>. Als er 'n </p> mist, is die opeens wel aanwezig in de gegenereerde code. Wat elke browser precies aanpast, zal iets verschillen en kan ook nog veranderen. Oudere versies van Internet Explorer bijvoorbeeld veranderden een <p> juist in een <P>, nieuwere versies deden dat niet meer.

Als je met behulp van JavaScript elementen invoegt of verwijdert in de HTML (zoals in dit voorbeeld gebeurt), zie je dat alleen in deze gegenereerde code.

De code aanpassen aan je eigen ontwerp

Toegankelijkheid en zoekmachines

De tekst in dit hoofdstukje is een algemene tekst, die voor elke pagina geldt. Eventueel specifiek voor dit voorbeeld geldende problemen en eventuele aanpassingen om die problemen te voorkomen staan bij Bekende problemen (en oplossingen).

Toegankelijkheid (in het Engels 'accessibility') is belangrijk voor bijvoorbeeld blinden die een schermlezer gebruiken, of voor motorisch gehandicapte mensen die moeite hebben met het bedienen van een muis. Een spider van een zoekmachine (dat is het programmaatje dat de site indexeert voor de zoekmachine) is te vergelijken met een blinde. Als je je site goed toegankelijk maakt voor gehandicapten, is dat gelijk goed voor een hogere plaats in een zoekmachine. Dus als je 't niet uit sociale motieven wilt doen, kun je 't uit egoïstische motieven doen.

(Op die plaats in de zoekmachine heb je maar beperkt invloed. De toegankelijkheid van je site is maar één van de factoren, maar zeker niet onbelangrijk.)

Als je bij het maken van je site al rekening houdt met toegankelijkheid, is dat nauwelijks extra werk. 't Is ongeveer te vergelijken met inbraakbescherming: doe dat bij 'n nieuw huis en 't is nauwelijks extra werk, doe 't bij 'n bestaand huis en 't is al snel 'n enorme klus.

Enkele tips die helpen bij toegankelijkheid:

Getest in

Laatst gecontroleerd op 5 augustus 2023.

Onder dit kopje staat alleen maar, hoe en waarin is getest. Alle eventuele problemen, ook die met betrekking tot zoomen, lettergroottes, toegankelijkheid, uit staan van JavaScript en/of css, enzovoort staan iets hieronder bij Bekende problemen (en oplossingen). Het is belangrijk dat deel te lezen, want uit een test kan ook prima blijken dat iets totaal niet werkt!

Dit voorbeeld is getest op de volgende systemen:

Desktopcomputers

Linux (KDE neon 5.27) (2560 x 1080 px, resolution: 96 ppi):
Firefox, Google Chrome en Vivaldi, in grotere en kleinere browservensters.
In Vivaldi is ook ruimtelijke navigatie ('Spatial Navigation') getest.

Laptops

Windows 10 (1600 x 900 px, resolution: 106 ppi):
Firefox, Google Chrome en Edge, in grotere en kleinere browservensters.

OS X 11.7.8 ('Big Sur') (1440 x 900 px, resolution: 96 ppi, device-pixel-ratio: 1):
Firefox, Safari, Google Chrome en Microsoft Edge, in grotere en kleinere browservensters.

Tablets

iPad met iOS 12.5.7 (2048 x 1536 px, device-pixel-ratio: 2:
Safari, Chrome, Firefox en Microsoft Edge (alle portret en landschap).

iPad met iPadOS 16.5.1 (2160 x 1620 px, 264 ppi):
Safari, Chrome, Firefox en Microsoft Edge (alle portret en landschap).

Android 6.0 ('Marshmallow') (1920 x 1200 px, resolution: 224 ppi):
Samsung Internet, Firefox en Chrome (alle portret en landschap).

Android 8.1 ('Oreo') (1920 x 1200 px, resolution: 218 ppi):
Samsung Internet, Firefox, Microsoft Edge en Chrome (alle portret en landschap).

Android 13 (2000 x 1200 px, resolution: 225 ppi):
Samsung Internet, Firefox, Microsoft Edge en Chrome (alle portret en landschap).

Smartphones

iPhone met iOS 15.7.7 (1334 x 750 px, 326 ppi):
Safari, Chrome, Firefox en Microsoft Edge (alle portret en landschap).

Android 7.0 ('Nougat') (1280 x 720 px, resolution: 294 ppi):
Samsung Internet, Firefox, Microsoft Edge en Chrome (alle portret en landschap).

Android 9.0 ('Pie') (1920 x 1080 px, resolution: 424 ppi):
Samsung Internet, Firefox, Microsoft Edge en Chrome (alle portret en landschap).

Android 13 ('Tiramisu') (2408 x 1080 px, resolution: 401 ppi):
Samsung Internet, Firefox, Microsoft Edge en Chrome (alle portret en landschap).

Er is op de aan het begin van dit hoofdstukje genoemde controledatum getest in de meest recente versie van de browser, die op het betreffende besturingssysteem kon draaien. Het aantal geteste browsers en systemen is al tamelijk fors, en als ook nog rekening gehouden moet worden met (zwaar) verouderde browsers, is het gewoon niet meer te doen. Surfen met een verouderde browser is trouwens vragen om ellende, want updates van browsers hebben heel vaak met beveiligingsproblemen te maken.

In- en uitzoomen en - voor zover de browser dat kan - een kleinere en grotere letter zijn ook getest. Er is ingezoomd en vergroot tot zover de browser kan, maar niet verder dan 200%.

Er is getest met behulp van muis en toetsenbord, behalve op iOS, iPadOs en Android, waar een touchscreen is gebruikt. Op Windows 10 is getest met touchscreen, touchpad, toetsenbord, muis, en - waar dat zinvol was - op een combinatie daarvan. Op OS X 11.7.8 is getest met (een combinatie van) toetsenbord, touchpad en muis.

Als in een voorbeeld JavaScript is gebruikt, is ook getest of het werkt zonder JavaScript. Dat is alleen gedaan in de browsers, waarin in de instellingen JavaScript kan worden uitgeschakeld.

Ook is getest zonder css en - als afbeeldingen worden gebruikt - zonder afbeeldingen.

Schermlezers en dergelijke

Naast deze 'gewone' browsers is ook getest in Lynx, WebbIE, NVDA, TalkBack, VoiceOver, Orca en Verteller.

Lynx is een browser die alleen tekst laat zien en geen css gebruikt. Er is getest op Linux.

WebbIE. is een browser die gericht is op mensen met een handicap. Er is getest op Windows 10.

NVDA is een schermlezer, zoals die door blinden wordt gebruikt. Er is getest op Windows 10 in combinatie met Firefox.

TalkBack is een in Android ingebouwde schermlezer. Er is getest in combinatie met Chrome op Android 6.0, 7.0, 8.1, 9 en 13

VoiceOver is een in iOS en OS X ingebouwde schermlezer. Er is getest in combinatie met Safari op iOS 12.5.7 en 15.7.7, iPadOS 16.5.1 en OS X 11.7.8.

Orca is een schermlezer voor Linux. Er is getest in combinatie met Firefox op KDE Neon 5.27.

Verteller (Narrator) is een in Windows 10 ingebouwde schermlezer. Er is getest in combinatie met Edge.

(Voor de bovenstaande programma's zijn links naar sites met uitleg en dergelijke te vinden op de pagina met links onder Toegankelijkheid → Schermlezers, tekstbrowsers, en dergelijke.)

Als het voorbeeld in deze programma's toegankelijk is, zou het in principe toegankelijk moeten zijn in alle aangepaste browsers en dergelijke. En dus ook voor zoekmachines, want een zoekmachine is redelijk vergelijkbaar met een blinde.

Eventuele problemen in schermlezers (en eventuele aanpassingen om die te voorkomen) staan iets hieronder bij Bekende problemen (en oplossingen).

Waar dat zinvol was, is ook nog getest op combinaties als een grote letter in een schermlezer met toetsenbordbediening.

Alleen op de hierboven genoemde systemen en browsers is getest. Er is dus niet getest op bijvoorbeeld 'n Blackberry. Er is een kans dat dit voorbeeld niet (volledig) werkt op niet-geteste systemen en apparaten. Om het wel (volledig) werkend te krijgen, zul je soms (kleine) wijzigingen en/of (kleine) aanvullingen moeten aanbrengen, bijvoorbeeld met JavaScript.

Er is ook geen enkele garantie dat iets werkt in een andere tablet of smartphone dan hierboven genoemd, omdat fabrikanten in principe de software kunnen veranderen. Dit is anders dan op de desktop, waar browsers altijd (vrijwel) hetzelfde werken, zelfs op verschillende besturingssystemen. Iets wat in Samsung Internet op Android werkt, zal in de regel overal werken in die browser, maar een garantie is er niet. De enige garantie is het daadwerkelijk testen op een fysiek apparaat. En aangezien er duizenden mobiele apparaten zijn, is daar geen beginnen aan.

De html is gevalideerd met de html-validator, de css met de css-validator van w3c. Als om een of andere reden niet volledig gevalideerd kon worden, wordt dat bij Bekende problemen (en oplossingen) vermeld.

Nieuwe browsers worden pas getest, als ze uit het bèta-stadium zijn. Anders is er 'n redelijke kans dat je tegen 'n bug zit te vechten, die voor de uiteindelijke versie nog gerepareerd wordt.

Dit voorbeeld is alleen getest in de hierboven met name genoemde browsers. Vragen over niet-geteste browsers kunnen niet worden beantwoord, en het melden van fouten in niet-geteste browsers heeft ook geen enkel nut. (Melden van fouten, problemen, enzovoort in wel geteste browsers: graag! Dat kan op het forum.)

Bekende problemen (en oplossingen)

Waarop en hoe is getest, kun je gelijk hierboven vinden bij Getest in.

Als je hieronder geen oplossing vindt voor een probleem dat met dit voorbeeld te maken heeft, kun je op het forum proberen een oplossing te vinden voor je probleem. Om forumspam te voorkomen, moet je je helaas wel registreren, voordat je op het forum een probleem kunt aankaarten.

Bij toegankelijkheid is er vaak geen goed onderscheid te maken tussen oplossing en probleem. Zonder (heel simpele) aanpassingen heb je vaak 'n probleem, en omgekeerd. Daarom staan wat betreft toegankelijkheid aanpassingen en problemen hier bij elkaar in dit hoofdstukje.

Voor zover van toepassing wordt eerst het ontbreken van JavaScript, css en/of afbeeldingen besproken. Vervolgens problemen en aanpassingen met betrekking tot toegankelijkheid voor specifieke groepen bezoekers, zoals zoomen en andere lettergrootte, Tab-toets, tekstbrowsers en schermlezers. Als laatste volgen de overige problemen in één of meer specifieke browsers.

Als in een onderdeel geen problemen aanwezig zijn, staat in een smal groen kadertje 'Geen problemen'. Bij een onderwerp over toegankelijkheid zijn er soms geen problemen, maar alleen aanpassingen. Ook in dat geval staat bovenaan in een smal groen kadertje 'Geen problemen'. Daaronder staan dan de aanpassingen.

Als in een onderdeel één of meer problemen worden besproken, staat van elk probleem in een breed rood kadertje een korte samenvatting daarvan.

Als bij het probleem een oplossing wordt gegeven, staat de samenvatting in een rode stippellijn. Bij een onderwerp over toegankelijkheid zijn er soms, naast de opgeloste problemen, ook aanpassingen. In dat geval staan staan die aanpassingen boven de kadertjes met opgeloste problemen.

Als bij het probleem geen oplossing is gevonden, staat de samenvatting in een rode ononderbroken lijn. Bij een onderwerp over toegankelijkheid zijn er soms, naast de problemen, ook aanpassingen. In dat geval staan staan die aanpassingen boven de kadertjes met problemen.

De hieronder staande lijst met problemen is erg lang. Maar eigenlijk zijn er alleen serieuze problemen bij het gebruik van schermlezers en het toetsenbord. Die problemen zijn allemaal op te lossen met JavaScript. Alle andere problemen zijn tamelijk klein. De lijst is vooral zo lang, omdat die zo volledig mogelijk is.

Zonder JavaScript

Dit menu werkt ook zonder JavaScript, maar het werkt beter met JavaScript. Bij gebruik van een schermlezer of het toetsenbord geldt dit nog sterker: de submenu's zijn te openen, maar echt goed toegankelijk is het niet.

De lijst hieronder is tamelijk lang, omdat ook de oplossingen worden besproken. Een korter overzicht van de verschillen tussen wel en geen JavaScript is te vinden bij Kort overzicht van problemen die met JavaScript worden opgelost.

Probleem: een submenu kan wel worden geopend door een aanraking of klik op de bijbehorende <button>, maar niet op dezelfde manier worden gesloten.

Als je een submenu kunt openen met een aanraking of klik op een knop, zou je verwachten dat het ook weer gesloten kan worden door diezelfde knop weer aan te raken of te klikken. Dat is niet zo, omdat het openen en sluiten zonder JavaScript wordt geregeld met behulp van :focus en :focus-within (en in Safari op OS X en alle browsers op iOS en iPadOS met behulp van :hover). En als een knop de focus heeft en je raakt of klikt die nogmaals aan, houdt de knop gewoon de focus.

Sluiten van een submenu kan alleen door aanraken van of klikken op een andere <button>, of door een aanraking of klik buiten het menu.

Met behulp van JavaScript wordt bijgehouden, of een submenu is geopend of niet. Dat gebeurt door de WAI-ARIA-code aria-expanded. Bij een geopend submenu staat bij de bijbehorende <button>:

<button aria-expanded="true">

Als een submenu is gesloten, staat bij de bijbehorende <button>:

<button aria-expanded="false">

(de andere attributen bij de <button> zijn hierboven even weggelaten.)

Er wordt nu geen gebruik gemaakt van :hover, :focus of :focus-within, maar met behulp van JavaScript wordt de waarde bij aria-expanded veranderd. Hierdoor kan een submenu nu op dezelfde manier worden geopend en gesloten: door een <button> aan te raken of te klikken. Dit gebeurt met behulp van de attribuut-selector [aria-expanded="true"] of [aria-expanded="false"].

Probleem: in smalle browservensters kan de menubalk met de zes <button>'s met submenu's wel worden getoond door een aanraking of klik op de <button> met 'Menu openen', maar deze kan alleen weer worden verborgen door een klik of aanraking buiten het menu.

In smallere browservensters is de menubalk met de zes <button>'s met de submenu's verborgen, omdat er te weinig ruimte is om deze altijd te tonen. De menubalk wordt pas getoond door een speciale <button> voor smallere vensters aan te raken of te klikken.

Het verhaal is verder precies hetzelfde als gelijk hierboven voor de zes <button>'s met de submenu's. Zonder JavaScript wordt de menubalk met de zes <button>'s alleen weer verborgen door een aanraking of klik buiten het menu, met JavaScript kan deze ook worden gesloten door de <button> met 'Menu sluiten' aan te raken of te klikken.

Probleem: Escape sluit een geopend submenu niet en in een smal browservensters verbergt Escape de menubalk met de zes <button>'s met submenu's niet.

Mensen zijn gewend om dingen als een pop-up of een dropdownmenu te kunnen sluiten door het indrukken van Escape.

Met behulp van JavaScript wordt gekeken, welke toets er wordt ingedrukt. Als dat Escape is, wordt een geopend submenu gesloten en krijgt de <button>, die bij dat submenu hoort, weer de focus.

Als in smallere browservensters een submenu is geopend, wordt dit ook gesloten door het indrukken van Escape. Als je nogmaals Escape indrukt, wordt ook de menubalk met de zes <button>'s met submenu's weer verborgen. Als geen submenu is geopend, sluit de eerste Escape de menubalk.

Probleem: in Safari op OS X kan het menu niet worden geopend of gesloten door op een knop te klikken.

Zonder JavaScript wordt het openen en sluiten van submenu's geregeld met behulp van :hover, :focus en :focus-within. Omdat in Safari op OS X een <button> geen focus kan krijgen, kun je een submenu niet tonen of verbergen met een klik op een knop. :hover werkt wel gewoon: als je met de muis over een knop hovert, opent het submenu bij die knop. Vervolgens kun je over dat submenu hoveren, het sluit pas weer als de muis niet meer over het submenu hovert.

Met JavaScript wordt het tonen en verbergen geregeld met behulp van de WAI-ARIA-code aria-expanded. Hierdoor kan ook in Safari op OS X een submenu worden geopend of gesloten door op een knop te klikken.

Probleem: bij heel sterk vergroten kan in Safari op OS X de menubalk, en dus de submenu's, niet worden getoond.

Als in Safari op OS X het menu sterk wordt vergroot, wordt dit weergegeven alsof het in een klein browservenster staat. Omdat in Safari een <button> geen focus kan krijgen, kan de menubalk hierdoor niet worden getoond, en daarmee ook niet de daarin zittende submenu's.

In een breder browservenster speelt dit niet, want daarin is de menubalk altijd zichtbaar. Ook met JavaScript aan is er geen probleem, want daarin wordt tonen en verbergen van menubalk en submenu's niet met focus geregeld.

Probleem: bij gebruik van de Tab-toets opent onder elke <button> het bijbehorende submenu, waarvan eerst álle links moeten worden doorlopen, voor een volgende <button> de focus krijgt.

Zonder JavaScript wordt het openen en sluiten van submenu's geregeld met behulp van :hover, :focus en :focus-within: zolang een <button> of één van de links in het bijbehorende submenu de focus heeft, blijft het submenu geopend. Als je met de Tab-toets bij de eerste knop aankomt, moet je hierdoor eerst door alle negen links van het eerste submenu heen lopen, voordat je bij de tweede knop aankomt. Je moet dus in totaal tien keer op de Tab-toets drukken, voordat je bij de tweede knop aankomt. Als je bij de tweede knop aankomt, opent het submenu bij die knop. Enzovoort. Om bij de laatste link van het zesde submenu te komen, moet je 42 keer op de Tab-toets drukken.

Achteruitgaan met Shift+Tab gaat sneller, omdat je dan de submenu's niet in gaat.

Met JavaScript kunnen de submenu's geopend en gesloten worden door op Enter of de Spatiebalk te drukken. Je kunt dan gewoon door de knoppen lopen, zonder dat je door alle submenu's hoeft te gaan.

Probleem: in smallere browservensters werkt de skip-link aan het begin van de pagina ook, terwijl die daar overbodig is.

Aan het begin van de pagina staat een skip-link. Gebruikers van het toetsenbord kunnen hiermee in één keer de zes <button>'s met submenu's passeren. In kleinere browservensters is deze skip-link overbodig, want daarin moet je eerst de menubalk met die zes <button>'s openen.

Met behulp van JavaScript wordt een id aan de skip-link toegevoegd, waardoor deze in smallere browservensters wordt verborgen.

Probleem: schermlezers kunnen een submenu openen door het indrukken van de bijbehorende <button>, maar sluiten kan niet op deze manier.

Zonder JavaScript wordt het openen en sluiten van submenu's geregeld met behulp van :hover, :focus en :focus-within: zolang een <button> of één van de links in het bijbehorende submenu de focus heeft, blijft het submenu geopend. Alle geteste schermlezers kunnen een submenu openen door het indrukken van een <button> met Enter, Spatiebalk of op een soortgelijke manier. Het sluiten van het submenu kan echter niet op deze manier. Hierdoor moet je eerst volledig door een geopend submenu heen, voordat je bij een volgende <button> komt. (Je kunt wel op de gebruikelijke manier van <button> naar <button> en/of van <ul> naar <ul> 'springen'.)

Met JavaScript wordt het openen en sluiten geregeld met behulp van de WAI-ARIA-code aria-expanded. Hierdoor kan een submenu nu ook worden gesloten door het indrukken van de bijbehorende <button> met Enter, Spatiebalk, of op een soortgelijke manier.

Probleem: schermlezers zeggen alleen iets als 'knop' bij de <button>, zonder enige verdere toelichting.

Alle schermlezers melden netjes dat een element een <button> is met iets als 'knop'. Maar wát die knop dan doet, is volstrekt onduidelijk. Ook is volstrekt onduidelijk, of een submenu wordt getoond of niet.

JavaScript voegt de WAI-ARIA-code aria-expanded toe. Als een submenu is geopend, wordt de waarde van aria-expanded 'true':

<button aria-expanded="true">

Als een submenu is gesloten, wordt de waarde van aria-expanded 'false':

<button aria-expanded="false">

Schermlezers melden nu of de knop is uitgevouwen of niet.

In smallere browservensters wordt op dezelfde manier gemeld, of de menubalk met de zes <button>'s met de submenu's zichtbaar is of niet.

Probleem: in schermlezers in smallere browservensters worden 'Menu openen' en 'Menu sluiten' soms verkeerd getoond en daardoor verkeerd voorgelezen.

In smallere browservensters moet je eerst de menubalk met de zes <button>'s met submenu's zichtbaar maken door het indrukken van een speciale <button> voor smallere vensters. Als de menubalk niet zichtbaar is, is de tekst van die <button> 'Menu openen'. Als de menubalk zichtbaar is, is de tekst van die <button> 'Menu sluiten'. Dit is ook de tekst die wordt voorgelezen.

Als je 'n aantal keren door het menu heen en weer gaat, staat in de <button> soms de verkeerde tekst, en wordt dus de verkeerde tekst voorgelezen. Met JavaScript wordt altijd de juiste tekst voorgelezen: 'Menu openen' als de menubalk onzichtbaar is, 'Menu sluiten' als deze zichtbaar is.

Probleem: in VoiceOver op OS X worden altijd alle submenu's geopend.

Zonder JavaScript wordt het openen en sluiten van submenu's geregeld met behulp van :hover, :focus en :focus-within: zolang een <button> of één van de links in het bijbehorende submenu de focus heeft, blijft het submenu geopend. Hierdoor wordt in VoiceOver op OS X bij elke <button> áltijd het bijbehorende submenu geopend.

Om bij de volgende <button> te komen, moet je eerst door het geopende submenu heen. Om bij de laatste link van de het zesde submenu te komen, moet je dus door zes <button>'s en 36 links heen. Vanzelfsprekend is alles van Apple superieur, maar dit is misschien iets te superieur.

Met JavaScript wordt het tonen en verbergen van submenu's geregeld met de WAI-ARIA-code aria-expanded. Hierdoor kan in VoiceOver op OS X een submenu worden geopend en gesloten door de bijbehorende <button> met Enter in te drukken.

Probleem: in Verteller op Windows worden in een smal browservenster links alleen gevolgd door het indrukken van Control + Shift + Enter.

Als je sterk genoeg inzoomt, gaat het menu zich ook in bredere browservensters gedragen zoals in smallere vensters. Een link hoort gevolgd te worden door het indrukken van Enter of de Spatiebalk, maar dat werkt hier niet. Alleen door het indrukken van Control + Shift + Enter wordt een link gevolgd. Bovendien wordt de link in een nieuwe tab geopend.

Met JavaScript wordt de link ook op de gewone manier gevolgd.

Zonder css

Geen problemen.

De links worden getoond als een serie lijsten met links. Uiteraard is de hele lay-out wel verdwenen.

Toetsenbordnavigatie

Voor gebruikers van de Tab-toets is het belangrijk om te weten, welke knop, link, en dergelijke de focus heeft. Dit wordt in dit voorbeeld aangegeven door de knop of link een witte achtergrond en een blauwe outline te geven. Zonder die kenmerken zou een gebruiker van de Tab-toets bijvoorbeeld niet weten, welke link wordt gevolgd bij het indrukken van Enter. Daarom is deze indicatie heel belangrijk.

Voor gebruikers van een muis of touchscreen is deze indicatie niet van belang, want die weten wat ze aanraken of -klikken, mag je hopen. En echt mooi is het niet. Daarom wordt voor het aangeven van de focus de pseudo-class :focus-visible gebruikt: bij gebruik van het toetsenbord is de focus-indicatie zichtbaar, bij gebruik van touchscreen of muis niet.

In dit voorbeeld wordt JavaScript gebruikt om bij het indrukken van Escape een geopend submenu te sluiten. Daarbij wordt de focus teruggezet op de bij het submenu horende <button>. Volgens de (op dit punt ietwat onduidelijke) specificatie moet daarbij gehandeld worden zoals bij het laatste element dat de focus had: als dat een focus-indicatie had, moet de <button> die ook krijgen, als dat geen focus-indicatie had, hoort de <button> die niet te krijgen.

Dit is precies zoals Firefox het doet. Safari (en daarmee ook alle browsers op iOS en iPadOS) en alle op Chromium gebaseerde browsers (onder andere Google Chrome, Edge en Vivaldi) geven de <button> na het indrukken van Escape altijd een focus-indicatie, ongeacht de focus-indicatie van het laatste element dat de focus had. Firefox lijkt het hier correct geïmplementeerd te hebben.

Dit is niet echt storend of zo, het wordt vooral voor de volledigheid vermeld. In het ergste geval krijgt een gebruiker van de muis of een touchscreen een blauw randje om een knop te zien. Vermoedelijk zal dit niet direct tot een stijging van het gebruik van hartbewakingsapparatuur leiden.

Probleem: zonder JavaScript loopt de Tab-toets door álle submenu's.

Dit wordt uitgebreider beschreven onder het kopje Zonder Javascript bij Probleem: bij gebruik van ...

Met JavaScript aan is er geen enkel probleem.

Tekstbrowsers

Geen problemen.

WebbIE en Lynx tonen de links in het menu als een serie lijsten met links.

Schermlezers

Probleem: NVDA in Firefox leest '«' voor als 'dubbele pijl omlaag'.

In de zes <button>'s met submenu wordt het teken '«' gebruikt om aan te geven of een submenu is geopend of niet. Dit teken wordt weergegeven met behulp van content en ::after. Voor schermlezers heeft dit teken geen enkel nut. Daarom wordt het weergegeven met behulp van:

content: "«" / "";

Het deel voor de '/' verschijnt op het scherm, het deel achter de '/' is bedoeld voor schermlezers. Hier is dat "": helemaal niets. Er wordt dus niets voorgelezen.

Firefox ondersteunt dit echter niet, waardoor NVDA in Firefox 'dubbele pijl omlaag' voorleest. Safari (en daarmee ook alle andere browsers op iOS en iPadOS) ondersteunt dit ook niet, maar die leest het hele teken gewoon niet voor.

(Standaard wordt NVDA alleen getest in combinatie met Firefox. Bij een snelle test met de combinatie NVDA en Chrome/Edge blijken deze dit inderdaad niet voor te lezen.)

(Als je een breder browservenster voldoende inzoomt, gaat het menu zich hetzelfde gedragen als in een smaller venster. Daarin zit een extra <button> met ook dit teken. Hier speelt dit echter niet, want bij die <button> zit de '«' in een extra <span>, die met de WAI-ARIA-code aria-hidden="true" wordt verborgen voor schermlezers. Bij de zes <button>'s met submenu's kan zo'n extra <span> helaas niet worden gebruikt, want dan kan de '«' niet goed worden gepositioneerd.)

Probleem: zonder JavaScript is het menu nauwelijks bruikbaar voor schermlezers

Alles werkt wel, maar vraag niet hoe. Dit wordt uitgebreider beschreven bij Zonder JavaScript hierboven.

Zoomen en andere lettergrootte

Probleem: bij heel sterk vergroten kan in Safari op OS X de menubalk, en dus de submenu's, niet worden getoond.

Als in Safari op OS X het menu sterk wordt vergroot, wordt dit weergegeven alsof het in een klein browservenster staat. Omdat in Safari een <button> geen focus kan krijgen, kan de menubalk hierdoor niet worden getoond, en daarmee ook niet de daarin zittende submenu's.

In een breder browservenster speelt dit niet, want daarin is de menubalk altijd zichtbaar. Ook met JavaScript aan is er geen probleem, want daarin wordt tonen en verbergen van menubalk en submenu's niet met focus geregeld.

Probleem: bij een andere lettergrootte staat het teken '«' niet meer op de goede plaats.

Openteken staat te hoog, sluitteken te laag.

Op de afbeelding is de tekst vergroot tot 200%. Hierdoor staat het '«' niet meer op de juiste plaats, niet achter 'openen' en niet achter 'sluiten'.

Omdat alles verder prima werkt en zo is dit niet echt een groot probleem. Het ziet er alleen wat minder netjes uit.

Bij zoomen speelt dit probleem niet, het speelt alleen bij een andere lettergrootte.

Overige problemen

Probleem: als de tekst wordt gescrold, is in bredere browservensters in oudere versies van iOS de tekst zichtbaar tussen menu en bovenkant van het venster.

In bredere browservensters staan de zes <button>'s met de submenu's in een menubalk, iets onder de bovenkant van het venster. Onder het menu staat opvultekst, die hoger is dan het venster.

Bij scrollen van de tekst zou die tekst zichtbaar worden in de kier tussen menubalk en bovenkant van het browservenster. Daarom wordt die ruimte met behulp van .menubalk::before afgeschermd met dezelfde kleur als de achtergrond van de pagina.

In iOS 12.5.7 werkt die afscherming niet: de tekst is daarin zichtbaar in de kier. In iOS 15 werkt het wel. Omdat deze oudere versies van iOS nauwelijks nog worden gebruikt, en omdat het alleen om een klein cosmetisch probleem gaat, is niet echt gezocht naar een oplossing.

Omdat op iOS álle browsers verplicht werken met webkit, de weergave-machine van Safari, geldt dit voor alle browsers op oudere versies van iOS.

Probleem: li:has(ul:hover) valideert niet.

De css-validator van w3c geeft een foutmelding bij het in de css gebruikte li:has(ul:hover), omdat de validator altijd wat achterloopt. In dit geval verslikt de validator zich in de binnen :has() gebruikte pseudo-class :hover. Pseudo-classes en dergelijke binnen :has() staan nog niet zolang in de ontwerpspecificatie en formeel moeten ze nog worden goedgekeurd, maar ze werken al wel. Daarom kan deze foutmelding worden genegeerd.

Probleem: als in Firefox een submenu wordt geopend door over een <button> te hoveren, verandert bij hoveren over het submenu de stand van het teken '«' ten onrechte van de stand voor geopend naar de stand voor gesloten.

Bij hoveren over een <button> opent een submenu. Hierbij wordt ook de stand van het teken '«' aangepast. Als je vervolgens over het geopende submenu hovert, moet de stand van '«' hetzelfde blijven: geopend. Om dit voor elkaar te krijgen, wordt gebruik gemaakt van de selector li:has(ul:hover). Omdat Firefox :has() niet ondersteunt, werkt dat in Firefox niet en verandert de '«' ten onrechte weer naar de stand voor gesloten.

Firefox is bezig de ondersteuning voor :has() te implementeren, dus binnenkort zal dit zijn opgelost. Bovendien gaat het om een kleinigheid, dus er is verder niet gezocht naar een oplossing hiervoor.

Wijzigingen

Alleen grotere wijzigingen worden hier vermeld, geen dingen als een link die is geüpdatet.

:

Nieuw opgenomen.

13 januari 2010:

Bij Bekende problemen (en oplossingen) → Toegankelijkheid stukje over Opera herschreven. Opera blijkt 'n aantal fantastische mogelijkheden te hebben, die helaas nauwelijks te vinden zijn.

6 februari 2011:

5 maart 2012:

Volledig herschreven.

31 maart 2012:

Paar kleine onjuistheden uit stuk over Javascript gehaald.

5 augustus 2023:

Volledig herschreven. Alleen de belangrijkste wijzigingen staan hieronder.

Inhoud van de download en licenties

De inhoud van deze download kan vrij worden gebruikt, met drie beperkingen:

* Sommige onderdelen die van 'n andere site of zo afkomstig zijn, vallen mogelijk onder een of andere licentie. Dat is hieronder bij het betreffende onderdeel te vinden.

* Je gebruikt het materiaal uit deze download volledig op eigen risico. Het kan prima zijn dat er fouten in de hier verstrekte code en dergelijke zitten. Voor eventuele schade die door gebruik van materiaal uit deze download ontstaat, in welke vorm dan ook, zijn www.css-voorbeelden.nl en medewerkers daarvan op geen enkele manier verantwoordelijk.

* Dit voorbeeld (en de bijbehorende uitleg en dergelijke) wordt min of meer regelmatig bijgewerkt. Het is daarom niet toegestaan dit voorbeeld (en de bijbehorende uitleg en dergelijke) op welke manier dan ook te verspreiden, zonder daarbij duidelijk te vermelden dat voorbeeld, uitleg, en dergelijke afkomstig zijn van www.css-voorbeelden.nl en dat daar altijd de nieuwste versie is te vinden. Dit is om te voorkomen dat er verouderde versies worden verspreid.

Een link naar www.css-voorbeelden.nl wordt trouwens altijd op prijs gesteld.

menu-066-dl.html: de pagina met het voorbeeld.

menu-066.pdf: deze uitleg (aangepast aan de inhoud van de download).

menu-066-inhoud-download-en-licenties.txt: een kopie van de tekst onder dit kopje (Inhoud van de download en licenties).

066-css-dl:

menu-066-dl.css: stylesheet voor menu-066-dl.html.

menu-066-hulp-dl.css: stylesheet voor de hulppagina.

066-js-dl:

menu-066-dl.js: javascript voor het menu.

066-files-dl:

menu-066-hulp-dl.html: pagina achter de links in het menu.

HTML

De code is geschreven in een afwijkende lettersoort. De code die te maken heeft met de basis van dit voorbeeld (essentiële code), is in de hele uitleg onderstippeld blauw. Alle niet-essentiële code is bruin. (In de inhoudsopgave staat alles in een gewone letter vanwege de leesbaarheid.)

In de html hieronder wordt alleen de html besproken, waarover iets meer is te vertellen. Een <h1> bijvoorbeeld wordt in de regel niet genoemd, omdat daarover weinig interessants valt te melden. (Als bijvoorbeeld het uiterlijk van de <h1> wordt aangepast met behulp van css, staat dat verderop bij de bespreking van de css.)

Zaken als een doctype en charset hebben soms wat voor veel mensen onbekende effecten, dus daarover wordt hieronder wel een en ander geschreven.

Omdat de html voor de hulppagina's achter de links heel simpel is, wordt die hier niet besproken.

<!doctype html>

Een document moet met een doctype beginnen om weergaveverschillen tussen browsers te voorkomen. Zonder doctype is de kans op verschillende (en soms volkomen verkeerde) weergave tussen verschillende browsers heel erg groot.

Geldige doctypes vind je op www.w3.org/QA/2002/04/valid-dtd-list.

Gebruik het volledige doctype, inclusief de eventuele url, anders werkt het niet goed.

Het hier gebruikte doctype is dat van html5. Dit kan zonder enig probleem worden gebruikt: het werkt zelfs in Internet Explorer 6.

<html lang="nl tabindex="-1"">

Het attribuut lang="nl" bij <html> geeft aan dat de pagina in het Nederlands is. De taal is van belang voor schermlezers, automatisch afbreken, automatisch genereren van aanhalingstekens, juist gebruik van decimale punt of komma, en dergelijke.

Op iPadOS wordt het submenu niet gesloten, als het scherm buiten het menu wordt aangeraakt. Door aan <html> een negatieve tabindex te geven, gebeurt dat wel.

<meta charset="utf-8">

Zorgt dat de browser letters met accenten en dergelijke goed kan weergeven.

utf-8 is de beste charset (tekenset), omdat deze alle talen van de wereld (en nog heel veel andere extra tekens) bestrijkt, maar toch niet meer ruimte inneemt voor de code, dan nodig is. Als je utf-8 gebruikt, hoef je veel minder entiteiten (&auml; en dergelijke) te gebruiken, maar kun je bijvoorbeeld gewoon ä gebruiken.

Deze regel moet zo hoog mogelijk komen te staan, als eerste regel binnen de <head>, omdat de regel anders door sommige browsers niet wordt gelezen.

In html5 hoeft deze regel niet langer te zijn, dan wat hier staat.

<meta name="viewport" content="width=device-width, initial-scale=1">

Mobiele apparaten variëren enorm in grootte. En dat is een probleem. Sites waren, in ieder geval tot enkele jaren geleden, gemaakt voor desktopbrowsers. En die hebben, in vergelijking met bijvoorbeeld een smartphone, heel brede browservensters. Hoe moet je op 'n smartphone een pagina weergeven, die is gemaakt voor de breedte van een desktop? Je kunt natuurlijk wachten tot álle sites zijn omgebouwd voor smartphones, tablets, enzovoort, maar dan moet je waarschijnlijk heel erg lang wachten.

Mobiele browsers gokken erop dat een pagina een bepaalde breedte heeft. Safari voor mobiel bijvoorbeeld gaat ervan uit dat een pagina 980 px breed is. De pagina wordt vervolgens zoveel versmald dat deze binnen het venster van het apparaat past. Op een iPhone wordt de pagina dus veel smaller dan op een iPad. Vervolgens kan de gebruiker inzoomen op het deel van de pagina dat deze wil zien.

Dit betekent ook dat bij het openen van de pagina de tekst meestal heel erg klein wordt weergegeven. (Meestal, want niet alle browsers en apparaten doen het op dezelfde manier.) Niet erg fraai, maar bedenk maar 'ns 'n betere oplossing voor bestaande sites.

Nieuwe sites of pagina's kunnen echter wel rekening houden met de veel kleinere vensters van mobiele apparaten. In dit voorbeeld bijvoorbeeld wordt in smallere vensters de menubalk verborgen en wordt het menu alleen op verzoek getoond.

Maar die stomme mobiele browser weet dat niet, dus die gaat ervan uit dat ook deze pagina 980 px breed is, en verkleint die dan. Dat is ongeveer even behulpzaam als de gedienstige kelner die behulpzaam de stoel naar achteren trekt, net als jij wilt gaan zitten.

Om de door de browser aangeboden hulp vriendelijk maar beslist te weigeren, wordt deze tag gebruikt. Hiermee geef je aan dat de pagina is geoptimaliseerd voor mobiele apparaten.

De kreet width=device-width zegt tegen de mobiele browser dat de breedte van de weer te geven pagina gelijk is aan de breedte van het apparaat. Als een iPad in portretstand bijvoorbeeld 768 px breed is, wordt de pagina ook 768 px breed.

Er staat nog een tweede deel in de tag: initial-scale=1. Sommige mobiele apparaten zoomen een pagina gelijk in of uit. Ook weer in een poging behulpzaam te zijn. Ook dat is hier niet nodig. Er is ook een instructie om zoomen helemaal onmogelijk te maken, maar die wordt niet gebruikt. De bezoeker kan zelf nog gewoon zoomen, wat belangrijk is voor mensen die wat slechter zien.

<h2>Toegankelijk responsief dropdownmenu</h2>

Voor gebruikers van schermlezers is onduidelijk, wat voor menu dit is. Daarom is gelijk onder de <nav> een kopregel met een titel aangebracht. Deze wordt buiten het scherm geparkeerd, zodat de lay-out niet wordt verstoord. Ook al staat deze regel buiten het scherm, schermlezers lezen de regel toch gewoon voor.

De titel staat in een <h2>, omdat veel gebruikers van schermlezers een pagina scannen door van <h> naar <h> te springen. Door de titel in een <h2> te zetten, wordt ook dan de titel voorgelezen.

<button type="button" class="voor-smal"><span>Menu&nbsp;</span><span aria-hidden="true">«</span></button>

In smallere browservensters is er geen ruimte voor een horizontale menubalk met zes <button>'s. Daarom wordt die menubalk in smalle vensters verborgen. In deze smalle vensters staat bovenin het venster een <button>, waarmee de zes <button>'s met de submenu's getoond en verborgen kunnen worden. In bredere vensters wordt deze knop verborgen.

Het attribuut type="button" doet wat vreemd aan binnen de tag <button>, maar zonder dit attribuut denkt de browser dat het een knop is om een formulier te verzenden.

&nbsp; is een zogenaamde 'harde spatie'. De letters zijn een afkorting van 'non-breaking space'. Met behulp van ::after wordt achter 'Menu', afhankelijk van het zichtbaar zijn van menubalk, 'openen' of 'sluiten' geplaatst. Na 'Menu' moet een spatie komen, omdat er anders 'Menuopenen' of 'Menusluiten' komt te staan. Een gewone spatie verdwijnt echter, daarom wordt een harde spatie gebruikt.

Om aan te geven of een menu is geopend of niet, wordt het teken '«' gebruikt. Afhankelijk van het geopend of gesloten zijn van het menu, wordt dit teken naar boven of naar beneden gedraaid. '«' is een gewoon leesteken en wordt daarom door sommige schermlezers voorgelezen, terwijl dit voor schermlezers geen enkel nut heeft. Daarom wordt het voor schermlezers met behulp van de WAI-ARIA-code aria-hidden="true" voor schermlezers verborgen.

Als het bijbehorende JavaScript wordt uitgevoerd, wordt aan button.voor-smal een id toegevoegd:

<button class="voor-smal" id="voor-smal-js">

Omdat een id áltijd meer specificiteit, meer gewicht, heeft dan een class, kunnen hierdoor op een vrij simpele manier selectors worden gemaakt, die alleen werken als JavaScript wordt uitgevoerd, en die dan 'winnen' van de selectors die bedoeld zijn voor als JavaScript ontbreekt. (Als je deze veranderingen wilt zien, moet je niet naar de gewone code, maar naar de Gegenereerde code kijken.)

<a class="skippy" href="#content">Skip menu</a>

Dit is een zogenaamde 'skip-link'. Mensen die de Tab-toets gebruiken om over de pagina te navigeren, kunnen met behulp hiervan in één keer de menubalk met de zes knoppen passeren. Normaal genomen is deze link onzichtbaar, pas als de link door indrukken van de Tab-toets de focus krijgt, wordt deze zichtbaar. Door de Tab-toets nogmaals in te drukken, verdwijnt de skip-link weer.

Als de link wordt gevolgd, wordt in één keer naar main#content gegaan, de belangrijkste inhoud van de pagina (wat hier flauwekultekst is).

Als het bijbehorende JavaScript wordt uitgevoerd, wordt aan a.skippy een id toegevoegd:

<a class="skippy" id="skippy-js">

Omdat een id áltijd meer specificiteit, meer gewicht, heeft dan een class, kunnen hierdoor op een vrij simpele manier selectors worden gemaakt, die alleen werken als JavaScript wordt uitgevoerd, en die dan 'winnen' van de selectors die bedoeld zijn voor als JavaScript ontbreekt.

Deze id wordt gebruikt om de skip-link in smallere browservensters te verbergen, want daar moet de menubalk eerst worden geopend. Daardoor hoef je daarin niet eerst langs de zes <button>'s in de menubalk.

(Als je deze veranderingen wilt zien, moet je niet naar de gewone code, maar naar de Gegenereerde code kijken.)

<ul class="menubalk">

Binnen ul.menubalk staat vrijwel het hele menu.

Als het bijbehorende JavaScript wordt uitgevoerd, wordt aan ul.menubalk een id toegevoegd:

<ul class="menubalk" id="menubalk-js">

Omdat een id áltijd meer specificiteit, meer gewicht, heeft dan een class, kunnen hierdoor op een vrij simpele manier selectors worden gemaakt, die alleen werken als JavaScript wordt uitgevoerd, en die dan 'winnen' van de selectors die bedoeld zijn voor als JavaScript ontbreekt. (Als je deze veranderingen wilt zien, moet je niet naar de gewone code, maar naar de Gegenereerde code kijken.)

<button type="button" aria-controls="menu-1">Menu 1</button>

<ul id="menu-1">

Gelijk voor elke <ul> met een submenu staat een <button>. Om aan schermlezers duidelijk te maken dat de <button> bij de daarop volgende <ul> hoort, krijgt elke <ul> een id. In de <button> staat de bijbehorende WAI-ARIA-code aria-controls, die verwijst naar de <ul>.

Het attribuut type="button" doet wat vreemd aan binnen de tag <button>, maar zonder dit attribuut denkt de browser dat het een knop is om een formulier te verzenden.

<main id="content" tabindex="-1">

Op iOS wordt het submenu niet gesloten, als het scherm buiten het menu wordt aangeraakt. Door aan <main> een negatieve tabindex te geven, gebeurt dat wel.

Om dezelfde reden is ook aan <html> een negatieve tabindex gegeven. Op nieuwere versies van iOS en op iPadOS is die tabindex bij <html> voldoende, maar voor oudere versies van iOS moet ook <main> een tabindex krijgen.

<p lang="la">

Als opvultekst is Latijnse tekst gebruikt. De taal is van belang voor schermlezers, automatisch afbreken, automatisch genereren van aanhalingstekens, juist gebruik van decimale punt of komma, en dergelijke. Daarom wordt met lang="la" aangegeven dat de tekst binnen de <p>'s Latijn is. (En tot mijn niet geringe verbazing blijkt een schermlezer als NVDA dat dan, voor zover ik dat kan beoordelen, op de juiste manier voor te lezen.)

<script src="066-js-dl/menu-066-dl.js"></script>

Vrijwel onderaan de pagina staat een koppeling naar een extern JavaScript.

Dit script gebruikt een aantal onderdelen van de pagina, zoals alle <button>'s. Om deze onderdelen te kunnen gebruiken, moeten ze eerst door de browser worden gemaakt. Daarom staat het script helemaal onderaan de html: je weet dan zeker, dat alle elementen al zijn aangemaakt.

Bovendien stopt de opbouw van de pagina, zodra de browser een script ontmoet, omdat het script eerst wordt uitgevoerd. Het zou immers kunnen dat het script (heel) belangrijk is voor de weergave. Dat is hier niet het geval. Door het script onderaan de pagina te zetten, voorkom je ook die in dit geval overbodige vertraging.

Als het script op slechts één pagina wordt gebruikt, zoals hier het geval is, kun je het hele script ook tussen <script> en </script> zetten, onderaan de pagina zelf. Als het op meer pagina's wordt gebruikt, is het beter om het in een eigen bestand te zetten. Het hoeft dan maar één keer te worden gedownload.

In dit geval had het script dus gewoon binnen de pagina kunnen worden gezet. Maar het is vanwege de overzichtelijkheid toch in een apart bestand gezet: nu kun je het apart bekijken.

CSS

De code is geschreven in een afwijkende lettersoort. De code die te maken heeft met de basis van dit voorbeeld (essentiële code) is in de hele uitleg onderstippeld blauw. Alle niet-essentiële code is bruin. (In de inhoudsopgave staat alles in een gewone letter vanwege de leesbaarheid.)

De css voor de hulppagina's achter de links is heel simpel, daarom wordt die hier niet besproken.

Technisch gezien is er geen enkel bezwaar om de css in de stylesheet allemaal achter elkaar op één regel te zetten:

div#header-buiten {position: absolute; right: 16px; width: 100%; height: 120px; background: yellow;} div p {margin-left 16px; height: 120px; text-align: center;}

Maar als je dat doet, krijg je gegarandeerd hele grote problemen, omdat het volstrekt onoverzichtelijk is. Beter is het om de css netjes in te laten springen:

              div#header-buiten {
		position: absolute;
		right: 16px;
		width: 100%;
		height: 120px;
		background: yellow;
	}

	div p {
		margin-left: 16px;
		height: 120px;
		text-align: center;
	}

Hiernaast is het heel belangrijk voldoende commentaar (uitleg) in de stylesheet te schrijven. Op dit moment weet je waarschijnlijk (hopelijk...), waarom je iets doet. Maar over vijf jaar kan dat volstrekt onduidelijk zijn. Op deze site vind je nauwelijks commentaar in de stylesheets, maar dat heeft een simpele reden: deze uitleg is in feite één groot commentaar.

Op internet zelf is het goed, als de stylesheet juist zo klein mogelijk is. Dus voor het uploaden kun je normaal genomen het beste het commentaar weer verwijderen. Veel mensen halen zelfs alles wat overbodig is weg, voordat ze de stylesheet uploaden. Inspringingen bijvoorbeeld zijn voor mensen handig, een computer heeft ze niet nodig.

Je hebt dan eigenlijk twee stylesheets. De uitgebreide versie waarin je dingen uitprobeert, verandert, enzovoort, met commentaar, inspringingen, en dergelijke. Dat is de mensvriendelijke versie. Daarnaast is er dan een stylesheet die je op de echte site gebruikt: een gecomprimeerde versie.

Dat comprimeren kun je met de hand doen, maar er bestaan ook hulpmiddelen voor. Op de pagina met links kun je onder het kopje Gereedschap → Snelheid, testen, gzip, CLS, comprimeren (inclusief theorie) links naar sites vinden, waar je bestanden kunt comprimeren.

(Stylesheets op deze site zijn niet gecomprimeerd. Omdat het vaak juist om de css gaat, kunnen mensen dan zonder al te veel moeite de css bekijken.)

css voor alle vensters

/* menu-066-dl.css */

Om vergissingen te voorkomen is het een goede gewoonte bovenaan het stijlbestand even de naam neer te zetten. Voor je het weet, zit je anders in het verkeerde bestand te werken.

body

Het element waarbinnen de hele pagina staat. Veel instellingen die hier worden opgegeven, worden geërfd door de nakomelingen van <body>. Ze gelden voor de hele pagina, tenzij ze later worden gewijzigd. Dit geldt bijvoorbeeld voor de lettersoort, de lettergrootte en de voorgrondkleur.

background: #ff9;

Achtergrondkleurtje.

color: black;

Voorgrondkleur zwart. Dit is onder andere de kleur van de tekst.

Hoewel dit de standaardkleur is, wordt deze toch specifiek opgegeven. Hierboven is een achtergrondkleur opgegeven. Sommige mensen hebben zelf de voorgrond‑ en/of achtergrondkleur veranderd, bijvoorbeeld omdat ze slecht kleuren kunnen onderscheiden. Als nu de achtergrondkleur wordt veranderd, maar niet de voorgrondkleur, loop je het risico dat tekstkleur en achtergrondkleur te veel op elkaar gaan lijken.

Door beide op te geven, is redelijk zeker dat achtergrond‑ en tekstkleur genoeg van elkaar blijven verschillen. Als de gebruiker !important heeft gebruikt in een eigen stylesheet, is er nog niets 1aan de hand, want dan veranderen achtergrond‑ en voorgrondkleur geen van beide.

font-family: Arial, Helvetica, sans-serif;

Als Arial is geïnstalleerd op de machine van de bezoeker, wordt deze gebruikt, anders Helvetica. Als die ook niet wordt gevonden, wordt in ieder geval een schreefloze letter (zonder dwarsstreepjes) gebruikt.

margin: 0;

Slim om te doen vanwege verschillen tussen browsers.

h2

Alle <h2>'s. Een kopregel voor schermlezers, zodat die weten wat voor menu het is.

position: absolute;

Om de <h2> op een bepaalde plaats neer te kunnen zetten.

Er wordt gepositioneerd ten opzichte van het 'containing block'. Dat is de eerste voorouder die zelf een bepaalde eigenschap heeft, zoals een andere positie dan statisch. Als zo'n voorouder er niet is, zoals hier het geval is, wordt gepositioneerd ten opzichte van het venster van de browser.

top: -2000px; left: -20000px;

Ver boven en links van het browservenster neerzetten. Ook aan de bovenkant buiten het venster, want als de <h2> alleen links buiten het venster wordt neergezet, kan deze soms toch in schermlezers links blokkeren.

.voor-smal

Menubalk in smal venster.

Alle elementen met class="voor-smal". Dat is er maar één: de op de afbeelding zichtbare <button> voor smallere browservensters, waarmee de menubalk met de zes <button>'s met submenu's verborgen of getoond kan worden. In bredere vensters is voldoende ruimte om de menubalk altijd te tonen.

appearance: none;

Hoewel de <button> moet werken als een <button>, moet deze eruit zien als een balk bovenin het browservenster. Daarom wordt het standaard uiterlijk van een <button> (dat in elke browser verschilt) verwijderd.

background: #e4e4e4;

Zilverkleurige achtergrondkleur.

color: black;

Voorgrondkleur zwart. Dit is onder andere de kleur van de tekst.

Hoewel dit de standaardkleur is, wordt deze toch specifiek opgegeven. Hierboven is een achtergrondkleur opgegeven. Sommige mensen hebben zelf de voorgrond‑ en/of achtergrondkleur veranderd, bijvoorbeeld omdat ze slecht kleuren kunnen onderscheiden. Als nu de achtergrondkleur wordt veranderd, maar niet de voorgrondkleur, loop je het risico dat tekstkleur en achtergrondkleur te veel op elkaar gaan lijken.

Door beide op te geven, is redelijk zeker dat achtergrond‑ en tekstkleur genoeg van elkaar blijven verschillen. Als de gebruiker !important heeft gebruikt in een eigen stylesheet, is er nog niets aan de hand, want dan veranderen achtergrond‑ en voorgrondkleur geen van beide.

Dit is ook al bij <body> opgegeven, maar sommige mensen hebben bij álle elementen de kleuren veranderd. Het heeft immers weinig zin, als ze dat alleen bij de body doen, terwijl de sitebouwer de kleuren ook bij bijvoorbeeld de paragrafen heeft aangepast.

width: 100%;

Bij een fixed gepositioneerd element is de breedte normaal genomen ten opzichte van het venster van de browser. Hierdoor wordt button.voor-smal altijd even breed als het venster, ongeacht de breedte van het venster.

Later wordt in de <button> een tekst gezet met een witte achtergrond. Die tekst is veel smaller dan het venster van de browser, waardoor de <button> ook smaller lijkt. In werkelijkheid werkt de hele grijze balk als een <button>. Je zou die werking ook kunnen beperken tot het witte vlak met de tekst, maar dat lijkt niet echt nodig en zou weer extra code opleveren.

font-size: 1.1em;

Iets grotere letter.

Als eenheid wordt de relatieve eenheid em gebruikt, omdat bij gebruik van een absolute eenheid zoals px niet alle browsers de lettergrootte kunnen veranderen. Zoomen kan wel altijd, ongeacht welke eenheid voor de lettergrootte wordt gebruikt.

text-align: left;

Tekst in de <button> links uitlijnen.

border: none;

Hoewel iets hierboven bij appearance het standaard uiterlijk van de <button> is verwijderd, heeft de <button> in sommige browsers toch nog een border. Die wordt hier alsnog verwijderd.

border-bottom: black solid 1px;

Aan de onderkant komt wel een zwart randje.

padding: 0;

In sommige browsers blijft de <button> ook nog een padding houden, die wordt ook verwijderd.

position: fixed;

Om de <button> op een bepaalde plaats neer te kunnen zetten.

Er wordt gepositioneerd ten opzichte van het 'containing block'. Dat is bij een fixed positie normaal genomen het venster van de browser. Hierdoor scrolt de <button> niet mee met de pagina.

top: 0;

Bovenaan het browservenster neerzetten.

z-index: 10;

Normaal genomen worden elementen in de volgorde van de html op het scherm gezet. Wat later in de html staat, wordt over eerdere elementen gezet. Om te zorgen dat het menu altijd bovenaan staat, krijgt het een hogere z-index.

Een z-index werkt alleen in bepaalde omstandigheden. Eén van die omstandigheden is een fixed positie. Die is iets hierboven aan de <button> gegeven, dus dat is geregeld.

.voor-smal::after

Met behulp van ::after wordt bij de elementen met class="voor-smal" een pseudo-element gemaakt. Hier is maar één element met deze class: de <button> waarmee in smallere browservensters de menubalk met de zes <button>'s met submenu's kan worden getoond of verborgen. Dit pseudo-element wordt gebruikt om achter het woordje 'Menu' het woordje 'openen' of 'sluiten' te zetten.

background: white;

Witte achtergrond.

color: black;

Voorgrondkleur zwart. Dit is onder andere de kleur van de tekst.

Hoewel dit de standaardkleur is, wordt deze toch specifiek opgegeven. Hierboven is een achtergrondkleur opgegeven. Sommige mensen hebben zelf de voorgrond‑ en/of achtergrondkleur veranderd, bijvoorbeeld omdat ze slecht kleuren kunnen onderscheiden. Als nu de achtergrondkleur wordt veranderd, maar niet de voorgrondkleur, loop je het risico dat tekstkleur en achtergrondkleur te veel op elkaar gaan lijken.

Door beide op te geven, is redelijk zeker dat achtergrond‑ en tekstkleur genoeg van elkaar blijven verschillen. Als de gebruiker !important heeft gebruikt in een eigen stylesheet, is er nog niets aan de hand, want dan veranderen achtergrond‑ en voorgrondkleur geen van beide.

Dit is ook al bij <body> opgegeven, maar sommige mensen hebben bij álle elementen de kleuren veranderd. Het heeft immers weinig zin, als ze dat alleen bij de body doen, terwijl de sitebouwer de kleuren ook bij bijvoorbeeld de paragrafen heeft aangepast.

content: "openen";

Met behulp van content wordt het woordje 'openen' weergegeven.

box-sizing: border-box;

Hier iets onder worden een breedte en een regelhoogte opgegeven. (Omdat verder geen hoogte wordt opgegeven, is de regelhoogte ook de gewone hoogte). Standaard worden een border en een padding bij deze breedte en hoogte opgeteld. Het element wordt daardoor dus breder en/of hoger dan de opgegeven breedte en hoogte.

Hier komt dat slecht uit. Met de hier opgegeven waarde bij box-sizing komen border en padding bínnen de opgegeven breedte en hoogte te staan, waardoor het element niet breder en hoger wordt dan de opgegeven breedte en hoogte.

display: inline-block;

Een met behulp van ::after gemaakt pseudo-element is van zichzelf een inline-element. Hierdoor zijn eigenschappen als breedte en hoogte niet te gebruiken. Een inline-block is een soort kruising tussen een inline- en een blok-element. Het komt niet op een nieuwe regel te staan, maar eigenschappen als hoogte en breedte zijn wel te gebruiken.

width: 4.8em;

Breedte. Dit is breder dan nodig is voor het woordje 'openen' (en dus helemaal voor het woordje 'sluiten'). Hierdoor ontstaat er rechts van 'openen' ruimte om daar het teken '«' neer te zetten.

Als eenheid wordt de relatieve eenheid em gebruikt, zodat de breedte mee verandert met de lettergrootte. Bij gebruik van een absolute eenheid zoals px verandert de breedte niet mee met de lettergrootte. Zoomen kan wel altijd, ongeacht welke eenheid voor de breedte wordt gebruikt.

line-height: 2.6em;

Regelhoogte.

Omdat geen gewone hoogte voor het pseudo-element is opgegeven, is dit ook gelijk de hoogte van het pseudo-element. Tekst wordt automatisch halverwege de regelhoogte neergezet, dus de tekst staat gelijk netjes verticaal gecentreerd binnen het pseudo-element, en daarmee op gelijke hoogte met het woordje 'Menu' in de <button>.

Als eenheid wordt de relatieve eenheid em gebruikt, omdat bij gebruik van een absolute eenheid zoals px de regelhoogte niet mee verandert met de lettergrootte. Zoomen kan wel altijd, ongeacht welke eenheid voor de regelhoogte wordt gebruikt.

border-top: white solid 3px; border-right: black solid 1px; border-bottom: white solid 3px; Deel van border in knop voor smalle vensters zichtbaar gemaakt.

De border-right zorgt voor het zwarte verticale lijntje rechts van de tekst.

De witte borders boven en rechts zijn op de afbeelding even zichtbaar gemaakt door ze zwart te maken. Omdat ze in het voorbeeld wit zijn, zijn ze normaal genomen niet te zien.

Als de <button> de focus heeft, wordt dit voor gebruikers van de Tab-toets aangegeven door een blauwe border met een breedte van 3 px rondom het witte vlak in de <button> te zetten. In feite gaat het om drie borders. In de <button> zitten een <span> met het woord 'Menu', het hier besproken pseudo-element met 'openen' of 'sluiten', en een <span> met het teken «.

Die drie borders moeten op elkaar aansluiten. Om dat voor elkaar te krijgen, wordt hier alvast bij het pseudo-element ruimte gereserveerd voor die border. Als de border zichtbaar moet worden, hoeft deze dan alleen nog maar van kleur te veranderen.

.voor-smal span:first-child

Kinderen van dezelfde ouder kunnen worden geteld, net zoals dat bij kinderen uit een gezin het geval is: eerste kind, tweede kind, derde kind, enzovoort. Je kunt elementen selecteren op basis van het volgnummer binnen zo'n reeks kinderen. In dit geval wordt geen echt volgnummer gebruikt, maar :first-child: alleen het eerste kind. Omdat dit zo vaak voorkomt, is hier een apart sleutelwoord voor: first-child.

.voor-smal: Alle elementen met class="voor-smal". Dat is er maar één: een <button> voor smallere browservensters, waarmee de menubalk met de zes <button>'s met submenu's verborgen of getoond kan worden.

span: alle <span>'s binnen die <button>.

:first-child: alleen de <span>'s die een eerste kind zijn. Binnen deze <span> zit het woordje 'Menu'.

De hele selector in gewone taal: alle <span>'s binnen button.voor-smal, die een eerste kind zijn.

De in deze selector zittende pseudo-class :first-child kan onverwachte bijwerkingen hebben. In dit geval is er maar één element met class="voor-smal", dus er kan maar één <span> het eerste kind daarvan zijn. Maar als in het voorbeeld een tweede element met een class="voor-smal" zou worden gebruikt, zou deze selector ook voor eventuele <span>'s binnen dat element gelden, als ze een eerste kind zijn.

Je kunt dat opvangen door bijvoorbeeld nav voor de selector te zetten: nav .voor-smal span:first-child. Dan geldt de selector alleen voor <span>'s binnen een element met class="voor-smal" binnen een <nav>, en niet voor eventuele <span>'s binnen een ander element met class="voor-smal".

background: white;

Witte achtergrond.

color: black;

Voorgrondkleur zwart. Dit is onder andere de kleur van de tekst.

Hoewel dit de standaardkleur is, wordt deze toch specifiek opgegeven. Hierboven is een achtergrondkleur opgegeven. Sommige mensen hebben zelf de voorgrond‑ en/of achtergrondkleur veranderd, bijvoorbeeld omdat ze slecht kleuren kunnen onderscheiden. Als nu de achtergrondkleur wordt veranderd, maar niet de voorgrondkleur, loop je het risico dat tekstkleur en achtergrondkleur te veel op elkaar gaan lijken.

Door beide op te geven, is redelijk zeker dat achtergrond‑ en tekstkleur genoeg van elkaar blijven verschillen. Als de gebruiker !important heeft gebruikt in een eigen stylesheet, is er nog niets aan de hand, want dan veranderen achtergrond‑ en voorgrondkleur geen van beide.

Dit is ook al bij <body> opgegeven, maar sommige mensen hebben bij álle elementen de kleuren veranderd. Het heeft immers weinig zin, als ze dat alleen bij de body doen, terwijl de sitebouwer de kleuren ook bij bijvoorbeeld de paragrafen heeft aangepast.

display: inline-block;

Een <span> is van zichzelf een inline-element. Hierdoor zijn eigenschappen als regelhoogte niet te gebruiken. Een inline-block is een soort kruising tussen een inline- en een blok-element. Het komt niet op een nieuwe regel te staan, maar eigenschappen als regelhoogte zijn wel te gebruiken.

box-sizing: border-box;

Hier iets onder wordt een regelhoogte opgegeven. (Omdat verder geen hoogte wordt opgegeven, is de regelhoogte ook de gewone hoogte). Standaard worden een border en een padding bij deze hoogte opgeteld. Het element wordt daardoor dus hoger dan de opgegeven regelhoogte.

Hier komt dat slecht uit. Met de hier opgegeven waarde bij box-sizing komen border en padding bínnen de opgegeven hoogte te staan, waardoor het element niet hoger wordt dan de opgegeven regelhoogte.

line-height: 2.6em;

Regelhoogte.

Omdat geen gewone hoogte voor de <span> is opgegeven, is dit ook gelijk de hoogte van de <span>. Tekst wordt automatisch halverwege de regelhoogte neergezet, dus het woordje 'Menu' staat gelijk netjes verticaal gecentreerd binnen de <span>, en daarmee binnen de <button>.

Als eenheid wordt de relatieve eenheid em gebruikt, omdat bij gebruik van een absolute eenheid zoals px de regelhoogte niet mee verandert met de lettergrootte. Zoomen kan wel altijd, ongeacht welke eenheid voor de regelhoogte wordt gebruikt.

border: white solid 3px; border-right: none;

Witte border van 3 px breed, behalve rechts.

Als de <button> de focus heeft, wordt dit voor gebruikers van de Tab-toets aangegeven door een blauwe border met een breedte van 3 px rondom het witte vlak in de <button> te zetten. Voor deze border wordt hier alvast ruimte gereserveerd. Als de border zichtbaar moet worden, hoeft deze dan alleen nog maar van kleur te veranderen. Een iets uitgebreider verhaal hierover staat iets hoger bij border-top: white solid 3px;

padding-left: 5px;

Kleine ruimte links van de tekst.

pointer-events: none;

Als JavaScript aanstaat, wordt gekeken of een element met id="voor-smal-js" is aangeraakt of -geklikt. Als dat zo is, wordt de menubalk met de zes <button>'s met de submenu's getoond of verborgen. Dat element met id="voor-smal-js" is de hele <button> bovenaan het venster van de browser: het witte blok met de tekst en de daarop volgende grijze balk.

De hier besproken <span> met het woordje 'Menu' zit binnen deze <button>. Als je nu precies deze <span> aanraakt of -klikt, wordt deze <span> door JavaScript herkend als het element dat is aangeraakt of -geklikt. Dat is niet button#voor-smal-js, dus heeft het aanraken of -klikken van deze <span> geen enkel effect: de menubalk opent of sluit niet.

Met behulp van deze eigenschap wordt een aanraking of klik door de <span> genegeerd. De aanraking of klik gaat rechtstreeks naar de ouder van de <span>, en dat is button#voor-smal-js. Waardoor de aanraking of klik toch werkt, zoals deze is bedoeld.

.voor-smal span:last-child

De <span> binnen button.voor-smal die het laatste kind van die <button> is. Dit is de <span> waarbinnen de '«' zit. Een langere beschrijving van deze selector staat gelijk hierboven bij .voor-smal span:first-child. Het enige verschil: daar gaat het om het eerste kind, hier om het laatste kind.

display: inline-block;

Een <span> is van zichzelf een inline-element. Hierdoor zijn eigenschappen als breedte en hoogte niet te gebruiken. Een inline-block is een soort kruising tussen een inline- en een blok-element. Het komt niet op een nieuwe regel te staan, maar eigenschappen als breedte en hoogte zijn wel te gebruiken.

width: 34px; height: 34px; font-size: 1.2em; line-height: 30px; padding-left: 5px;

Het is tamelijk lastig om de <span> met de '«' in alle browsers op de juiste plaats te krijgen: op gelijke hoogte met de tekst ervoor en daar niet te dichtbij of te ver vanaf.

Wat het er niet makkelijk op maakt: het teken wordt 270° gedraaid als de zes <button>'s met de submenu's niet zichtbaar zijn, en 90° als deze wel zichtbaar zijn. Dat betekent bijvoorbeeld dat de breedte niet de breedte wordt, maar de hoogte.

Enig uitproberen levert deze combinatie op.

Omdat de meeste maten hier in de absolute eenheid px zijn, veranderen die niet mee met een andere lettergrootte. Hierdoor staat het teken '«' bij een andere lettergrootte niet meer netjes op gelijke hoogte als de rest van de tekst. Maar het is nog steeds prima zichtbaar, en als overal een relatieve eenheid als em wordt gebruikt, krijg je het teken bij een standaardlettergrootte niet in alle browsers op de juiste plaats.

pointer-events: none;

Als JavaScript aanstaat, bereikt een aanraking of klik de <button>, waar deze <span> in zit, niet. Hierdoor wordt de menubalk met de zes <button>'s met de submenu's niet getoond of verborgen, als de '«' wordt aangeraakt of -geklikt. Door deze regel gebeurt dat wel. Een langere beschrijving staat iets hierboven bij pointer-events: none; bij de eerste <span>, waar precies hetzelfde probleem speelt.

position: absolute; Afbeelding xxx: .

Om de <span> op een bepaalde plaats neer te kunnen zetten. Als dat niet zou gebeuren, komt de '«' op de gewone plaats te staan, afhankelijk van de volgorde in de html. Eerst komt dan de <span> met 'Menu', dan deze <span>, en als laatste het bij .voor-smal::after gemaakte pseudo-element met 'openen' of 'sluiten'.

Er wordt gepositioneerd ten opzichte van het 'containing block'. Dat is de eerste voorouder die zelf een bepaalde eigenschap heeft, zoals een andere positie dan statisch. Als zo'n voorouder er niet is, zoals hier het geval is, wordt gepositioneerd ten opzichte van het venster van de browser.

left: 5.3em;

Op 5,3 em vanaf links neerzetten. Op deze afstand komt de '«' gelijk naast 'openen' te staan.

Als eenheid wordt de relatieve eenheid em gebruikt, zodat de afstand vanaf links mee verandert met de lettergrootte. Bij gebruik van een absolute eenheid zoals px verandert die afstand niet mee met de lettergrootte. Hierdoor zou de '«' bij een grotere letter over 'openen' of 'sluiten' komen te staan. Zoomen kan wel altijd, ongeacht welke eenheid voor de hoogte wordt gebruikt.

transform: rotate(270deg);

Door de '«' 270° te draaien, komen de punten naar beneden te staan.

.voor-smal:focus

Voor dit element is eerder css opgegeven. Deze wordt binnen dit blokje herhaald in de volgorde, waarin deze in de stylesheet staat, zodat alles hier overzichtelijk bij elkaar staat.

.voor-smal {appearance: none; background: #e4e4e4; color: black; width: 100%; font-size: 1.1em; text-align: left; border: none; border-bottom: black solid 1px; padding: 0; position: fixed; top: 0; z-index: 10;}

Alle elementen met class="voor-smal". Dat is er maar één: de <button> voor smallere browservensters, waarmee de menubalk met de zes <button>'s met submenu's verborgen of getoond kan worden. Maar alleen als deze <button> de focus heeft.

outline: none;

Als een <button> de focus heeft, krijgt die <button> automatisch een outline. Dat is belangrijk voor gebruikers van de Tab-toets, omdat die anders niet weten welke <button> op een Enter of zo reageert. Die outline moet je daarom nooit zonder meer weghalen.

Hier wordt de focus aangegeven met een witte achtergrond en een brede blauwe rand. Daarom kan de standaard-outline bij focus worden weggehaald.

.voor-smal:focus-visible::after, .voor-smal:focus-visible span:first-child

Voor deze elementen is eerder css opgegeven. Deze wordt binnen dit blokje herhaald in de volgorde, waarin deze in de stylesheet staat, zodat alles hier overzichtelijk bij elkaar staat.

.voor-smal::after {background: white; color: black; content: "openen"; box-sizing: border-box; display: inline-block; width: 4.8em; line-height: 2.6em; border-top: white solid 3px; border-right: black solid 1px; border-bottom: white solid 3px;}

.voor-smal span:first-child {background: white; color: black; display: inline-block; box-sizing: border-box; line-height: 2.6em; border: white solid 3px; border-right: none; padding-left: 5px; pointer-events: none;}

Twee selectors, gescheiden door een komma. Het eerste deel van de twee selectors is hetzelfde:

.voor-smal:focus-visible: de elementen met class=".voor-smal", maar alleen als die elementen de focus hebben, én als zichtbaar moet zijn dat een element de focus heeft. Hier heeft maar één element class="focus": de <button> waarmee in smalle browservensters menubalk met de zes <button>'s met de submenu's getoond en verborgen kan worden.

Het tweede deel van de twee selectors verschilt wel:

::after: het bij .voor-smal::after gemaakte pseudo-element, waarin 'openen' of 'sluiten' staat.

span:first-child: de <span> die het eerste kind van button.voor-smal is. Binnen deze <span> staat het woordje 'Menu'. Een langere beschrijving van :first-child staat hierboven bij .voor-smal span:first-child.

Beide selectors samengevat: als button.voor-smal de focus heeft, doe dan iets met het bij button.voor-smal met ::after gemaakte pseudo-element, en met de eerste <span> in button.voor-smal. Oftewel: doe iets met de tekst in de <button>. En omdat het teken '«' bij .voor-smal span:first-child binnen het met ::after gemaakte pseudo-element is neergezet, doet dit ook mee.

Voor gebruikers van de Tab-toets is uiterst belangrijk dat ze kunnen zien, waar op de pagina ze zijn. Links, knoppen, en dergelijke kunnen door gebruik van de Tab-toets de focus krijgen. Als een link de focus heeft, wordt die link gevolgd door het indrukken van Enter. Als een keuzevakje (checkbox) de focus heeft, kan dit worden aan- of uitgevinkt met de Spatiebalk. Enzovoort.

Welk element de focus heeft, wordt aangegeven door een kadertjes. Als je dat kadertje weghaalt, kan een gebruiker van de Tab-toets niet meer zien, waar op de pagina die zich bevindt. Daarom mag je dat kadertje nooit zonder meer weghalen.

Maar een gebruiker van de muis of van een touchscreen heeft dat kadertje helemaal niet nodig, want die weet, waar met de muis is geklikt, of waar het scherm is aangeraakt. En voor het mooie hoef je dat kadertje niet te tonen. Het moet opvallen en is daardoor in de regel knap storend, als het overbodig is.

:focus-visible lost dit op. Het kadertje wordt alleen weergegeven, als dat zinvol is. Op touchscreens en bij gebruik van de muis wordt het niet weergegeven, en bij gebruik van de Tab-toets wel.

:focus-visible wordt pas ondersteund in iOS 15 en Safari 15.4 op OS X. Dat is geen probleem, want op iOS willen we toch geen focus-indicatie en oudere versies van Safari worden nauwelijks nog gebruikt.

border-color: blue; border-width: 3px; Knop in smalle vensters als deze focus heeft.

Bij .voor-smal::after en .voor-smal span:first-child is aan verschillende kanten een witte border van 3px breed aangebracht. Die border zie je niet, omdat die wegvalt tegen de witte achtergrond van het blokje met tekst. Nu wordt de kleur verandert van wit naar blauw, waardoor je de border wel ziet.

Aan het uiterlijk verandert verder niets, want de ruimte voor deze blauwe borders was als het ware al gereserveerd.

Op één border na: rechts is bij .voor-smal::after een zwarte border met een breedte van slechts 1 px neergezet. Daarom wordt hier ook de breedte aangepast. Nu staat rechts ook een blauwe border van 3 px breed. Dat die breedte rechts van 1 in 3 px verandert, waardoor het witte blokje iets breder wordt, maakt niets uit, want rechts van het witte blokje is lege ruimte zat.

.skippy

De elementen met class="skippy". Dat is er maar één: een skip-link, waarmee in één keer het hele menu gepasseerd kan worden.

background: white;

Witte achtergrond.

width: 10.5em;

Breedte. Als eenheid wordt de relatieve eenheid em gebruikt, zodat de breedte mee verandert met de lettergrootte. Bij gebruik van een absolute eenheid zoals px verandert de breedte niet mee met de lettergrootte. Zoomen kan wel altijd, ongeacht welke eenheid voor de breedte wordt gebruikt.

height: 4.8em;

Hoogte. Als eenheid wordt de relatieve eenheid em gebruikt, zodat de hoogte mee verandert met de lettergrootte. Bij gebruik van een absolute eenheid zoals px verandert de hoogte niet mee met de lettergrootte. Zoomen kan wel altijd, ongeacht welke eenheid voor de hoogte wordt gebruikt.

font-size: 1.2em;

Iets grotere letter. Als eenheid wordt de relatieve eenheid em gebruikt, omdat bij gebruik van een absolute eenheid zoals px niet alle browsers de lettergrootte kunnen veranderen. Zoomen kan wel altijd, ongeacht welke eenheid voor de lettergrootte wordt gebruikt.

line-height: 2.4em;

Regelhoogte. Als eenheid wordt de relatieve eenheid em gebruikt, omdat bij gebruik van een absolute eenheid zoals px de regelhoogte niet mee verandert met de lettergrootte. Zoomen kan wel altijd, ongeacht welke eenheid voor de regelhoogte wordt gebruikt.

text-align: center;

Tekst horizontaal centreren.

border: red solid 3px;

Opvallende rode rand.

position: fixed;

Om de skip-link op een bepaalde plaats neer te kunnen zetten.

Er wordt gepositioneerd ten opzichte van het 'containing block'. Dat is bij een fixed positie normaal genomen het venster van de browser. Hierdoor scrolt de skip-link niet mee met de pagina.

Een <a> is van zichzelf een inline-element, waardoor eigenschappen als breedte en hoogte niet gebruikt kunnen worden. Door de <a> fixed te positioneren verandert deze in een soort blok-element, waardoor dit soort eigenschappen wel is te gebruiken.

top: 20px;

20 px vanaf de bovenkant neerzetten.

left: -20000px;

Ver links buiten het scherm neerzetten.

z-index: 250;

Normaal genomen worden elementen in de volgorde van de html op het scherm gezet. Wat later in de html staat, wordt over eerdere elementen gezet. Om te zorgen dat de skip-link altijd bovenaan staat, krijgt deze een hogere z-index.

Een z-index werkt alleen in bepaalde omstandigheden. Eén van die omstandigheden is een fixed positie. Die is iets hierboven aan de <a> gegeven, dus dat is geregeld.

.skippy:focus

Voor dit element is eerder css opgegeven. Deze wordt binnen dit blokje herhaald in de volgorde, waarin deze in de stylesheet staat, zodat alles hier overzichtelijk bij elkaar staat.

.skippy {background: white; width: 10.5em; height: 4.8em; font-size: 1.2em; line-height: 2.4em; text-align: center; border: red solid 3px; position: fixed; top: 20px; left: -20000px; z-index: 250;}

De elementen met class="skippy". Dat is er maar één: een skip-link, waarmee in één keer het hele menu gepasseerd kan worden. Maar alleen als dit element de focus heeft.

left: 50%;

Eerder is a.skippy links buiten het scherm geparkeerd. Nu de skip-link de focus heeft, wordt deze halverwege het venster van de browser neergezet.

translateX(-50%);

Hier gelijk boven is de linkerkant van a.skippy precies in het midden van het venster van de browser gezet. Als je nou a.skippy de helft van de eigen breedte terug naar links zet, staat a.skippy horizontaal precies gecentreerd binnen het venster.

Met de bij transform horende functie translateX() kun je 'n element ten opzichte van zichzelf verplaatsen. En als je die verplaatsing in procenten opgeeft, gelden die procenten ten opzichte van het element zelf. Omdat de browser bij het maken van de pagina alle maten weet, kan de browser altijd uitrekenen, hoeveel 50% is.

translateX() verplaatst in horizontale richting. Omdat de waarde negatief is, wordt naar links verplaatst. Met 50%, precies de helft van de breedte van skip-link a.skippy. Oftewel: de helft van a.skippy staat nu links van het midden van het browservenster, en de andere helft staat rechts daarvan: horizontaal gecentreerd. Hierdoor is de skip-link ook in heel smalle vensters volledig zichtbaar.

ul

Alle <ul>'s. Dat zijn er hier zeven: één <ul> waarbinnen zes <button>'s zitten, en bij elk van die zes <button>'s een <ul> met een submenu.

list-style-type: none;

De standaard bolletjes en dergelijke bij een <ul> weghalen, die zijn hier niet nodig.

Als je list-style-type: none; gebruikt, herkent schermlezer VoiceOver op iOS, iPadOS en OS X de <ul> niet meer als een lijst, waardoor deze niet meer wordt aangekondigd als een lijst. Maar de <ul> wordt weer wel herkend als een lijst als de <ul> binnen een <nav> staat. Omdat het hier om een menu gaat, is dat hier het geval.

Voor de diepere logica hierachter moet je contact opnemen met de faculteit Applegetica van de Rijksuniversiteit Urk. Alleen daar bezitten ze de diepzinnige rechtlijnigheid om dit uit te kunnen leggen.

margin: 0; padding: 0;

De standaard marge en padding bij een <ul> verwijderen.

.menubalk

De elementen met class="menubalk". Dat is er maar één: de <ul> waarbinnen vrijwel het hele menu zit.

display: none;

Verbergen. In smallere browservensters is er niet genoeg ruimte om een menubalk met zes <button>'s te tonen. Daarom wordt deze pas getoond, als hiervoor is gekozen door het aanraken of -klikken van een voor de menubalk zittende <button>.

flex-direction: column;

Als de menubalk zichtbaar wordt gemaakt, gebeurt dat met display: flex;. De waarde flex is onderdeel van een hele reeks eigenschappen en waarden, die bij elkaar 'flexbox' worden genoemd. Met display: flex; wordt ul.menubalk in een zogenaamde 'flex container' veranderd. Dit maakt het veel makkelijker om de directe kinderen van dit element, de 'flex items', op een bepaalde plaats neer te zetten. De directe kinderen van de menubalk zijn hier zes <li>'s. In elk van die <li>'s zit weer een <button> met bijbehorend submenu.

Met flex-direction wordt de richting van de hoofdas (in het Engels: 'main axis') van de flex container aangegeven. Standaard heeft flex-direction de waarde 'row': horizontaal, waardoor de flex items naast elkaar komen te staan: op één regel. Maar hier moeten de flex items niet naast, maar onder elkaar komen te staan, in een kolom. In smalle browservensters is in de breedte niet genoeg ruimte om zes knoppen naast elkaar neer te zetten.

max-height: 80vh; Derde submenu geopend in een smal venster.

In hele lage browservensters, zoals op mobieltjes in landschapsstand, is het menu te hoog, als 'n submenu is geopend. Hierdoor zou een deel onder het venster komen te staan. Omdat de menubalk iets hieronder fixed wordt gepositioneerd, kun je niet scrollen. Hierdoor zou een deel van het menu onbereikbaar worden.

Daarom wordt hier een maximumhoogte aan de menubalk gegeven. Op de afbeelding is het submenu bij de derde <button> geopend, waardoor de vijfde en zesde knop niet meer zichtbaar zijn, maar die kunnen nu binnen het venster worden gescrold (daarvoor is ook nog de eigenschap overflow gelijk hieronder nodig).

De eenheid vh is gebaseerd op de hoogte van het venster van de browser. 1 vh is 1% van de hoogte van het venster, en 80 vh is 80% van de hoogte. De menubalk met de zes <button>'s wordt hierdoor nooit hoger dan 80% van de hoogte van het venster, ongeacht de hoogte van het venster. Ook niet als een van de submenu's is geopend.

overflow: auto;

Standaard heeft overflow de waarde visible: laat alles zien, ook als het niet in het element past. Mogelijk wordt de lay-out dan verstoord, maar er verdwijnt in ieder geval niets. Hier heb je daar niets aan, omdat iets hieronder de menubalk fixed wordt gepositioneerd. Het is heel mooi dat alles wordt getoond, maar als dat onder het venster van de browser wordt getoond, zie je het nog steeds niet.

De waarde auto zorgt ervoor dat je de inhoud van de menubalk kunt scrollen, als deze hoger is dan de gelijk hierboven opgegeven maximumhoogte van 80 vh.

overscroll-behavior-y: contain;

Als de menubalk wordt gescrold en deze is boven‑ of onderaan gekomen, wordt standaard de rest van de pagina gescrold. Vervelend, want dan moet je mogelijk opzoeken, waar de pagina stond.

Dit zorgt ervoor dat de pagina niet wordt gescrold, ook niet als het begin of eind van de menubalk is bereikt. Hier is dat alleen in verticale richting nodig, daarom wordt overscroll-behavior-y gebruikt: de 'y' staat voor de verticale richting.

position: fixed;

Om het element op een bepaalde plaats neer te kunnen zetten.

Er wordt gepositioneerd ten opzichte van het 'containing block'. Dat is bij een fixed positie normaal genomen het venster van de browser. Hierdoor scrolt de menubalk – en daarmee ook alles erin – niet mee met de pagina.

top: 3.2rem;

Op 3,2 rem vanaf de bovenkant van het browservenster neerzetten. Hiermee staat de menubalk precies onder button.voor-smal, de knop waarmee in smallere vensters de menubalk met de zes <button>'s kan worden verborgen of getoond.

Als eenheid wordt de relatieve eenheid rem gebruikt, omdat bij gebruik van een absolute eenheid zoals px de afstand tot de bovenkant van het browservenster niet mee verandert met de lettergrootte. Zoomen kan wel altijd, ongeacht welke eenheid wordt gebruikt.

De minder bekende rem is ongeveer hetzelfde als de em. Alleen is de lettergrootte bij rem gebaseerd op de lettergrootte van het <html>-element, waardoor de rem overal op de pagina precies even groot is, ook als de bezoeker de lettergrootte heeft veranderd. Bij de em kan de lettergrootte worden beïnvloed door de voorouders van het element, bij de rem niet.

Randen van menu en knop sluiten niet goed aan.

Door afrondingsverschillen blijkt het trouwens onmogelijk om in alle browsers de rand aan de bovenkant van de eerste knop gelijk te laten vallen met de rand aan de onderkant van button.voor-smal. Als je 't in de ene goed hebt, staat 't kreng in de andere weer net te hoog of te laag. De afbeelding is gemaakt in Firefox. De menubalk staat iets te hoog, waardoor de horizontale zwarte rand 'n lelijke hik krijgt.

Normaal genomen worden elementen die lager in de html staan boven eerdere elementen weergegeven. ul.menubalk komt daardoor boven button.voor-smal te staan. Maar omdat button.voor-smal bij .voor-smal fixed is gepositioneerd en een z-index van 10 heeft gekregen, komt <button> toch over de menubalk heen te staan. Daardoor loopt de zwarte rand aan de onderkant van de <button> zonder hik door, want de zwarte rand aan de bovenkant van de menubalk verdwijnt nu onder de <button>.

Mogelijk is de bovenste van de zes <button>'s met submenu's nu 'n heel klein beetje lager dan de andere vijf, maar dat valt niet op. Terwijl een verschil van 1 px in die zwarte lijn wél enorm opvalt.

.menubalk button

Alle <button>'s binnen de elementen met class="menubalk". De zes <button>'s met submenu's binnen de menubalk.

appearance: none;

Hoewel de <button>'s moeten werken als een <button>, moeten deze er niet zo uitzien. In smallere browservensters zien ze eruit als zes serie grijze blokken boven elkaar, in bredere vensters als een menubalk met zes knoppen. Daarom wordt het standaard uiterlijk van een <button> (dat in elke browser verschilt) verwijderd.

background: #e4e4e4;

Zilverkleurige achtergrondkleur.

color: black;

Voorgrondkleur zwart. Dit is onder andere de kleur van de tekst.

Hoewel dit de standaardkleur is, wordt deze toch specifiek opgegeven. Hierboven is een achtergrondkleur opgegeven. Sommige mensen hebben zelf de voorgrond‑ en/of achtergrondkleur veranderd, bijvoorbeeld omdat ze slecht kleuren kunnen onderscheiden. Als nu de achtergrondkleur wordt veranderd, maar niet de voorgrondkleur, loop je het risico dat tekstkleur en achtergrondkleur te veel op elkaar gaan lijken.

Door beide op te geven, is redelijk zeker dat achtergrond‑ en tekstkleur genoeg van elkaar blijven verschillen. Als de gebruiker !important heeft gebruikt in een eigen stylesheet, is er nog niets aan de hand, want dan veranderen achtergrond‑ en voorgrondkleur geen van beide.

Dit is ook al bij <body> opgegeven, maar sommige mensen hebben bij álle elementen de kleuren veranderd. Het heeft immers weinig zin, als ze dat alleen bij de body doen, terwijl de sitebouwer de kleuren ook bij bijvoorbeeld de paragrafen heeft aangepast.

width: 10em;

Breedte. Als eenheid wordt de relatieve eenheid em gebruikt, zodat de breedte mee verandert met de lettergrootte. Bij gebruik van een absolute eenheid zoals px verandert de breedte niet mee met de lettergrootte. Zoomen kan wel altijd, ongeacht welke eenheid voor de breedte wordt gebruikt.

height: 2.6em;

Hoogte. Als eenheid wordt de relatieve eenheid em gebruikt, zodat de hoogte mee verandert met de lettergrootte. Bij gebruik van een absolute eenheid zoals px verandert de hoogte niet mee met de lettergrootte. Zoomen kan wel altijd, ongeacht welke eenheid voor de hoogte wordt gebruikt.

font-size: 1.1em;

Iets grotere letter. Als eenheid wordt de relatieve eenheid em gebruikt, omdat bij gebruik van een absolute eenheid zoals px niet alle browsers de lettergrootte kunnen veranderen. Zoomen kan wel altijd, ongeacht welke eenheid voor de lettergrootte wordt gebruikt.

text-align: center;

Tekst horizontaal centreren.

border: black solid 1px;

Zwart randje.

padding: 3px 0;

Omdat voor onder en links geen waarden zijn opgegeven, krijgen die automatisch dezelfde waarde als boven en rechts. Hier staat dus eigenlijk 3px 0 3px 0 in de volgorde boven – rechts – onder – links.

Om niet geheel duidelijke redenen is dit nodig voor iPads. Zonder padding komt het teken '«' niet naast 'Menu 1' tot en met 'Menu 6' te staan, maar eronder. Niet heel ernstig, maar dit is netter.

.menubalk li:last-of-type ul

Voor dit element is eerder css opgegeven. Deze wordt binnen dit blokje herhaald in de volgorde, waarin deze in de stylesheet staat, zodat alles hier overzichtelijk bij elkaar staat.

ul {list-style-type: none; margin: 0; padding: 0;}

Kinderen van dezelfde ouder kunnen worden geteld, net zoals dat bij kinderen uit een gezin het geval is: eerste kind, tweede kind, derde kind, enzovoort. Je kunt ook alleen 'n bepaald soort element tellen, net zoals je alleen kinderen van jonger of ouder dan zes jaar kunt tellen. Met de pseudo-class :nth-of-type() worden alleen elementen van een bepaald soort geteld. De pseudo-class :last-of-type(), onderdeel van deze selector, werkt hetzelfde, maar telt van achter naar voren. Omdat het vaak voorkomt dat je het laatste element van 'n bepaald type wilt hebben, heeft dat een aparte pseudo-class, het in deze selector gebruikte :last-of-type.

.menubalk: de elementen met class="menubalk. Dat is er maar één: de <ul> waarbinnen vrijwel het hele menu zit.

li: alle <li>'s binnen ul.menubalk.

:last-of-type: het element met een bepaald volgnummer, geteld van achter naar voren.

In dit geval wordt geen volgnummer gebruikt, maar een speciaal voor het laatste element bedoelde pseudo-class: :last-of-type. Voor alle eerdere elementen gebruik je 'n soortgelijke pseudo-class, maar dan met een volgnummer: :nth-last-of-type(). Tussen de haakjes komt het volgnummer. :nth-last-of-type(1) is precies hetzelfde als :last-of-type, maar de laatste is wat mensvriendelijker.

Omdat voor :last-of-type (of voor eerdere elementen :nth-last-of-type()) een li staat, worden alleen <li>'s geteld. Als binnen ul.menubalk 327 <p>'s zitten, tellen die niet mee. Hadden ze maar 'n <li> moeten zijn.

ul: de <ul>'s binnen deze laatste <li>.

De hele selector in gewone taal: de <ul>'s binnen elke laatste <li> binnen ul.menubalk.

Binnen ul.menubalk zit een hele serie <ul>'s. Elke laatste <li> binnen zo'n <ul> valt binnen deze selector. Maar er is maar één laatste <li> die zelf weer een <ul> als nakomeling heeft: de <li> met de zesde (en laatste) <button> met submenu. Uiteindelijk bestrijkt deze selector dus alleen de <ul> met het zesde (en laatste) submenu.

De pseudo-class :last-of-type (of :nth-last-of-type()) kan onverwachte bijwerkingen hebben. In dit geval is er maar één <ul> die onder deze selector valt. Maar er zouden makkelijk nog andere <ul>'s onder deze selector kunnen vallen, bijvoorbeeld als er <ul>'s met een sub-submenu zouden zijn. (Dit zou je in dit geval kunnen oplossen door aan het toevoegen van een >: .menubalk > li:last-of-type ul. Dan geldt de selector alleen de <li>'s die een direct kind van ul.menubalk zijn.)

border-bottom: black solid 1px;

In smallere browservensters staan de zes <button>'s onder elkaar. Ze worden van elkaar gescheiden door een zwart lijntje. Iets verderop bij .menubalk ul li krijgen de <li>'s van de submenu's een zwart randje en worden ze met margin-top: 1px; 1 px naar boven verplaatst, om te voorkomen dat de zwarte randjes tussen twee <li>'s twee keer zo dik worden.

Hierdoor valt echter ook het zwarte randje onderaan de zesde <button> weg, waardoor aan de onderkant van de zes <button>'s de zwarte rand mist. Zoals vaker met css zijn er 37 manieren om dit op te lossen. Hier is gekozen voor de 38e manier: een zwart randje aan de onderkant van de <ul> met het laatste submenu.

.menubalk button::after

Alle <button>'s binnen het element met class="menubalk". Dit zijn de zes <button>'s met de submenu's. Met behulp van ::after wordt bij elk van die <button>'s een pseudo-element gemaakt. Dit pseudo-element wordt gebruikt om het teken '«' weer te geven.

content: "«";

Met behulp van content wordt het teken '«' weergegeven. Dit teken wordt door sommige schermlezers voorgelezen met iets als 'dubbele pijl omlaag'. Hier gelijk onder bij @supports (content: "«" / "") wordt geprobeerd dit te voorkomen.

display: inline-block;

Een met behulp van ::after gemaakt pseudo-element is van zichzelf een inline-element. Hierdoor zijn eigenschappen als breedte en hoogte niet te gebruiken. Een inline-block is een soort kruising tussen een inline- en een blok-element. Het komt niet op een nieuwe regel te staan, maar eigenschappen als hoogte en breedte zijn wel te gebruiken.

width: 20px; font-size: 1.2em; text-align: center; margin-left: 20px;

Breedte.

Het teken '«' wordt naar beneden of naar boven gedraaid, afhankelijk van of een submenu zichtbaar is of niet. Het is tamelijk lastig om dit teken in beide richtingen in alle browsers netjes achter de rest van de tekst te krijgen. Deze combinatie blijkt overal goed te werken. (Ook translate(-2px, -4px) gelijk hieronder is hier nog voor nodig.)

Omdat de meeste maten hier in de absolute eenheid px zijn, veranderen die niet mee met een andere lettergrootte. Hierdoor staat het teken '«' bij een andere lettergrootte niet meer netjes op gelijke hoogte als de rest van de tekst. Maar het is nog steeds prima zichtbaar, en als overal een relatieve eenheid als em wordt gebruikt, krijg je het teken bij een standaardlettergrootte niet in alle browsers op de juiste plaats.

transform: rotate(270deg) translate(-2px, -4px); Open-teken.

Met behulp van transform kan een element worden gemanipuleerd. Met behulp van de bij transform horende functie rotate() kun je een element draaien. rotate(270deg) draait het element, in dit geval de '«', 270° met de klok mee. Hierdoor worden de punten naar beneden gericht, zoals op de afbeelding is te zien.

Hier gelijk boven is het teken '«' met een combinatie van allerlei eigenschappen op de juiste plaats neergezet. Ook als het teken wordt omgekeerd, moet het nog op de juiste plaats staan. Naast de iets hierboven al gebruikte eigenschappen blijkt ook nog een kleine aanpassing met behulp van de bij transform horende functie translate() nodig. Deze functie verplaatst een element ten opzichte van zichzelf.

Er staan twee waarden. De eerste waarde -2px voor de komma verplaatst 2px in horizontale richting. Naar links, omdat de waarde negatief is. De tweede waarde ‑4px na de komma verplaatst in verticale richting. Naar boven, omdat de waarde negatief is.

(Horizontaal, verticaal, links en boven moeten hier niet letterlijk worden genomen. Omdat het pseudo-element 270° is gedraaid, veranderen horizontaal, verticaal, links en boven ook van richting.)

@supports (content: "«" / "")

De css die binnen deze 'feature query' staat, geldt alleen voor browsers die content met een alternatieve tekst voor schermlezers ondersteunen. (Een 'feature query' is een vraag of een bepaalde eigenschap en waarde, of een bepaalde selector, worden ondersteund: iets als 'eigenschap vraag'.)

@supports: hierachter komt tussen haakjes de te onderzoeken eigenschap inclusief een bepaalde waarde te staan.

content: "«" / "": de eigenschap waarvan je de ondersteuning wilt testen, inclusief de waarde.

In dit geval is de eigenschap content. De waarde bij die eigenschap is "«" / "". (Een uitgebreider verhaal over content staat iets hieronder bij .menubalk button::after.)

Gelijk na deze regel komt een { te staan, en aan het einde van de css die binnen deze query valt een bijbehorende afsluitende }. Die zijn in de regel hierboven weggevallen, maar het geheel ziet er zo uit:

@supports (content: "«" / "") { body {color: silver;} (...) rest van de css voor deze feature query (...) footer {color: gold;} }

Voor de eerste css binnen deze feature query staat dus een extra {, en aan het eind staat een extra }.

Je kunt ook als voorwaarde opgeven dat meerdere eigenschappen (en/of waarden) moeten worden ondersteund, of dat een eigenschap juist niet mag worden ondersteund, maar dat gebeurt hier allemaal niet. Hier is de enige voorwaarde dat de / in content wordt ondersteund.

Alleen browsers die / in content ondersteunen, voeren de css binnen deze feature query uit. Oudere browsers kennen @supports niet en voeren de css binnen deze feature query daarom niet uit.

.menubalk button::after

Deze selector werkt alleen in browsers die content: "«" / ""; ondersteunen. Voor andere browsers is de uitleg hieronder niet van belang.

Voor deze elementen is eerder css opgegeven. Deze wordt binnen dit blokje herhaald in de volgorde, waarin deze in de stylesheet staat, zodat alles hier overzichtelijk bij elkaar staat.

.menubalk button::after {content: "«"; display: inline-block; width: 20px; font-size: 1.2em; text-align: center; margin-left: 20px; transform: rotate(270deg) translate(-2px, -4px);}

Alle <button>'s binnen het element met class="menubalk". Dit zijn de zes <button>'s met de submenu's. Met behulp van ::after wordt bij elk van die <button>'s een pseudo-element gemaakt. Dit pseudo-element wordt gebruikt om op het scherm het teken '«' weer te geven. In schermlezers wordt de '«' juist genegeerd.

content: "«" / "";

De « voor de / is wat browsers die dit ondersteunen op het scherm weergeven. De « staat tussen aanhalingstekens, want dan weet de browser dat het om een stukje letterlijke tekst gaat. De tekst na de / wordt niet getoond.

De "" na de / is bedoeld voor schermlezers. In browsers die dit ondersteunen, wordt in schermlezers de tekst na de / voorgelezen, en de tekst voor de / wordt genegeerd. Ook dit moet weer tussen aanhalingstekens staan. Omdat hier helemaal niets tússen de aanhalingstekens staat, leest een schermlezer niets voor. Precies de bedoeling, want gebruikers van een schermlezers hebben niets aan het voorlezen van iets als 'dubbele pijl omlaag gericht'.

content met een alternatieve tekst voor schermlezers wordt niet ondersteund door Firefox en Safari (en daarmee ook alle browsers op iOS en iPadOS). Omdat die dit niet ondersteunen, wordt de hele eigenschap content genegeerd. Hierdoor zou het teken '«' in deze browsers worden weggelaten.

Om dat te voorkomen is hier iets boven bij .menubalk button::after een 'gewone' regel met content gebruikt: content: "«". Firefox en Safari gebruiken die regel en negeren de regel met content hier, omdat eerst met behulp van @supports is getest, of een browser dit wel ondersteunt. Meer over Firefox en Safari is te vinden bij Probleem: NVDA in Firefox...

.menubalk ul

Voor deze elementen is eerder css opgegeven. Deze wordt binnen dit blokje herhaald in de volgorde, waarin deze in de stylesheet staat, zodat alles hier overzichtelijk bij elkaar staat.

ul {list-style-type: none; margin: 0; padding: 0;}

.menubalk li:last-of-type ul {border-bottom: black solid 1px;}

Alle <ul>'s binnen de elementen met class="menubalk". Er is maar één element met class="menubalk": de <ul> waarbinnen vrijwel het hele menu zit, waaronder de zes <ul>'s, waarbinnen weer de submenu's zitten. Binnen elk van deze <ul>'s zit een serie <li>'s met links.

height: 0;

Geen hoogte. De reden hiervan staat gelijk hieronder bij visibility: hidden;.

visibility: hidden;

Normaal genomen moeten de submenu's niet zichtbaar zijn. Bovendien moeten ze normaal genomen ook door schermlezers niet worden voorgelezen. Want ook al zie je ze niet, dat betekent niet dat ze niet worden voorgelezen (met alle bijbehorende <button>'s en links).

Vaak wordt display: none; gebruikt om iets – ook voor schermlezers – te verbergen. Als het dan getoond moet worden, verander je dat in display: block; of zoiets en hocus pocus pilatus pas: daar verschijnt uit het niets een submenu.

Hier is dit niet bruikbaar. Als JavaScript wordt gebruikt, wordt het tonen en verbergen van submenu's voornamelijk geregeld met behulp van de WAI-ARIA-code aria-expanded. Maar zonder JavaScript wordt dit met behulp van :focus, :focus-within en :hover geregeld. En dat levert een probleem op.

Als iemand de Tab-toets gebruikt om door het menu te lopen, wordt een submenu geopend als de <button> erboven de focus heeft. (Als JavaScript aanstaat, moet het submenu eerst worden geopend.) Bij de volgende Tab wordt dan het submenu binnengegaan en krijgt de eerste link binnen het bij de <button> horende submenu de focus. Met behulp van :focus-within blijft dit submenu geopend, tot alle links zijn doorlopen. Althans: dat is de bedoeling.

Helaas verliest de <button> de focus net voordat de eerste link binnen het submenu die focus krijgt. Waardoor de <ul> met het submenu acuut verdwijnt. Je kunt, zonder JavaScript, daardoor met de Tab-toets nooit een submenu doorlopen.

Daarom wordt iets hieronder met transition-delay: 0.1s; een minieme vertraging van 0,1 seconde aangebracht, voordat de <ul> met het submenu verdwijnt. Die nauwelijks merkbare vertraging is voldoende om de link in het submenu de focus te laten krijgen, voordat de <button> die verliest. Maar daardoor is display: none; niet bruikbaar, want dat kan niet worden vertraagd.

Submenu´s zijn niet goed gesloten in smal venster.

visibility kan wel worden vertraagd, en visibility: hidden; verbergt de <ul>'s met de submenu's ook voor schermlezers. Waarmee dit is opgelost en er traditiegetrouw een nieuw probleem ontstaat: visibility verbergt weliswaar iets, maar de ruimte wordt – anders dan bij display: none; – nog steeds gewoon ingenomen. Dat ziet eruit zoals op de afbeelding. Het valt niet te ontkennen dat de <ul>'s met de submenu's inderdaad onzichtbaar zijn, maar daar is dan ook alles mee gezegd. Ze nemen precies evenveel ruimte in, als wanneer ze zichtbaar zouden zijn.

Dit wordt opgelost door de regel hier gelijk boven: height: 0;. Nu nemen de <ul>'s nog slechts een hoogte van 0 px in, oftewel: geen hoogte. Waarmee de <ul>'s niet alleen onzichtbaar zijn, maar ook geen ruimte meer innemen.

transform: translateY(-2000px);

Ondanks dat de <ul>'s met de submenu's onzichtbaar zijn en geen hoogte hebben, kunnen ze in sommige schermlezers toch nog problemen opleveren, bijvoorbeeld een link elders op de pagina die niet gevolgd kan worden. Daarom worden ze met behulp van de bij transform horende functie translateY(-2000px) verplaatst. translateY() verplaatst in verticale richting. Omdat -2000px negatief is, wordt 2000 px naar boven verplaatst, ver boven het scherm.

transition-delay: 0.1s;

Hierboven zijn de <ul>'s met de submenu's met behulp van de drie eigenschappen height, visibility en translateY() verborgen. Waardoor de links erin uiteraard onbruikbaar zijn.

Als je nu bij :hover, :focus, :target, en dergelijke een andere waarde bij deze drie eigenschappen opgeeft om ze zichtbaar te maken, zodat de links gebruikt kunnen worden, zorgt transition-delay ervoor dat de drie eigenschappen niet onmiddellijk naar die nieuwe waarde worden veranderd bij :hover en dergelijke, maar pas na een vertraging. Als visibility wordt veranderd van hidden naar visible, vindt die verandering pas na een vertraging plaats. De hier opgegeven vertraging is 0,1 seconde. Waarom die minieme vertraging nodig is, staat iets hierboven bij visibility: hidden;.

Meestal wordt transition-delay alleen bij de verandering bij :hover en dergelijke opgegeven. De vertraging van beginstand naar verandering, en weer terug van verandering naar beginstand, is dan in beide richtingen hetzelfde. Maar dat hoeft niet. Door verschillende tijden op te geven, kun je een submenu bijvoorbeeld met een bepaalde vertraging verbergen, maar met een andere vertraging tonen. Dat is wat hier gebeurt.

Bij :hover wordt later een andere waarde opgegeven voor transition-delay. Als een submenu bijvoorbeeld met :hover zichtbaar wordt gemaakt en daar wordt transition-delay: 0.3s, gebruikt, duurt het 0,3 seconde voor het submenu zichtbaar is. Maar bij het weer verbergen wordt de waarde van 0,1 seconde, die hier is opgegeven, gebruikt. (Dat aanpassen bij :hover is nodig om te voorkomen dat de submenu's bij hoveren over de menubalk voortdurend openen sluiten.)

transition-delay is onderdeel van een serie bij elkaar horende eigenschappen, die allemaal met het vertragen, geleidelijk aan uitvoeren, en dergelijke van een verandering te maken hebben. Meestal worden die samengevat als transition: een zogenaamde 'shorthand', een manier om álle bij elkaar horende eigenschappen in één regel op te schrijven.

transition, en daarmee ook transition-delay, brengt een geheel eigen risico met zich mee. In de specificatie staat, welke eigenschappen met behulp van transition kunnen veranderen. Je kunt eventueel bij transition opgeven, voor welke eigenschappen het geldt. Als je bijvoorbeeld top en left beide wilt veranderen bij :checked, kun je bijvoorbeeld aangeven dat de geleidelijke verandering met behulp van transition alleen voor top geldt. left verandert dan gewoon in één keer.

Als je alleen transition-delay gebruikt, kun je niet opgeven, voor welke eigenschappen het geldt. Elke eigenschap die bij :hover, :focus, :target, en dergelijke wordt veranderd, valt onder transition-delay. In dit geval is dat geen probleem, omdat alle drie de eigenschappen bij deze selector worden veranderd bij :hover en dergelijke.

Als er meerdere eigenschappen veranderen, zou daar ook een eigenschap bij kunnen zitten die op dit moment niet door transition kan worden vertraagd. Maar als de specificatie of een van de browsermakers de geest krijgt en plotsklaps die eigenschap ook onder transition laat vallen, kan dat tot heel onverwachte resultaten leiden.

Stel dat je een bepaalde eigenschap heel traag wilt veranderen bij :hover en een andere eigenschap flitsend snel. Mogelijk wordt dat flitsend snel dan opeens ook heel traag, als transition in de toekomst die eigenschap ook gaat ondersteunen.

Als er ook maar de kleinste kans is op dit soort problemen, of als niet alle eigenschappen die veranderen onder transition moeten vallen, moet je aangeven, welke eigenschap(pen) transition mag aansturen.

Je kunt dat doen door transition te gebruiken en daarbij aan te geven, voor welke eigenschappen het geldt. Of je kunt eventueel alleen transition-property gebruiken en daarmee de eigenschappen aangeven, waarvoor transition-delay geldt.

.menubalk ul li

Alle <li>'s binnen een <ul> die weer binnen een element met class="menubalk" zitten. Er is maar één element met class="menubalk": de <ul> waarbinnen vrijwel het hele menu zit, waaronder zes <ul>'s met in elke <ul> een serie <li>'s met links: de submenu's.

background: #e4e4e4;

Zilverkleurige achtergrondkleur.

color: black;

Voorgrondkleur zwart. Dit is onder andere de kleur van de tekst.

Hoewel dit de standaardkleur is, wordt deze toch specifiek opgegeven. Hierboven is een achtergrondkleur opgegeven. Sommige mensen hebben zelf de voorgrond‑ en/of achtergrondkleur veranderd, bijvoorbeeld omdat ze slecht kleuren kunnen onderscheiden. Als nu de achtergrondkleur wordt veranderd, maar niet de voorgrondkleur, loop je het risico dat tekstkleur en achtergrondkleur te veel op elkaar gaan lijken.

Door beide op te geven, is redelijk zeker dat achtergrond‑ en tekstkleur genoeg van elkaar blijven verschillen. Als de gebruiker !important heeft gebruikt in een eigen stylesheet, is er nog niets aan de hand, want dan veranderen achtergrond‑ en voorgrondkleur geen van beide.

Dit is ook al bij <body> opgegeven, maar sommige mensen hebben bij álle elementen de kleuren veranderd. Het heeft immers weinig zin, als ze dat alleen bij de body doen, terwijl de sitebouwer de kleuren ook bij bijvoorbeeld de paragrafen heeft aangepast.

width: 9em; Links in submenu zijn smaller dan de knop erboven.

Breedte. De <button>'s boven de submenu's zijn 10 em breed. Door de <li>'s met de links iets smaller te maken, is duidelijker dat het submenu bij de erboven zittende <button> hoort, zoals op de afbeelding bij het vierde submenu is te zien..

Als eenheid wordt de relatieve eenheid em gebruikt, zodat de breedte mee verandert met de lettergrootte. Bij gebruik van een absolute eenheid zoals px verandert de breedte niet mee met de lettergrootte. Zoomen kan wel altijd, ongeacht welke eenheid voor de breedte wordt gebruikt.

margin-top: -1px;

Hier gelijk onder krijgen alle <li>'s een zwart randje. Als twee <li>'s aan elkaar grenzen, komen de rand aan boven‑ en onderkant van de twee <li>'s tegen elkaar aan te staan. Daardoor zou de horizontale rand tussen twee <li>'s een dubbele dikte krijgen. Door de <li>'s allemaal 1 px naar boven te verplaatsen, komen de twee borders over elkaar heen te staan, zodat ze samen toch maar 1 px breed zijn, net als de andere borders.

(Bij de zes <button>'s speelt eigenlijk hetzelfde probleem. Deze hebben bij .menubalk button een zwart randje gekregen en staan ook tegen elkaar aan. Omdat een negatieve marge de elementen daadwerkelijk verplaatst, worden ook de tweede tot en met de zesde <button> 1 px omhoog gezet, want deze staan onder een <li>. Hierdoor is de dubbele border bij de zes <button>'s ook gelijk opgelost.)

border: black solid 1px;

Zwart randje.

.menubalk ul li:last-of-type

Voor deze elementen is eerder css opgegeven. Deze wordt binnen dit blokje herhaald in de volgorde, waarin deze in de stylesheet staat, zodat alles hier overzichtelijk bij elkaar staat.

.menubalk ul li {background: #e4e4e4; color: black; width: 9em; margin-top: -1px; border: black solid 1px;}

Kinderen van dezelfde ouder kunnen worden geteld, net zoals dat bij kinderen uit een gezin het geval is: eerste kind, tweede kind, derde kind, enzovoort. Je kunt ook alleen 'n bepaald soort element tellen, net zoals je alleen kinderen van jonger of ouder dan zes jaar kunt tellen. Met de pseudo-class :nth-of-type() worden alleen elementen van een bepaald soort geteld. De pseudo-class :last-of-type(), onderdeel van deze selector, werkt hetzelfde, maar telt van achter naar voren. Omdat het vaak voorkomt dat je het laatste element van 'n bepaald type wilt hebben, heeft dat een aparte pseudo-class, het in deze selector gebruikte :last-of-type.

.menubalk ul li: Alle <li>'s binnen een <ul> die weer binnen een element met class="menubalk" zitten. Er is maar één element met class="menubalk": menubalk ul.menubalk. Daarbinnen zitten zes <ul>'s met in elke <ul> een serie <li>'s met links: de submenu's.

:last-of-type: het element met een bepaald volgnummer, geteld van achter naar voren.

In dit geval wordt geen volgnummer gebruikt, maar een speciaal voor het laatste element bedoelde pseudo-class: :last-of-type. Voor alle eerdere elementen gebruik je 'n soortgelijke pseudo-class, maar dan met een volgnummer: :nth-last-of-type(). Tussen de haakjes komt het volgnummer. :nth-last-of-type(1) is precies hetzelfde als :last-of-type, maar de laatste is wat mensvriendelijker.

Omdat voor :last-of-type (of voor eerdere elementen :nth-last-of-type()) een li staat, worden alleen <li>'s geteld. Als binnen ul.menubalk 327 <p>'s zitten, tellen die niet mee. Hadden ze maar 'n <li> moeten zijn.

De hele selector in gewone taal: elke laatste <li> binnen een <ul> binnen ul.menubalk. Hierin zit de laatste link van elk submenu.

De pseudo-class :last-of-type (of :nth-last-of-type()) kan onverwachte bijwerkingen hebben. In dit geval moet elke <ul> binnen ul.menubalk onder deze selector vallen. Maar er zouden makkelijk nog andere <ul>'s onder deze selector kunnen vallen, bijvoorbeeld als er <ul>'s met een sub-submenu zouden zijn. (Dit zou je in dit geval kunnen oplossen door aan de <ul>'s met de submenu's een class te geven en door het toevoegen van een >: .menubalk ul.sub-menu > li:last-of-type ul. Dan geldt de selector alleen voor de <ul>'s met een class="sub-menu" en alleen voor <li>'s die daar een direct kind van zijn.)

margin-bottom: -1px; Dubbele border tussen link en knop.

Hier gelijk boven bij .menubalk ul li is een te dikke horizontale rand tussen <li>'s en <button>'s voorkomen door alle <li>'s 1 px naar boven te verplaatsen met margin-top: -1px;.

Alleen blijft er dan nog een dubbele rand tussen de onderste <li> van elk submenu en de daaronder zittende <button>, zoals op de afbeelding is te zien. Want die onderste <li> wordt wel 1 px naar boven verplaatst, maar de daaronder zittende <button> niet.

Door de laatste <li> uit elk submenu aan de onderkant een negatieve marge van 1 px te geven, wordt de daaronder zittende <button> 1 px naar boven getrokken. Nu staan de rand van de laatste <li> en de <button> daaronder over elkaar heen en zijn ook 1 px dik, net als de andere randen.

.menubalk a

Alle <a>'s binnen de elementen met class="menubalk". Er is maar één element met class="menubalk": de <ul> waarbinnen vrijwel het hele menu zit. Dit zijn de links binnen het menu.

display: block; De grootte van de links zichtbaar gemaakt.

Een <a> is een inline-element. Daardoor wordt de <a> niet breder dan de tekst die erin zit. Bovendien kun je geen regelhoogte opgeven. Daardoor werkt de link alleen, als je precies de tekst aanraakt of -klikt. Op de afbeelding is het deel van de link dat werkt even omgeven door een stippellijn.

Door de <a>'s te veranderen in een blok-element worden ze even breed als hun ouder: de zilverkleurige <li>. Bovendien kan nu een regelhoogte aan de <a> worden gegeven. Hierdoor wordt de link overal gevolgd, ongeacht waar je de zilverkleurige <li> aanraakt.

line-height: 2.2em;

Regelhoogte.

Omdat geen gewone hoogte voor de <a>'s is opgegeven, is dit ook gelijk de hoogte van de <a>'s. Tekst wordt automatisch halverwege de regelhoogte neergezet, dus de tekst staat gelijk netjes verticaal gecentreerd binnen de <a>'s. Omdat ook bij de <li>'s geen hoogte is opgegeven, worden die precies hoog genoeg om de inhoud ervan weer te geven. Oftewel: die worden ook even hoog als de <a>'s.

Als eenheid wordt de relatieve eenheid em gebruikt, omdat bij gebruik van een absolute eenheid zoals px de regelhoogte niet mee verandert met de lettergrootte. Zoomen kan wel altijd, ongeacht welke eenheid voor de regelhoogte wordt gebruikt.

text-align: center;

Tekst horizontaal centreren.

nav:focus-within .voor-smal::after

Voor dit element is eerder css opgegeven. Deze wordt binnen dit blokje herhaald in de volgorde, waarin deze in de stylesheet staat, zodat alles hier overzichtelijk bij elkaar staat.

.voor-smal::after {background: white; color: black; content: "openen"; box-sizing: border-box; display: inline-block; width: 4.8em; line-height: 2.6em; border-top: white solid 3px; border-right: black solid 1px; border-bottom: white solid 3px;}

.voor-smal:focus-visible::after, .voor-smal:focus-visible span:first-child {border-color: blue; border-width: 3px;}

nav: alle <nav>'s. Dat is er hier maar één: de <nav> waarbinnen het hele menu zit.

:focus-within: als het element (hier de <nav>) of een van de nakomelingen daarvan de focus heeft.

.voorsmal::after: met behulp van ::after wordt bij de elementen met class="voor-smal" een pseudo-element gemaakt. Hier is maar één element met deze class: de <button> waarmee in smallere browservensters de menubalk met de zes <button>'s met submenu's kan worden getoond of verborgen. Dit pseudo-element wordt gebruikt om achter het woordje 'Menu' het woordje 'openen' of 'sluiten' te zetten.

content: "sluiten";

Eerder is het woordje 'openen' achter het woordje 'Menu' gezet. Als een element binnen de <nav> de focus heeft, kan het niet anders dan dat de zes <button>'s met de submenu's zichtbaar zijn, en mogelijk is er ook nog een submenu zichtbaar.

In dat geval moet het woordje 'openen' vervangen worden door het woordje 'sluiten', want het menu kan nu niet meer worden geopend, maar wel worden gesloten.

nav:focus-within .voor-smal span:last-child

Voor dit element is eerder css opgegeven. Deze wordt binnen dit blokje herhaald in de volgorde, waarin deze in de stylesheet staat, zodat alles hier overzichtelijk bij elkaar staat.

.voor-smal span:last-child {display: inline-block; width: 34px; height: 34px; font-size: 1.2em; line-height: 30px; padding-left: 5px; pointer-events: none; position: absolute; left: 5.3em; transform: rotate(270deg);}

Kinderen van dezelfde ouder kunnen worden geteld, net zoals dat bij kinderen uit een gezin het geval is: eerste kind, tweede kind, derde kind, enzovoort. Je kunt elementen selecteren op basis van het volgnummer binnen zo'n reeks kinderen. In dit geval wordt geen echt volgnummer gebruikt, maar :last-child: alleen het laatste kind. Omdat dit zo vaak voorkomt, is hier een apart sleutelwoord voor: last-child.

nav: alle <nav>'s. Dat is er hier maar één: de <nav> waarbinnen het hele menu zit.

:focus-within: als het element (hier de <nav>) of een van de nakomelingen daarvan de focus heeft.

.voor-smal: alle elementen met class="voor-smal". Dat is er maar één: de <button> voor smallere browservensters, waarmee de menubalk met de zes <button>'s met submenu's verborgen of getoond kan worden.

span: alle <span>'s binnen .voor-smal.

last-child: alleen de <span>'s die een laatste kind zijn. Binnen deze <span> zit het teken '«'. Een langere beschrijving van :last-child staat hierboven bij .voor-smal span:first-child. Het enige verschil: daar gaat het om het eerste kind, hier om het laatste kind.

De hele selector in gewone taal: alle <span>'s binnen button.voor-smal die weer binnen een <nav> zit. Maar alleen als <nav> of een nakomeling daarvan de focus heeft, en alleen de <span> die een laatste kind is.

Als een element binnen de <nav> de focus heeft, kan het niet anders dan dat de zes <button>'s met de submenu's zichtbaar zijn, en mogelijk is er ook nog een submenu zichtbaar. In dat geval moet de '«' niet meer naar boven wijzen, maar naar beneden.

(Omdat er twee <span>'s als ouder button.voor-smal hebben, zou je hier ook span:nth-child(2) kunnen gebruiken. Maar :last-child blijft altijd het laatste kind, ook als er eventueel <span>'s worden toegevoegd of weggehaald.)

line-height: 24px;

Als de zes <button>'s met de submenu's zijn verborgen, staat het teken '«' met de punten naar beneden. Als de zes <button>'s zichtbaar zijn, worden in de regel hieronder de punten naar boven gericht. Het is tamelijk lastig om het teken '«' in alle browsers op alle systemen, als het van richting verandert, op dezelfde plaats te houden. Bovendien moet het ook goed achter 'Menu sluiten' blijven staan.

Door de regelhoogte iets aan te passen, in combinatie met de verplaatsing in de regel gelijk hieronder, lukt dat redelijk goed. De hier en hieronder gebruikte waarden zijn gevonden door uitproberen en helaas niet het gevolg van geniale wiskundige inzichten.

transform: rotate(90deg) translate(18px, 4px);

Met behulp van transform kan een element worden gemanipuleerd.

Met behulp van de bij transform horende functie rotate() kun je een element draaien. rotate(90deg) draait het element, in dit geval de '«', 90° met de klok mee. Hierdoor worden de punten naar boven gericht.

Om het teken op de juiste plaats te houden, blijkt ook nog een kleine aanpassing met behulp van de bij transform horende functie translate() nodig. Deze functie verplaatst een element ten opzichte van zichzelf.

Er staan twee waarden. De eerste waarde 18px voor de komma verplaatst 18 px in horizontale richting. Naar rechts, omdat de waarde positief is. De tweede waarde 4px na de komma verplaatst in verticale richting. Naar beneden, omdat de waarde positief is.

(Horizontaal, verticaal, rechts en beneden moeten hier niet letterlijk worden genomen. Omdat het pseudo-element 90° is gedraaid, veranderen horizontaal, verticaal, rechts en beneden ook van richting.)

nav:focus-within .menubalk

Voor dit element is eerder css opgegeven. Deze wordt binnen dit blokje herhaald in de volgorde, waarin deze in de stylesheet staat, zodat alles hier overzichtelijk bij elkaar staat.

.menubalk {display: none; flex-direction: column; max-height: 80vh; overflow: auto; overscroll-behavior-y: contain; position: fixed; top: 3.2rem;}

nav: alle <nav>'s. Dat is er hier maar één: de <nav> waarbinnen het hele menu zit.

:focus-within: als het element (hier de <nav>) of een van de nakomelingen daarvan de focus heeft.

.menubalk: de elementen met class="menubalk". Er is maar één element met class="menubalk": de <ul> waarbinnen vrijwel het hele menu zit.

De hele selector in gewone taal: doe iets met ul.menubalk, maar alleen als <nav> of een nakomeling daarvan de focus heeft.

display: flex;

Omdat in smallere browservensters geen ruimte is voor een menubalk met zes <button>'s, is deze eerder bij .menubalk met display: none; verborgen.

Als een nakomeling van <nav> de focus heeft, moet de menubalk worden getoond.

Dat zichtbaar maken gebeurt hier met display: flex; waarmee ul.menubalk in een zogenaamde 'flex container' verandert. Dit maakt het veel makkelijker om de directe kinderen van dit element, de 'flex items', op een bepaalde plaats neer te zetten. De directe kinderen van de menubalk zijn hier zes <li>'s. In elk van die <li>'s zit weer een <button> met bijbehorend submenu.

@media (pointer: coarse)

De css die binnen deze 'media query' staat, geldt alleen voor apparaten, waarvan de belangrijkste aanwijzer een onnauwkeurige is, zoals een vinger.

@media: geeft aan dat het om css gaat die alleen van toepassing is, als aan bepaalde voorwaarden wordt voldaan. Al langer bestond de mogelijkheid om met behulp van zo'n @media-regel css voor bijvoorbeeld printers op te geven. css3 heeft dat uitgebreid tot veel meer eigenschappen, zoals of er wel of niet gehoverd kan worden, en of de invoermethode ruw of precies is.

(pointer: coarse): het primaire invoermechanisme moet 'ruw' (in het Engels 'coarse') zijn. Op gevaar af elke manicure en alle liefhebbende ouders nu dodelijk te beledigen: zelfs het meest fijnzinnig behandelde babyvingertje wordt in dit verband als 'ruw' gezien. (Hmm, als je je baby naar de manicure brengt, ligt hier mogelijk een nobele taak voor bepaalde instanties. Maar dat terzijde.)

De andere twee waarden zijn 'none' (geen invoermechanisme) en 'fine' (een precies invoermechanisme zoals een muis).

De eigenschap en de waarde staan tussen tussen haakjes, zodat duidelijk is dat deze bij elkaar horen.

Gelijk na deze regel komt een { te staan, en aan het einde van de css die binnen deze media query valt een bijbehorende afsluitende }. Die zijn in de regel hierboven weggevallen, maar het geheel ziet er zo uit:

@media (pointer: coarse) { body {color: silver;} (...) rest van de css voor deze media-regel (...) footer {color: gold;} }

Voor de eerste css binnen deze media query staat dus een extra {, en aan het eind staat een extra }.

In smallere browservensters wordt de menubalk met de zes <button>'s met submenu's zichtbaar gemaakt met behulp van nav:focus-within. Dat is een probleem, want in Safari kan een <button> geen focus krijgen. Omdat alle browsers op iOS en iPadOS verplicht de weergave-machine van Safari moeten gebruiken, geldt dit ook voor alle andere browsers op iOS en iPadOS.

Dit probleem doet zich op smartphones voor, maar ook op tablets en in Safari op OS X als het menu daarin sterk wordt vergroot, want dan wordt de weergave hetzelfde als die voor een smaller browservenster.

Daarom is hier aanvullende css voor nodig. Door te testen op een onnauwkeurige aanwijzer, zal deze css vrijwel alleen werken op smartphones en tablets, want die hebben vrijwel altijd als belangrijkste aanwijzer een vinger.

Je zou dit ook in alle browsers kunnen laten werken, maar dat is nogal storend. Als het menu sterk wordt vergroot, wordt het op de desktop hetzelfde weergegeven als in een smal browservenster, waarin bovenin een <button> staat, waarmee de menubalk kan worden getoond of verborgen. In de regel wordt op de desktop met een muis of touchpad gewerkt. Als de css hier ook daarbij zou werken, zou de menubalk elke keer, als de muis over die knop gaat, openfloepen. Wat nogal irritant is. Vandaar dat wordt getest op een onnauwkeurige aanwijzer: nu worden muis en touchpad genegeerd.

Dit heeft één nadeel: als je in Safari op OS X heel sterk vergroot, wordt het menu weergegeven zoals in een smaller browservenster. Omdat muis en touchpad worden genegeerd, werkt de css hier niet. Daardoor kan de menubalk niet worden getoond, en daardoor blijven ook de submenu's verborgen. Bij de weergaven voor bredere vensters speelt dit niet, want daarin is de menubalk altijd zichtbaar. En ook als JavaScript aanstaat speelt dit niet, want dan wordt het tonen en verbergen op een heel andere manier geregeld. Kortom: in Safari op OS X speelt dit alleen, als heel sterk wordt vergroot én als JavaScript uitstaat. Dat zal niet vaak het geval zijn.

De test hier is niet helemaal honderd procent waterdicht, maar zal vrijwel altijd werken. In het ergste geval floept de menubalk ten onrechte tevoorschijn, en dat is ook niet het eind van de wereld.

nav:hover .voor-smal::after {content: "sluiten";}

nav:hover .voor-smal span:last-child {line-height: 24px; transform: rotate(90deg) translate(18px, 4px);} nav:hover .menubalk {display: flex;}

Deze drie selectors werken alleen op apparaten, waarvan de belangrijkste aanwijzer een onnauwkeurige is, zoals een vinger. Voor andere apparaten is de uitleg hieronder niet van belang.

Voor deze elementen is eerder css opgegeven. Deze wordt hier niet allemaal herhaald, omdat het een enorme hoeveelheid is. Bovendien lijken deze drie selectors heel erg op drie eerdere.

Iets hierboven staan ongeveer dezelfde drie regels:

nav:focus-within .voor-smal::after {content: "sluiten"}

nav:focus-within .voor-smal span:last-child {line-height: 24px; transform: rotate(90deg) translate(18px, 4px);

nav:focus-within .menubalk {display: flex;}

Het grootste verschil: die drie regels beginnen met nav:focus-within, en de drie regels hier beginnen met nav:hover: als over <nav> wordt gehoverd. Zoals iets hierboven bij @media (pointer: coarse) beschreven, kan een <button> op iOS, iPadOS en in Safari geen focus krijgen. Hierdoor werkt nav:focus-within (als de <nav> of een nakomeling de focus heeft) niet.

Omdat :hover al uitgebreid werd gebruikt voor mobieltjes en tablets bestonden, moest daar een oplossing voor worden gevonden. Bij gebrek aan een muis kun je op smartphones en tablets niet ergens overheen hoveren. Om dat op te lossen wordt :hover op smartphones en tablets ongeveer hetzelfde afgehandeld als een aanraking. Als je dus niet nav:focus-within, maar nav:hover gebruikt, werkt dat ongeveer hetzelfde.

Dat is met deze drie regels gebeurd: nav:focus-within is vervangen door nav:hover. Voor de rest werken deze drie regels precies hetzelfde als de eerdere drie. De beschrijving ervan is bij de eerdere drie regels te vinden.

.menubalk button:focus-visible, .menubalk a:focus-visible

Voor deze elementen is eerder css opgegeven. Deze wordt binnen dit blokje herhaald in de volgorde, waarin deze in de stylesheet staat, zodat alles hier overzichtelijk bij elkaar staat.

.menubalk button {appearance: none; background: #e4e4e4; color: black; width: 10em; height: 2.6em; font-size: 1.1em; text-align: center; border: black solid 1px;}

.menubalk a {display: block; line-height: 2.2em; text-align: center;}

De eerste selector .menubalk button:focus-visible, voor de komma:

.menubalk: de elementen met class="menubalk". Er is maar één element met class="menubalk": de <ul> waarbinnen vrijwel het hele menu zit.

button: de <button>'s binnen ul.menubalk. Dit zijn de zes <button>'s met submenu's.

:focus-visible: maar alleen als de <button> de focus heeft, én als deze zichtbaar moet zijn dat dit element de focus heeft. (Alleen als de bezoeker de Tab-toets gebruikt, meer over :focus-visible is te vinden bij Voor gebruikers van de Tab-toets...)

De tweede selector .menubalk a:focus-visible, na de komma:

Deze is precies hetzelfde als de eerste selector hierboven, alleen geldt deze voor de <a>'s binnen ul.menubalk, de links in de submenu's.

background: white;

Witte achtergrond.

outline: blue solid 3px;

Blauwe outline.

Een outline werkt hier beter dan een border. Een border van 3 px breed wordt bij de breedte en hoogte opgeteld, waardoor andere elementen moeten opschuiven. Een outline wordt gewoon lekker asociaal over andere elementen heen gezet.

outline-offset: -3px;

Zoals hier gelijk boven beschreven, wordt een outline gewoon over de buren heen gezet. De outline is 3 px breed. Als je de outline met -3px 3 px naar binnen verplaatst, blijft deze toch beschaafd binnen het 'eigen' element staan.

.menubalk button:hover::after, .menubalk li:focus-within button::after

Voor deze elementen is eerder css opgegeven. Deze wordt binnen dit blokje herhaald in de volgorde, waarin deze in de stylesheet staat, zodat alles hier overzichtelijk bij elkaar staat.

.menubalk button::after {content: "«"; display: inline-block; width: 20px; font-size: 1.2em; text-align: center; margin-left: 20px; transform: rotate(270deg) translate(-2px, -4px);}

Alleen in browsers die content: "«" / ""; ondersteunen:

.menubalk button::after {content: "«" / "";}

De eerste selector .menubalk button:hover::after, voor de komma:

.menubalk: de elementen met class="menubalk". Er is maar één element met class="menubalk": de <ul> waarbinnen vrijwel het hele menu zit.

button: de <button>'s binnen ul.menubalk. De zes <button>'s met de submenu's.

:hover: maar alleen als er over een <button> wordt gehoverd.

::after: met behulp van ::after wordt bij elk van die <button>'s een pseudo-element gemaakt. Dit pseudo-element wordt gebruikt om het teken '«' weer te geven.

De hele eerste selector in gewone taal: doe iets met het met behulp van ::after gemaakte pseudo-element, maar alleen als over een in ul.menubalk zittende <button> wordt gehoverd.

De tweede selector .menubalk li:focus-within button::after, na de komma:

.menubalk: de elementen met class="menubalk". Er is maar één element met class="menubalk": de <ul> waarbinnen vrijwel het hele menu zit.

li: alle <li>'s binnen ul.menubalk.

:focus-within: als het element (hier de <li>) of een van de nakomelingen daarvan de focus heeft. In elke <li> zitten een <button> en een serie <a>'s, die de focus kunnen krijgen.

button: de <button>'s binnen de <li>'s. De zes <button>'s met de submenu's.

::after: met behulp van ::after wordt bij elk van die <button>'s een pseudo-element gemaakt. Dit pseudo-element wordt gebruikt om het teken '«' weer te geven.

De hele tweede selector in gewone taal: doe iets met het bij <button> met behulp van ::after gemaakte pseudo-element, maar alleen als de binnen ul.menubalk zittende <li>, waar de <button> in zit, of een van de nakomelingen daarvan de focus heeft.

transform: rotate(90deg) translate(2px, 0);

Met behulp van transform kan een element worden gemanipuleerd. Met behulp van de bij transform horende functie rotate() kun je een element draaien. rotate(90deg) draait het element, in dit geval de '«', 90° met de klok mee. Hierdoor worden de punten naar boven gericht.

Om het teken op de juiste plaats te houden, blijkt ook nog een kleine aanpassing met behulp van de bij transform horende functie translate() nodig. Deze functie verplaatst een element ten opzichte van zichzelf.

Er staan twee waarden. De eerste waarde 2px voor de komma verplaatst 2 px in horizontale richting. Naar rechts, omdat de waarde positief is. De tweede waarde 0 na de komma verplaatst in verticale richting. Omdat hier 0 staat, verwijdert deze alleen maar de eerder bij ..menubalk button::after opgegeven verticale verplaatsing van -4px.

(Horizontaal, verticaal en rechts moeten hier niet letterlijk worden genomen. Omdat het pseudo-element 90° is gedraaid, veranderen horizontaal, verticaal en rechts ook van richting.)

.menubalk li:has(ul:hover) button::after

Voor deze elementen is eerder css opgegeven. Deze wordt binnen dit blokje herhaald in de volgorde, waarin deze in de stylesheet staat, zodat alles hier overzichtelijk bij elkaar staat.

.menubalk button::after {content: "«"; display: inline-block; width: 20px; font-size: 1.2em; text-align: center; margin-left: 20px; transform: rotate(270deg) translate(-2px, -4px);}

Alleen in browsers die content: "«" / ""; ondersteunen:

.menubalk button::after {content: "«" / "";}

Weer in alle browsers:

.menubalk button:hover::after, .menubalk li:focus-within button::after {transform: rotate(90deg) translate(2px, 0);}

.menubalk: de elementen met class="menubalk". Er is maar één element met class="menubalk": de <ul> waarbinnen vrijwel het hele menu zit.

li: alle <li>'s binnen ul.menubalk.

:has(): maar alleen als die <li>'s voldoen aan de voorwaarde die tussen de haakjes staat.

:has(ul:hover): dit is de voorwaarde, waaraan de <li>'s moeten voldoen. Er moet worden gehoverd over een <ul> die binnen de <li> staat.

button: de <button>'s binnen de <li>'s. De zes <button>'s met de submenu's.

::after: met behulp van ::after wordt bij elk van die <button>'s een pseudo-element gemaakt. Dit pseudo-element wordt gebruikt om het teken '«' weer te geven.

De hele selector in gewone taal: doe iets met het bij <button> met behulp van ::after gemaakte pseudo-element, maar alleen als binnen de <li> binnen ul.menubalk, waar de <button> in zit, een <ul> zit, waarover wordt gehoverd.

Dat is een hele mondvol. Er zijn zes <ul>'s die onder de selector .menubalk li ul vallen: de zes <ul>'s met de submenu's. Als over een link binnen een van deze <ul>'s wordt gehoverd, wordt automatisch ook over ouder <ul> gehoverd. In dat geval moet het teken '«' naar boven wijzen. Zonder deze regel zou, als over een submenu wordt gehoverd, het teken '«' weer naar beneden wijzen, terwijl het submenu nog is geopend.

Hier gebeurt precies hetzelfde als gelijk hierboven bij .menubalk button:hover::after, .menubalk li:focus-within button::after, wanneer over een van de zes <button>'s met submenu wordt gehoverd, of wanneer een link binnen een submenu de focus heeft.

Omdat :has() een tamelijk nieuwe selector is, werkt deze nog niet in Firefox . Als je in Firefox over een submenu hovert, wijst de '«' inderdaad weer naar beneden.

Firefox is bezig de ondersteuning voor :has() te implementeren, dus binnenkort zal dit zijn opgelost. Bovendien gaat het om een kleinigheid, dus er is verder niet gezocht naar een oplossing hiervoor.

Je zou deze selector op dezelfde regel als de twee eerdere selectors kunnen zetten, maar dan negeert Firefox alle drie de selectors. Als een browser een selector niet ondersteunt, worden álle andere selectors in dezelfde regel genegeerd. Daarom staat deze selector op een aparte regel.

transform: rotate(90deg) translate(2px, 0);

Hiermee wordt het teken '«' naar boven gedraaid. Dit gebeurt op precies dezelfde manier als hierboven bij transform: rotate(90deg) translate(2px, 0);.

.menubalk button:hover + ul, .menubalk button:focus + ul, .menubalk ul:hover, .menubalk button + ul:focus-within

Voor deze elementen is eerder css opgegeven. Deze wordt binnen dit blokje herhaald in de volgorde, waarin deze in de stylesheet staat, zodat alles hier overzichtelijk bij elkaar staat.

ul {list-style-type: none; margin: 0; padding: 0;}

.menubalk li:last-of-type ul {border-bottom: black solid 1px;}

.menubalk ul {height: 0; visibility: hidden; transform: translateY(-2000px); transition-delay: 0.1s;}

Dit zijn vier selectors, gescheiden door een komma.

De eerste selector .menubalk button:hover + ul:

.menubalk: de elementen met class="menubalk". Er is maar één element met class="menubalk": de <ul> waarbinnen vrijwel het hele menu zit.

button: de <button>'s binnen ul.menubalk. Dit zijn de zes <button>'s met submenu's.

:hover: maar alleen als er over een <button> wordt gehoverd.

+: het element achter de + moet in de html direct volgen op het element voor de +. In dit geval gaat het om de <ul> die gelijk op de <button> volgt. Beide elementen moeten ook nog dezelfde ouder hebben. Dat is hier het geval, ze hebben beide als ouder dezelfde <li>.

ul: de <ul> die gelijk op de <button> volgt. Dit is de <ul> met het submenu bij die <button>.

De eerste selector in gewone taal: doe iets met de <ul> die gelijk op een <button> binnen ul.menubalk volgt, maar alleen als over die <button> wordt gehoverd.

De tweede selector .menubalk button:focus + ul:

.menubalk: de elementen met class="menubalk". Er is maar één element met class="menubalk": de <ul> waarbinnen vrijwel het hele menu zit.

button: de <button>'s binnen ul.menubalk. Dit zijn de zes <button>'s met submenu's.

:focus: maar alleen als een button de focus heeft.

+: het element achter de + in de html direct volgen op het element voor de +. In dit geval gaat het om de <ul> die gelijk op de <button> volgt. Beide elementen moeten ook nog dezelfde ouder hebben. Dat is hier het geval, ze hebben beide als ouder dezelfde <li>.

ul: de <ul> die gelijk op de <button> volgt. Dit is de <ul> met het submenu bij die <button>.

De tweede selector in gewone taal: doe iets met de <ul> die gelijk op een <button> binnen ul.menubalk volgt, maar alleen als die <button> de focus heeft.

De derde selector .menubalk ul:hover:

.menubalk: de elementen met class="menubalk". Er is maar één element met class="menubalk": de <ul> waarbinnen vrijwel het hele menu zit.

ul: de <ul>'s binnen ul.menubalk. De zes <ul>'s met de submenu's.

:hover: maar alleen als er over een <ul> wordt gehoverd.

De derde selector in gewone taal: doe iets met de <ul>'s binnen ul.menubalk, maar alleen als over een <ul> wordt gehoverd.

De vierde selector .menubalk button + ul:focus-within:

.menubalk: de elementen met class="menubalk". Er is maar één element met class="menubalk": de <ul> waarbinnen vrijwel het hele menu zit.

button: de <button>'s binnen ul.menubalk. Dit zijn de zes <button>'s met submenu's.

+: het element achter de + moet in de html direct volgen op het element voor de +. In dit geval gaat het om de <ul> die gelijk op de <button> volgt. Beide elementen moeten ook nog dezelfde ouder hebben. Dat is hier het geval, ze hebben beide als ouder dezelfde <li>.

ul: de <ul> die gelijk op de <button> volgt. Dit is de <ul> met het submenu bij die <button>.

:focus-within: maar alleen als de <ul> of een van de nakomelingen daarvan de focus heeft.

De vierde selector in gewone taal: doe iets met de <ul> die gelijk op een <button> binnen ul.menubalk volgt, maar alleen als die <ul> of een van de nakomelingen daarvan de focus heeft. In elk van deze <ul>'s zit een serie <li>'s met links. Elk van die links kan de focus krijgen.

Deze vier selectors samen zorgen ervoor dat één van de zes submenu's zichtbaar is als:

* over een van de zes <button>'s wordt gehoverd;

* een van deze <button>'s de focus heeft;

* als over een bij zo'n <button> horende <ul> wordt gehoverd;

* als een van de links in een van die <ul>'s de focus heeft.

In al deze gevallen moet een submenu worden getoond.

height: auto; visibility: visible; transform: translateY(0);

Eerder zijn bij .menubalk ul de <ul>'s met de submenu's verborgen met height: 0;, visibility: hidden; en transform: translateY(-2000px);. Hier wordt één van die <ul>'s, en daarmee de erin zittende links, zichtbaar gemaakt.

Waarom de <ul>'s op deze ietwat ingewikkelde manier zijn verborgen, staat beschreven bij .menubalk ul.

.menubalk button:focus + ul

Voor dit element is eerder css opgegeven. Deze wordt hier niet allemaal herhaald, omdat het nogal veel is. Hier wordt slechts een eerder bij .menubalk ul opgegeven vertraging weggehaald.

.menubalk: de elementen met class="menubalk". Er is maar één element met class="menubalk": de <ul> waarbinnen vrijwel het hele menu zit.

button: de <button>'s binnen ul.menubalk. Dit zijn de zes <button>'s met submenu's.

:focus: maar alleen als een button de focus heeft.

+: het element achter de + moet in de html direct volgen op het element voor de +. In dit geval gaat het om de <ul> die gelijk op de <button> volgt. Beide elementen moeten ook nog dezelfde ouder hebben. Dat is hier het geval, ze hebben beide als ouder dezelfde <li>.

ul: de <ul> die gelijk op de <button> volgt. Dit is de <ul> met het submenu bij die <button>.

De selector in gewone taal: doe iets met de <ul> die gelijk op een <button> binnen ul.menubalk volgt, maar alleen als die <button> de focus heeft.

transition-delay: 0s;

Iets hierboven is bij .menubalk button:hover ... opgegeven dat submenu's moeten worden getoond met behulp van :focus en :hover. Eerder is bij .menubalk ul opgegeven dat dit met een vertraging van 0,1 seconde gebeurt.

Hoewel die vertraging heel kort is, is die toch irritant als een submenu door een klik, een aanraking of door gebruik van de Tab-toets wordt getoond. Die vertraging is alleen nodig voor :hover, zoals beschreven bij .menubalk ul. Daarom wordt hier die vertraging weggehaald, als een submenu met behulp van :focus wordt getoond.

@media (pointer: fine)

De css die binnen deze 'media query' staat, geldt alleen voor apparaten, waarvan de belangrijkste aanwijzer een nauwkeurige is, zoals een muis.

@media: geeft aan dat het om css gaat die alleen van toepassing is, als aan bepaalde voorwaarden wordt voldaan. Al langer bestond de mogelijkheid om met behulp van zo'n @media-regel css voor bijvoorbeeld printers op te geven. css3 heeft dat uitgebreid tot veel meer eigenschappen, zoals of er wel of niet gehoverd kan worden, en of de invoermethode ruw of precies is.

(pointer: fine): het primaire invoermechanisme moet 'precies' zijn, zoals een muis of een touchpad. De andere twee waarden zijn 'none' (geen invoermechanisme) en 'coarse' (een ruw invoermechanisme zoals een vinger).

De eigenschap en de waarde staan tussen tussen haakjes, zodat duidelijk is dat deze bij elkaar horen.

Gelijk na deze regel komt een { te staan, en aan het einde van de css die binnen deze media query valt een bijbehorende afsluitende }. Die zijn in de regel hierboven weggevallen, maar het geheel ziet er zo uit:

@media (pointer: fine) { body {color: silver;} (...) rest van de css voor deze media-regel(...) footer {color: gold;} }

Voor de eerste css binnen deze media query staat dus een extra {, en aan het eind staat een extra }.

De test hier is niet helemaal honderd procent waterdicht, maar zal vrijwel altijd werken. In het ergste geval floepen de submenu's toch tevoorschijn, en dat is ook niet het eind van de wereld. (Over die floepende submenu's staat gelijk hieronder meer.)

.menubalk button:hover + ul, .menubalk button:hover::after

Deze selector werkt alleen op apparaten, waarvan de belangrijkste aanwijzer een nauwkeurige is, zoals een muis. Voor apparaten is de uitleg hieronder niet van belang.

Voor deze elementen is eerder css opgegeven. Deze wordt binnen dit blokje herhaald in de volgorde, waarin deze in de stylesheet staat, zodat alles hier overzichtelijk bij elkaar staat.

ul {list-style-type: none; margin: 0; padding: 0;}

.menubalk li:last-of-type ul {border-bottom: black solid 1px;}

.menubalk button::after {content: "«"; display: inline-block; width: 20px; font-size: 1.2em; text-align: center; margin-left: 20px; transform: rotate(270deg) translate(-2px, -4px);}

Alleen in browsers die content: "«" / ""; ondersteunen:

.menubalk button::after {content: "«" / "";}

Weer in alle browsers:

.menubalk ul {height: 0; visibility: hidden; transform: translateY(-2000px); transition-delay: 0.1s;}

.menubalk button:hover::after, .menubalk li:focus-within button::after {transform: rotate(90deg) translate(2px, 0);}

.menubalk li:has(ul:hover) button::after {transform: rotate(90deg) translate(2px, 0);}

.menubalk button:hover + ul, .menubalk button:focus +ul, .menubalk ul:hover, .menubalk button + ul:focus-within {height: auto; visibility: visible; transform: translateY(0);}

.menubalk button:focus + ul {transition-delay: 0s;}

De eerste selector .menubalk button:hover + ul, voor de komma:

.menubalk: de elementen met class="menubalk". Er is maar één element met class="menubalk": de <ul> waarbinnen vrijwel het hele menu zit.

button: de <button>'s binnen ul.menubalk. Dit zijn de zes <button>'s met submenu's.

:hover: maar alleen als er over een <button> wordt gehoverd.

+: het element achter de + moet in de html direct volgen op het element voor de +. In dit geval gaat het om de <ul> die gelijk op de <button> volgt. Beide elementen moeten ook nog dezelfde ouder hebben. Dat is hier het geval, ze hebben beide als ouder dezelfde <li>.

ul: de <ul> die gelijk op de <button> volgt. Dit is de <ul> met het submenu bij die <button>.

De hele eerste selector in gewone taal: doe iets met de <ul> die gelijk op een <button> binnen ul.menubalk volgt, maar alleen als over die <button> wordt gehoverd.

De tweede selector .menubalk button:hover::after, na de komma:

.menubalk: de elementen met class="menubalk". Er is maar één element met class="menubalk": de <ul> waarbinnen vrijwel het hele menu zit.

button: de <button>'s binnen ul.menubalk. De zes <button>'s met de submenu's.

:hover: maar alleen als er over een <button> wordt gehoverd.

::after: met behulp van ::after wordt bij elk van die <button>'s een pseudo-element gemaakt. Dit pseudo-element wordt gebruikt om het teken '«' weer te geven.

De hele tweede selector in gewone taal: doe iets met het met behulp van ::after gemaakte pseudo-element, maar alleen als over een in ul.menubalk zittende <button> wordt gehoverd.

transition-delay: 0.3s;

De eerder voor de <ul>'s met de submenu's opgegeven css heeft de submenu's eerst verborgen, waarna deze later met behulp van :focus en :hover worden getoond, als dat nodig is.

Met behulp van de eerder voor het met ::after gemaakte pseudo-element wordt het teken '«' weergegeven na 'Menu 1' tot en met 'Menu 6'. Met de punten naar beneden, en als een submenu wordt weergegeven, worden de punten van het teken bij dat submenu naar boven gedraaid.

Dit werkt prima bij een aanraking op een touchscreen en bij gebruik van de Tab-toets. Maar als een muis wordt gebruikt, werkt het minder goed. Als de muis boven een <button> in de menubalk komt, opent het bijbehorende submenu. Dat gebeurt met een eerder bij .menubalk ul opgegeven vertraging van 0,1 seconde. Die vertraging is zo kort, dat je dat niet echt merkt.

Maar die vertraging is óók zo kort dat bij bewegen van de muis over de menubalk alle submenu's achter elkaar verschijnen en weer verdwijnen. Heel irritant. Als het menu op de desktop sterk wordt vergroot, werkt het hetzelfde als in een smal browservenster: de <button>'s uit de menubalk staan onder elkaar. Hier is het nog veel vervelender, omdat de <button>'s van plaats veranderen als een submenu opent. Mensen die ooit een voortdurend wegspringende vlo hebben geprobeerd te vangen, zullen dit herkennen. Er zijn er voor minder aan de drank geraakt.

Hetzelfde verschijnsel doet zich voor bij het teken '«', als dit van richting verandert bij het verschijnen van een submenu. Alleen is dit niet echt irritant, omdat het hier maar om één teken gaat.

Daarom wordt hier voor hoveren een kleine extra vertraging ingebouwd: 0,3 seconde in plaats van 0,1 seconde. (Voor het teken '«' was eerder geen vertraging opgegeven, omdat dit niet echt irritant is.) Nu openen de submenu's gewoon bij hoveren over een <button>, maar als je de muis snel over de menubalk beweegt, openen ze door de vertraging van 0,3 seconde niet.

Omdat bij .menubalk ul eerder een vertraging van 0,1 seconde is opgegeven, verdwijnen de submenu's nog steeds na 0,1 seconde. Alleen het verschijnen wordt bij hoveren vertraagd.

main

Hierbinnen staat de belangrijkste inhoud van de pagina (in dit voorbeeld is dat alleen een titel en Latijnse flauwekul tekst).

background: white;

Witte achtergrond.

color: black;

Voorgrondkleur zwart. Dit is onder andere de kleur van de tekst.

Hoewel dit de standaardkleur is, wordt deze toch specifiek opgegeven. Hierboven is een achtergrondkleur opgegeven. Sommige mensen hebben zelf de voorgrond‑ en/of achtergrondkleur veranderd, bijvoorbeeld omdat ze slecht kleuren kunnen onderscheiden. Als nu de achtergrondkleur wordt veranderd, maar niet de voorgrondkleur, loop je het risico dat tekstkleur en achtergrondkleur te veel op elkaar gaan lijken.

Door beide op te geven, is redelijk zeker dat achtergrond‑ en tekstkleur genoeg van elkaar blijven verschillen. Als de gebruiker !important heeft gebruikt in een eigen stylesheet, is er nog niets aan de hand, want dan veranderen achtergrond‑ en voorgrondkleur geen van beide.

Dit is ook al bij <body> opgegeven, maar sommige mensen hebben bij álle elementen de kleuren veranderd. Het heeft immers weinig zin, als ze dat alleen bij de body doen, terwijl de sitebouwer de kleuren ook bij bijvoorbeeld de paragrafen heeft aangepast.

width: 90vw;

Breedte.

De eenheid vw is gebaseerd op de breedte van het venster van de browser. 1 vw is 1% van de breedte van het venster, en 90 vw is 90% van de breedte. <main> wordt hierdoor nooit breder dan 90% van de breedte van het venster, ongeacht de breedte van het venster.

max-width: 758px;

Hier gelijk boven is een breedte van 90% van het venster van de browser opgegeven. In bredere vensters worden de regels hierdoor veel te lang. Daarom wordt hier een maximumbreedte opgegeven.

margin: 4em auto 0;

Omdat voor links geen waarde is opgegeven, krijgt links automatisch dezelfde waarde als rechts. Hier staat dus eigenlijk 4em auto 0 auto in de volgorde boven – rechts – onder – links.

Boven een marge van 4 em. Als eenheid wordt de relatieve eenheid em gebruikt, zodat de hoogte mee verandert met de lettergrootte. Hierdoor blijft <main> altijd onder het menu staan, ook bij een grotere letter. Bij gebruik van een absolute eenheid zoals px verandert de hoogte niet mee met de lettergrootte. Zoomen kan wel altijd, ongeacht welke eenheid voor de hoogte wordt gebruikt.

Links en rechts auto, wat hier hetzelfde betekent als evenveel. Hierdoor komt <main> altijd horizontaal gecentreerd binnen ouder <body> te staan. <body> is een blok-element en wordt daardoor normaal genomen automatisch even breed als de ouder ervan: <html>. Omdat <html> het buitenste element is, wordt dit normaal genomen even breed als het venster van de browser.

Hierdoor staat uiteindelijk <main> altijd horizontaal gecentreerd binnen het venster van de browser, ongeacht de breedte van het venster. En daarmee ook de in <main> zittende tekst.

Deze manier van horizontaal centreren van een blok-element werkt alleen, als het blok-element een breedte heeft, want anders zou het normaal genomen even breed worden als de ouder ervan. Dat is geregeld, want iets hierboven heeft <main> zowel een breedte van 90 vw als een maximumbreedte van 758 px gekregen.

border: black solid 1px;

Zwart randje.

padding: 0 10px 10px;

Omdat voor links geen waarde is opgegeven, krijgt links automatisch dezelfde waarde als rechts. Hier staat dus eigenlijk 0 10px 10px 10px in de volgorde boven – rechts – onder – links. Aan de bovenkant zorgt een <h1> voor wat afstand tussen de buitenkant van en de tekst in <main>. Rechts, onder en links 10 px afstand tussen buitenkant van en tekst in <main>.

h1

Alle <h1>'s. Dat is er maar één: de belangrijkste kop van de pagina.

font-size: 1.2em;

Van zichzelf heeft een <h1> een wel erg grote letter. Die wordt hier iets kleiner gemaakt. Als eenheid wordt de relatieve eenheid em gebruikt, omdat bij gebruik van een absolute eenheid zoals px niet alle browsers de lettergrootte kunnen veranderen. Zoomen kan wel altijd, ongeacht welke eenheid voor de lettergrootte wordt gebruikt.

css voor vensters minimaal 760 px breed

@media screen and (min-width: 760px)

De css die hier tot nu toe staat, geldt voor alle browservensters. De css die binnen deze 'media query' staat, geldt alleen voor vensters die minimaal 760 px breed zijn. In deze bredere vensters wordt de menubalk met de zes <button>'s met submenu's altijd weergegeven.

@media: geeft aan dat het om css gaat die alleen van toepassing is, als aan bepaalde voorwaarden wordt voldaan. Al langer bestond de mogelijkheid om met behulp van zo'n @media-regel css voor bijvoorbeeld printers op te geven. css3 heeft dat uitgebreid tot veel meer eigenschappen, zoals de breedte en hoogte van het venster van de browser.

screen: deze regel geldt alleen voor schermweergave.

and: er komt nog een voorwaarde, waaraan moet worden voldaan.

(min-width: 760px): het browservenster moet minimaal 760 px breed zijn. Is het venster smaller, dan wordt de css die binnen deze media query staat genegeerd.

Gelijk na deze regel komt een { te staan, en aan het einde van de css die binnen deze query valt een bijbehorende afsluitende }. Die zijn in de regel hierboven weggevallen, maar het geheel ziet er zo uit:

@media screen and min-width: 760px) { body {color: silver;} (...) rest van de css voor deze media query (...) footer {color: gold;} }

Voor de eerste css binnen deze media query staat dus een extra {, en aan het eind staat een extra }.

Als je nou 'n mobieltje hebt met een resolutie van – om maar wat te noemen – 1024 x 768 px, dan geldt deze media query toch niet voor dat mobieltje. Terwijl dat toch echt meer dan 760 px breed is. Een vuig complot van gewetenloze multinationals? Voordat je je gaat beklagen bij Radar, zou ik eerst even verder lezen.

Steeds meer mobiele apparaten, maar ook steeds meer gewone beeldschermen, hebben een hogere resolutiedichtheid. Dat wil zeggen dat ze kleinere pixels hebben, die dichter bij elkaar staan. Daardoor zijn foto's, tekst, en dergelijke veel scherper weer te geven. Hoe kleiner de puntjes (de pixels) zijn, waaruit een afbeelding is opgebouwd, hoe duidelijker het wordt.

Er ontstaat alleen één probleem: als je de pixels twee keer zo klein maakt, wordt ook wat je ziet twee keer zo klein. En inmiddels zijn er al apparaten met pixels die meer dan vier keer zo klein zijn. Een lijntje van 1 px breed zou op die apparaten minder dan 'n kwart van de oorspronkelijke breedte krijgen en vrijwel onzichtbaar zijn. Een normale foto zou in een thumbnail veranderen. Kolommen zouden heel smal worden. Tekst zou onleesbaar klein worden. Allemaal fantastisch scherp, maar je hebt 'n vergrootglas nodig om 't te kunnen zien.

Om dit te voorkomen wordt een verschil gemaakt tussen css-pixels en schermpixels (in het Engels 'device-pixels'). De css-pixels zijn gebaseerd op de – tot voor kort – normale beeldschermen van de desktop. 1 css-pixel is op zo'n beeldscherm 1 pixel. Het aantal schermpixels is het werkelijk op het apparaat aanwezige aantal pixels (dat is het aantal pixels, waarvoor je hebt betaald).

Dat eerder genoemde mobieltje van 1024 x 768 px heeft wel degelijk het aantal pixels, waarvoor je hebt betaald. Maar die zitten dichter bij elkaar. Op een gewoon beeldscherm zitten 96 pixels per inch, wat wordt uitgedrukt met de eenheid ppi ('pixels per inch'). (Vaak wordt foutief de eenheid dpi ('dots per inch') gebruikt. Die eenheid is voor printers.) Als dat mobieltje een resolutie van 192 ppi heeft, 192 pixels per inch, zijn de pixels ervan twee keer zo klein als op een origineel beeldscherm. Er zijn per inch twee keer zoveel schermpixels aanwezig.

Om nu te voorkomen dat alles op dat mobieltje twee keer zo klein wordt, geeft het mobieltje niet het echte aantal schermpixels (1024 x 768), maar een lager aantal css-pixels door bij een media query. De 192 ppi van het mobieltje is twee keer zo veel als de 96 ppi van een normaal beeldscherm. Het aantal css-pixels is dan het aantal schermpixels gedeeld door 2. 1024 x 768 gedeeld door 2 is 512 x 384 px. Het aantal css-pixels is 512 x 384 px en zit daarmee dus ruim onder de 760 px van deze media query. Je bent dus niet opgelicht, of in ieder geval niet wat betreft het aantal pixel.

Door deze truc is een lijn van 1 px breed op een normaal beeldscherm ook op het mobieltje nog steeds 1 px breed, alleen wordt die ene (css-)pixel opgebouwd uit twee schermpixels (feitelijk vier, want het verhaal geldt voor breedte én hoogte). De dikte van het lijntje is hetzelfde, maar het is veel fijner opgebouwd. Bij lijntjes is dat verschil bijvoorbeeld in bochten goed te zien.

Hetzelfde verhaal geldt voor hogere resoluties, Een tablet met een breedte van 4096 schermpixels en een ppi van 384 (vier keer de originele dichtheid) geeft 4096 gedeeld door 4 = 1024 css-pixel door. Het lijntje van 1 px breedte op de originele monitor is nog steeds 1 css-pixel breed op de tablet, maar die ene css-pixel is nu opgebouwd uit zestien schermpixel.

(Overigens kun je met behulp van media query's ook testen op de resolutie met gebruik van het sleutelwoord 'resolution'. Apple gebruikt het niet-standaard 'device-pixel-ratio', maar het idee is hetzelfde. Dit kan bijvoorbeeld handig zijn om te bepalen, hoe groot een foto moet zijn.)

Kort samengevat: omdat niet het aantal schermpixels (waarvoor je hebt betaald), maar het aantal css-pixels (de door de ontwerper bedoelde afmeting) wordt doorgegeven, wordt voorkomen dat een hogeresolutiescherm onleesbaar klein wordt.

body

Deze selector werkt alleen in browservensters minimaal 760 px breed. Voor andere vensters is de uitleg hieronder niet van belang.

Voor dit element is eerder css opgegeven. Deze wordt binnen dit blokje herhaald in de volgorde, waarin deze in de stylesheet staat, zodat alles hier overzichtelijk bij elkaar staat. (Alleen wat binnen deze media query geldig is, wordt binnen dit blokje herhaald.)

body {background: #ff9; color: black; font-family: Arial, Helvetica, sans-serif; margin: 0;}

Het element waarbinnen de hele pagina staat. Veel instellingen die hier worden opgegeven, worden geërfd door de nakomelingen van <body>. Ze gelden voor de hele pagina, tenzij ze later worden gewijzigd. Dit geldt bijvoorbeeld voor de lettersoort, de lettergrootte en de voorgrondkleur.

font-size: 110%;

Iets groter dan standaard in deze grotere browservensters. 't Zal de leeftijd zijn, maar ik vind de standaardgrootte wat te klein.

Als eenheid wordt de relatieve eenheid % gebruikt, omdat bij gebruik van een absolute eenheid zoals px niet alle browsers de lettergrootte kunnen veranderen. Zoomen kan wel altijd, ongeacht welke eenheid voor de lettergrootte wordt gebruikt.

.voor-smal

Deze selector werkt alleen in browservensters minimaal 760 px breed. Voor andere vensters is de uitleg hieronder niet van belang.

Voor dit element is eerder css opgegeven. Deze wordt binnen dit blokje herhaald in de volgorde, waarin deze in de stylesheet staat, zodat alles hier overzichtelijk bij elkaar staat. (Alleen wat binnen deze media query geldig is, wordt binnen dit blokje herhaald.)

.voor-smal {appearance: none; background: #e4e4e4; color: black; width: 100%; font-size: 1.1em; text-align: left; border: none; border-bottom: black solid 1px; padding: 0; position: fixed; top: 0; z-index: 10;}

.voor-smal:focus {outline: none;}

Alle elementen met class="voor-smal". Dat is er maar één: de <button> voor smallere browservensters, waarmee de menubalk met de zes <button>'s met submenu's verborgen of getoond kan worden.

display: none;

De <button> waarmee de menubalk getoond en verborgen kan worden, is in bredere browservensters niet nodig, want de menubalk is daarin altijd zichtbaar. Daarom wordt deze <button> verborgen.

.menubalk

Deze selector werkt alleen in browservensters minimaal 760 px breed. Voor andere vensters is de uitleg hieronder niet van belang.

Voor dit element is eerder css opgegeven. Deze wordt binnen dit blokje herhaald in de volgorde, waarin deze in de stylesheet staat, zodat alles hier overzichtelijk bij elkaar staat. (Alleen wat binnen deze media query geldig is, wordt binnen dit blokje herhaald.)

.menubalk {display: none; flex-direction: column; max-height: 80vh; overflow: auto; overscroll-behavior-y: contain; position: fixed; top: 3.2rem;}

nav:focus-within .menubalk {display: flex;}

Alleen op apparaten, waarvan de belangrijkste aanwijzer een onnauwkeurige is, zoals een vinger:

nav:hover .menubalk {display: flex;}

De elementen met class="menubalk". Er is maar één element met class="menubalk": de <ul> waarbinnen vrijwel het hele menu zit.

display: flex;

Omdat in smallere browservensters geen ruimte is voor een menubalk met zes <button>'s, is deze eerder bij .menubalk met display: none; verborgen. In deze bredere vensters moet de menubalk voortdurend zichtbaar zijn.

Dat zichtbaar maken gebeurt hier met display: flex; waarmee ul.menubalk in een zogenaamde 'flex container' verandert. Dit maakt het veel makkelijker om de directe kinderen van dit element, de 'flex items', op een bepaalde plaats neer te zetten. De directe kinderen van de menubalk zijn hier zes <li>'s. In elk van die <li>'s zit weer een <button> met bijbehorend submenu.

flex-direction: row;

Met flex-direction wordt de richting van de hoofdas (in het Engels: 'main axis') van de flex container aangegeven. Eerder is die richting bij .menubalk met flex-direction: column; veranderd, waardoor de flex items (de zes <li>'s) onder elkaar komen te staan. In deze bredere browservensters moeten de flex items niet onder, maar naast elkaar komen te staan.

width: 90vw;

Breedte.

De eenheid vw is gebaseerd op de breedte van het venster van de browser. 1 vw is 1% van de breedte van het venster, en 90 vw is 90% van de breedte. <main> wordt hierdoor nooit breder dan 90% van de breedte van het venster, ongeacht de breedte van het venster.

max-width: 780px;

Hier gelijk boven is een breedte van 90% van het venster van de browser opgegeven. In bredere zouden de knoppen in de menubalk hierdoor heel breed worden. Daarom wordt hier een maximumbreedte opgegeven.

margin: 20px auto 0;

Omdat voor links geen waarde is opgegeven, krijgt links automatisch dezelfde waarde als rechts. Hier staat dus eigenlijk 20px auto 0 auto in de volgorde boven – rechts – onder – links.

Boven een marge van 20 px voor wat afstand tussen menubalk en bovenkant van het browservenster. Onderaan geen marge.

Links en rechts auto, wat hier hetzelfde betekent als evenveel. Oftewel: ul.menubalk staat altijd gecentreerd ten opzichte van de ouder ervan. Die ouder is het blok-element <nav>. Een blok-element wordt normaal genomen automatisch even breed als de ouder van het element. De ouder van <nav> is <body>, ook weer een blok-element, dat dus normaal genomen ook weer even breed wordt als de ouder ervan. De ouder van <body> is <html>. Omdat <html> het buitenste element is, wordt dit normaal genomen even breed als het venster van de browser. Hierdoor staat uiteindelijk ul.menubalk automatisch horizontaal gecentreerd in het venster, ongeacht de breedte van het venster.

Deze manier van horizontaal centreren van een blok-element werkt alleen, als het blok-element een breedte heeft, want anders zou het normaal genomen even breed worden als de ouder ervan.

Maar hier doet zich een eigenaardig verschijnsel voor. ul.menubalk is bij .menubalk fixed gepositioneerd, en bij een fixed gepositioneerd element werkt de truc om met auto te centreren normaal genomen niet. Hier werkt die echter toch.

Iets hieronder wordt met right: 0; en left: 0; opgegeven dat ul.menubalk van de linker- tot de rechterkant van het browservenster moet lopen. Iets hierboven is echter een maximumbreedte van 780 px opgegeven. In vensters met een breedte van maximaal 780 px werkt dit prima. Maar in bredere vensters wordt hier een onmogelijke opdracht gegeven.

Als het browservenster bijvoorbeeld 1000 px breed is en ul.menubalk moet van left: 0; tot right: 0; lopen, én mag vanwege de max-width: 780px; niet breder dan 780 px zijn, dan kan aan één van die drie eigenschappen onmogelijk worden voldaan.

De browser raakt hier dermate van in de war, dat right: 0; en left: 0; gewoon worden genegeerd en margin: 0 auto; toch werkt. Oftewel: bij een fixed positie werkt margin: 0 auto; niet, tenzij je ook left: 0; en right: 0; opgeeft.

(Deze weinig bekende truc werkt trouwens ook bij position: absolute;. Je kunt de horizontale plaatsing nog beïnvloeden door bij right of left een andere waarde dan 0 in te vullen. En het is ook niet zo dat de browser in de war raakt. Dit staat gewoon in de specificatie. Alleen is het zo weerzinwekkend technisch opgeschreven dat naar verluidt zelfs Einstein het pas na 38 keer lezen begreep. Inmiddels is er een nieuwe ontwerpspecificatie met een andere benadering, maar het resultaat is hetzelfde.)

pointer-events: none;

De submenu's zichtbaar gemaakt.

Bij .menubalk ul is voor smallere browservensters bij de <ul>'s met de submenu's met height: 0; de hoogte weggehaald, waardoor ze geen ruimte innemen. In deze bredere vensters wordt dit iets verderop bij .menubalk ul veranderd in height: auto;: hoog genoeg om de inhoud ervan (in elke <ul> een serie <li>'s met links) ervan weer te geven.

Je ziet de <ul>'s (en daardoor ook de submenu's) niet, omdat deze met visibility: hidden; en transform: translateY(-2000px); zijn verborgen. Maar ze nemen nog steeds gewoon hun normale ruimte in, ook al zie je ze niet.

Op de afbeelding zijn de <ul>'s zichtbaar gemaakt met een blauwe stippellijn.

ul.menubalk is zichtbaar gemaakt met rode stippen. ul.menubalk wordt, omdat de <ul>'s nu een hoogte hebben, hoog genoeg om de inhoud ervan weer te geven. Hierdoor kan bijvoorbeeld tekst die onder ul.menubalk zit niet worden gekopieerd, links zouden niet kunnen worden aangeklikt, enzovoort.

In de html komt <main> met de tekst na ul.menubalk. Normaal genomen zou hierdoor <main> met alles erin gewoon boven ul.menubalk staan en zou er geen probleem zijn. Maar ul.menubalk is bij .menubalk fixed gepositioneerd. En een element met een relatieve, absolute of fixed positie komt altijd boven een element zonder zo'n positie te staan. En zo'n element zonder positie is <main>.

Je zou dit bijvoorbeeld ook op kunnen lossen door <main> een relatieve positie te geven. Dan komt <main> met de tekst erin wel boven ul.menubalk te staan. Maar dan verdwijnen de submenu's weer onder <main>. Bovendien is de veroorzaker van deze kommer en kwel ul.menubalk, dus het is logischer om dit bij de veroorzaker op te lossen.

pointer-events: none; zorgt ervoor dat aanrakingen en klikken volledig worden genegeerd door het element en de nakomelingen ervan. Aanrakingen en klikken gaan als het ware dwars door ul.menubalk en nakomelingen heen. Nu kan tekst onder ul.menubalk wel worden gekopieerd, links eronder werken gewoon, enzovoort.

(Verderop wordt dit voor de <button>'s en links weer hersteld, want dat zijn nakomelingen van ul.menubalk en die zouden anders ook niet reageren op een klik of aanraking.)

top: 0;

Bovenaan het browservenster neerzetten.

left: 0; right: 0;

Normaal genomen zou ul.menubalk hierdoor het browservenster van helemaal links tot helemaal rechts vullen. Hier werkt het echter anders: de menubalk wordt horizontaal gecentreerd binnen het venster. De uitleg hiervan staat iets hierboven bij margin: 20px auto 0;.

.menubalk::before

Deze selector werkt alleen in browservensters minimaal 760 px breed. Voor andere vensters is de uitleg hieronder niet van belang.

Met behulp van ::before wordt bij de elementen met class="menubalk", de <ul> met de zes <button>'s met submenu's, een pseudo-element gemaakt. Dit pseudo-element wordt gebruikt om de ruimte boven de menubalk af te dekken.

De afdekking boven het menu zichtbaar gemaakt.

Boven de menubalk is een kleine lege ruimte. Als de tekst onder de menubalk naar boven wordt gescrold, is de tekst in die ruimte zichtbaar. Dit is wat er op de bovenste afbeelding hierboven gebeurt.

Daarom wordt deze ruimte met behulp van dit pseudo-element afgedekt. Op de onderste afbeelding hierboven heeft het pseudo-element even een rode achtergrondkleur gekregen, waardoor het zichtbaar is. Hoewel dat niet echt nodig is, wordt ook de ruimte rechts en links van de menubalk afgedekt. Daar is niets op tegen en het is makkelijker om het zo te doen dan om het pseudo-element even breed als de menubalk te maken.

background: #ff9;

Achtergrondkleurtje. Dit is dezelfde achtergrondkleur als de pagina bij body heeft gekregen, zodat je het pseudo-element niet ziet.

content: "";

Tussen de aanhalingstekens komt de tekst te staan, die weergegeven moet worden. Hier is geen tekst, daarom staat er niets tussen de aanhalingstekens. Maar content moet altijd aanwezig zijn, want anders wordt het hele pseudo-element niet weergegeven.

height: calc(2.6em + 24px);

Hoogte.

Met behulp van calc() kunnen berekeningen worden gemaakt. In dit geval wordt de hoogte van het pseudo-element berekend. Als dit te laag wordt, kan boven de menubalk tekst zichtbaar worden. Als het te hoog wordt, kan het tekst onder de menubalk afdekken.

2.6em: dit is de hoogte die de <button>'s in de menubalk bij .menubalk button hebben gekregen. Als eenheid wordt de relatieve eenheid em gebruikt, zodat de hoogte mee verandert met de lettergrootte: als de tekst in de <button>'s wordt vergroot, worden de <button>'s hoger. Het pseudo-element groeit dan gewoon mee. Bij gebruik van een absolute eenheid zoals px verandert de hoogte niet mee met de lettergrootte. Zoomen kan wel altijd, ongeacht welke eenheid voor de hoogte wordt gebruikt.

24px: boven ul.menubalk zit een marge van 20 px. Samen met nog wat dingen als borders is dit precies de hoogte van de menubalk.

De berekening wordt hier gemaakt met twee verschillende eenheden: em en px. Dat kan niet, eerst moeten alle eenheden worden gerekend naar dezelfde eenheid. Daarom rekent de browser de eenheid em om naar de eenheid px.

Bij het schrijven van de code kan dat omrekenen niet, omdat je niet weet hoe groot de lettergrootte en dus de hoogte van de menubalk is. Maar op het moment van weergave weet de browser dat wel.

De berekening wordt dan 2,6 keer de lettergrootte plus 24 px. Hiermee loopt het pseudo-element altijd van de bovenkant van het browservenster tot de onderkant van de menubalk.

position: fixed;

Om het pseudo-element op een bepaalde plaats neer te kunnen zetten.

Er wordt gepositioneerd ten opzichte van het 'containing block'. Dat is bij een fixed positie normaal genomen het venster van de browser. Hierdoor scrolt het pseudo-element, net als de menubalk, niet mee met de pagina.

Een met behulp van ::before gemaakt pseudo-element is van zichzelf een inline-element, waardoor eigenschappen als hoogte niet gebruikt kunnen worden. Door het pseudo-element fixed te positioneren verandert het in een soort blok-element, waardoor dit soort eigenschappen wel is te gebruiken.

top: 0;

Bovenaan het browservenster neerzetten.

left: 0; right: 0;

Van helemaal links in het browservenster tot helemaal rechts laten lopen.

.menubalk li

Deze selector werkt alleen in browservensters minimaal 760 px breed. Voor andere vensters is de uitleg hieronder niet van belang.

Voor deze elementen is eerder css opgegeven. Deze wordt binnen dit blokje herhaald in de volgorde, waarin deze in de stylesheet staat, zodat alles hier overzichtelijk bij elkaar staat. (Alleen wat binnen deze media query geldig is, wordt binnen dit blokje herhaald.)

.menubalk ul li {background: #e4e4e4; color: black; width: 9em; margin-top: -1px; border: black solid 1px;}

Alle <li>'s binnen de elementen met class="menubalk". Er is maar één element met class="menubalk": de <ul> waarbinnen vrijwel het hele menu zit.

flex-grow: 1;

Bij .menubalk is ul.menubalk met display: flex; in een zogenaamde 'flex container' veranderd. Daardoor zijn de directe kinderen van ul.menubalk in zogenaamde 'flex items' veranderd.

De <li>'s hieronder zijn een direct kind van ul.menubalk:

<ul class="menubalk"> <li id="direct-kind"></li> </ul>

De binnenste <li>'s hieronder zijn geen direct kind van ul.menubalk, omdat er tussen ul.menubalk en de binnenste <li>'s een <li> en een <ul> zitten:

<ul class="menubalk"> <li id="direct-kind"> <ul> <li id="geen-direct-kind"></li> </ul> </li> </ul>

Standaard nemen flex items – hier de zes <li>'s – niet meer ruimte in dan nodig is om de inhoud ervan weer te geven. Met flex-grow: 1; nemen de flex items de volle ruimte van hun ouder flex container ul.menubalk in. ul.menubalk heeft eerder bij .menubalk een breedte van 90 vw gekregen: 90% van de breedte van het browservenster (met een maximumbreedte van 780px). Die breedte wordt nu volledig opgevuld door de zes <li>'s.

Je kunt het ene flex item meer (of minder) laten groeien dan het andere door bij de flex items verschillende waarden voor flex-grow op te geven, maar dat gebeurt hier niet: alle zes hebben dezelfde waarde '1'.

Door alle nakomelingen van de zes <li>'s even breed te maken als de <li>'s, worden <button>'s en submenu's allemaal even breed als de <li>'s.

.menubalk button

Deze selector werkt alleen in browservensters minimaal 760 px breed. Voor andere vensters is de uitleg hieronder niet van belang.

Voor deze elementen is eerder css opgegeven. Deze wordt binnen dit blokje herhaald in de volgorde, waarin deze in de stylesheet staat, zodat alles hier overzichtelijk bij elkaar staat. (Alleen wat binnen deze media query geldig is, wordt binnen dit blokje herhaald.)

.menubalk button {appearance: none; background: #e4e4e4; color: black; width: 10em; height: 2.6em; font-size: 1.1em; text-align: center; border: black solid 1px;}

.menubalk button:focus-visible, .menubalk a:focus-visible {background: white; outline: blue solid 3px; outline-offset: -3px;}

Alle <button>'s binnen de elementen met class="menubalk". Er is maar één element met class="menubalk": de <ul> waarbinnen vrijwel het hele menu zit.

width: 100%;

Breedte.

Een breedte in procenten is normaal genomen ten opzichte van de ouder van het element. Dat is hier de <li>, waar de <button> in zit.

border-left: none; Te dikke randen tussen de knoppen.

Eerder hebben de <button>'s aan alle kanten een border van 1 px breed gekregen. Waar twee <button>'s elkaar raken, wordt dat een gezamenlijke border van 2 px breed, zoals op de afbeelding is te zien. Om dat te voorkomen wordt de border aan de linkerkant weggehaald.

pointer-events: auto;

Eerder is voorouder ul.menubalk bij .menubalk met pointer-events: none; ongevoelig gemaakt voor klikken en aanraken. Daardoor zijn ook de nakomelingen, waaronder deze <button>'s, ongevoelig geworden, waardoor ze niet meer werken. Hier worden de <button>'s weer gevoelig gemaakt voor een aanraking of klik.

position: relative;

Boven de menubalk is een lege ruimte. Bij scrollen zou de tekst van de pagina daarin zichtbaar zijn. Om dat te voorkomen is die ruimte bij .menubalk::before met behulp van een pseudo-element afgedekt. Die afdekking loopt van de bovenkant van het venster van de browser tot de onderkant van de menubalk.

De <button>'s staan in de html na .menubalk (en daarmee ook het pseudo-element bij .menubalk) en zouden normaal genomen boven het pseudo-element komen te staan. Maar het pseudo-element is fixed gepositioneerd, en een element met een fixed, absolute of relatieve wordt komt altijd boven een element zonder zo'n positie te staan. Daardoor verdwijnen de <button>'s volledig achter het pseudo-element.

Door de <button>'s ook een relatieve positie te geven, wordt dit opgelost: ze staan nu boven het pseudo-element. Omdat verder voor top en dergelijke niets wordt opgegeven, heeft dit verder geen enkele invloed.

.menubalk li:first-of-type button

Deze selector werkt alleen in browservensters minimaal 760 px breed. Voor andere vensters is de uitleg hieronder niet van belang.

Voor deze elementen is eerder css opgegeven. Deze wordt binnen dit blokje herhaald in de volgorde, waarin deze in de stylesheet staat, zodat alles hier overzichtelijk bij elkaar staat. (Alleen wat binnen deze media query geldig is, wordt binnen dit blokje herhaald.)

.menubalk button {appearance: none; background: #e4e4e4; color: black; width: 10em; height: 2.6em; font-size: 1.1em; text-align: center; border: black solid 1px;}

.menubalk button:focus-visible, .menubalk a:focus-visible {background: white; outline: blue solid 3px; outline-offset: -3px;}

.menubalk button {width: 100%; border-left: none; pointer-events: auto;}

Kinderen van dezelfde ouder kunnen worden geteld, net zoals dat bij kinderen uit een gezin het geval is: eerste kind, tweede kind, derde kind, enzovoort. Je kunt ook alleen 'n bepaald soort element tellen, net zoals je alleen kinderen van jonger of ouder dan zes jaar kunt tellen. Met de pseudo-class :nth-of-type() worden alleen elementen van een bepaald soort geteld. Omdat het vaak voorkomt dat je het eerste element van 'n bepaald type wilt hebben, heeft dat een aparte pseudo-class, het in deze selector gebruikte :first-of-type.

.menubalk: de elementen met class="menubalk. Dat is er maar één: de <ul> waarbinnen vrijwel het hele menu zit.

li: alle <li>'s binnen ul.menubalk.

:first-of-type: het element met een bepaald volgnummer. In dit geval wordt geen volgnummer gebruikt, maar een speciaal voor het eerste element bedoelde pseudo-class: :first-of-type. Voor alle latere elementen gebruik je 'n soortgelijke pseudo-class, maar dan met een volgnummer: :nth-of-type(). Tussen de haakjes komt het volgnummer. :nth-of-type(1) is precies hetzelfde als :first-of-type, maar de laatste is wat mensvriendelijker.

Omdat voor :first-of-type (of voor latere elementen :nth-of-type()) een li staat, worden alleen <li>'s geteld. Als binnen ul.menubalk 327 <p>'s zitten, tellen die niet mee. Hadden ze maar 'n <li> moeten zijn.

button: de <button>'s binnen deze eerste <li>.

De hele selector in gewone taal: de <button>'s binnen elke eerste <li> binnen ul.menubalk.

Binnen ul.menubalk zit een hele serie <ul>'s. Elke eerste <li> binnen zo'n <ul> valt binnen het stukje .menubalk li:first-of-type van deze selector, ook elke eerste <li> van een submenu. Maar er is maar één eerste <li>, waarbinnen weer een <button> zit. Uiteindelijk bestrijkt deze selector dus alleen de <button> in de eerste <li> binnen ul.menubalk: de eerste van de zes <button>'s met een submenu.

De pseudo-class :first-of-type (of :nth-of-type()) kan onverwachte bijwerkingen hebben. In dit geval is er maar één eerste <li>, waarbinnen een <button> zit. Maar als binnen ul.menubalk bijvoorbeeld nog 'n dieper geneste eerste <li> met een <button>zou zitten, zou deze selector ook voor die <button> gelden. (Dit zou je in dit geval kunnen oplossen door het toevoegen van >: .menubalk > li:first-of-type button. Dan geldt de selector alleen voor de eerste <li> die een direct kind van ul.menubalk is.)

border-left: black solid 1px;

Hier gelijk boven is de linkerborder bij de zes <button>'s met submenu's weggehaald, omdat die tegen de rechterborder aan komt te staan. De <button> in de eerste <li> is de meest linkse <button>. Deze heeft nu links geen border meer. Dat wordt hier hersteld.

.menubalk ul

Deze selector werkt alleen in browservensters minimaal 760 px breed. Voor andere vensters is de uitleg hieronder niet van belang.

Voor deze elementen is eerder css opgegeven. Deze wordt binnen dit blokje herhaald in de volgorde, waarin deze in de stylesheet staat, zodat alles hier overzichtelijk bij elkaar staat. (Alleen wat binnen deze media query geldig is, wordt binnen dit blokje herhaald.)

ul {list-style-type: none; margin: 0; padding: 0;}

.menubalk li:last-of-type ul {border-bottom: black solid 1px;}

.menubalk ul {height: 0; visibility: hidden; pointer-events: auto; transform: translateY(-2000px); transition-delay: 0.1s;}

.menubalk button:hover + ul, .menubalk button:focus +ul, .menubalk ul:hover, .menubalk button + ul:focus-within {height: auto; visibility: visible; transform: translateY(0);}

.menubalk button:focus + ul {transition-delay: 0s;}

Alleen op apparaten waarvan de belangrijkste aanwijzer een nauwkeurige is, zoals een muis:

.menubalk button:hover + ul, .menubalk button:hover::after {transition-delay: 0.3s;}

Alle <ul>'s binnen de elementen met class="menubalk". Er is maar één element met class="menubalk": de <ul> waarbinnen vrijwel het hele menu zit, waaronder de zes <ul>'s, waarbinnen de submenu's zitten. Binnen elk van deze <ul>'s zit een serie <li>'s met links.

height: auto;

Voor smallere browservensters is eerder height: 0; opgegeven, omdat de <ul>'s anders voor enorme gaten tussen de onder elkaar staande <button>'s zorgen. Hier is dat niet meer nodig en wordt de hoogte auto: zo hoog als nodig is.

max-height: 80vh; overflow: auto;

Maximumhoogte. Bij een heel sterke vergroting kan in wat lagere browservensters een deel van de langere submenu's aan de onderkant wegvallen. Omdat het menu fixed is gepositioneerd, kan niet worden gescrold om de onderkant van de submenu's te bereiken. Daarom wordt hier aan de <ul>'s een maximumhoogte gegeven.

De eenheid vh is gebaseerd op de hoogte van het venster van de browser. 1 vh is 1% van de hoogte van het venster, en 80 vh is 80% van de hoogte. De <ul>'s met de submenu's worden hierdoor nooit hoger dan 80% van de hoogte van het venster, ongeacht de hoogte van het venster.

overflow: auto; zorgt dat de <ul>'s gescrold kunnen worden, als ze hoger zijn dan 80 vh. Afhankelijk van browser en besturingssysteem kan hierbij een verticale scrollbalk verschijnen.

overscroll-behavior-y: contain;

Als de <ul> wordt gescrold en deze boven‑ of onderaan is gekomen, wordt standaard de rest van de pagina gescrold. Vervelend, want dan moet je mogelijk opzoeken, waar de pagina stond.

Dit zorgt ervoor dat de pagina niet wordt gescrold, ook niet als het begin of eind van de <ul> is bereikt. Hier is dat alleen in verticale richting nodig, daarom wordt overscroll-behavior-y gebruikt: de 'y' staat voor de verticale richting.

margin-left: -1px; Randen van submenu en knoppen sluiten niet goed aan.

Elke <ul> binnen ul.menubalk zit in een <li>. Die <li>'s hebben bij .menubalk ul li een border gekregen. De <ul>'s staan tegen die border aan, waardoor ze 1 px te veel naar rechts staan. Rechts is dat geen probleem, omdat de <ul>'s niet breder worden dan de <li>.

Links echter sluiten de borders van het submenu in de <ul> en de <button> niet netjes op elkaar aan, zoals op de afbeelding is te zien: de border van de <ul> staat 1 px te veel naar rechts.

Door de <ul>'s 1 px naar links te verplaatsen wordt dat opgelost.

pointer-events: auto;

Eerder is voorouder ul.menubalk bij .menubalk met pointer-events: none; ongevoelig gemaakt voor klikken en aanraken. Daardoor zijn ook de nakomelingen, waaronder de links binnen deze <ul>'s, ongevoelig geworden, waardoor ze niet meer werken. Hier worden de <ul>'s en daarmee ook hun nakomelingen weer gevoelig gemaakt voor een aanraking of klik.

.menubalk li:first-of-type ul

Deze selector werkt alleen in browservensters minimaal 760 px breed. Voor andere vensters is de uitleg hieronder niet van belang.

Voor dit element is eerder css opgegeven. Deze wordt hier niet allemaal herhaald, omdat het nogal veel is. Hier wordt slechts een eerder bij menubalk ul opgegeven marge weggehaald

De <ul>'s binnen de eerste <li>'s binnen ul.menubalk. Dat is er maar één: de <ul> met het eerste submenu. Een uitgebreidere beschrijving van deze selector staat iets hierboven bij .menubalk li:first-of-type button. Het enige verschil: daar gaat het om een <button> binnen de eerste <li>, hier om een <ul>.

margin-left: 0;

Hier gelijk boven zijn de zes <ul>'s met submenu's 1 px naar links gezet, zodat de borders van de submenu's netjes aansluiten op de borders van de <button>'s. Hierdoor verdwijnt echter bij de eerste <ul> de border links. Daarom wordt deze geen px naar links verplaatst.

.menubalk li:last-of-type ul

Deze selector werkt alleen in browservensters minimaal 760 px breed. Voor andere vensters is de uitleg hieronder niet van belang.

Voor dit element is eerder css opgegeven. Deze wordt hier niet allemaal herhaald, omdat het nogal veel is. Hier wordt slechts een eerder bij .menubalk li:last-of-type ul opgegeven border aan de onderkant weggehaald.

De <ul>'s binnen de laatste <li>'s binnen ul.menubalk. Dat is er maar één: de <ul> met het laatste submenu. Een uitgebreidere beschrijving van deze selector staat bij .menubalk li:last-of-type ul.

border-bottom: none;

De eerder bij .menubalk ul li:last-of-type opgegeven border aan de onderkant levert hier een dubbele border op, omdat deze tegen de border van de onderste <li> komt te staan. Daarom wordt deze hier weggehaald.

.menubalk ul li

Deze selector werkt alleen in browservensters minimaal 760 px breed. Voor andere vensters is de uitleg hieronder niet van belang.

Voor deze elementen is eerder css opgegeven. Deze wordt binnen dit blokje herhaald in de volgorde, waarin deze in de stylesheet staat, zodat alles hier overzichtelijk bij elkaar staat. (Alleen wat binnen deze media query geldig is, wordt binnen dit blokje herhaald.)

.menubalk ul li {background: #e4e4e4; color: black; width: 9em; margin-top: -1px; border: black solid 1px;}

.menubalk ul li:last-of-type {margin-bottom: -1px;}

.menubalk li {flex-grow: 1;}

.menubalk: de elementen met class="menubalk. Dat is er maar één: de <ul> waarbinnen vrijwel het hele menu zit.

ul: de <ul>'s binnen .menubalk.

li: de <li>'s binnen deze <ul>'s. Dit zijn de <li>'s, waar de links van de submenu's in zitten.

width: auto;

Deze <li>'s hebben eerder bij .menubalk ul li een breedte van 9 em gekregen, waardoor ze iets smaller zijn dan de <button> waar ze bij horen. In bredere browservensters moeten ze even breed worden als die <button>'s, omdat al duidelijk genoeg is dat ze daar bij horen.

Hier wordt die breedte weggehaald, waardoor ze even breed worden als hun ouder, de <ul> waar ze in zitten. Een <ul> is een blok-element en wordt daardoor normaal genomen even breed als de ouder ervan. Deze <ul>'s zitten weer in een <li>. Die <li>'s zijn bij .menubalk li met flex-grow: 1; zo breed gemaakt, dat ze de menubalk precies vullen. Uiteindelijk krijgen de <li>'s diezelfde breedte.

Omdat de <button>'s bij .menubalk button met width: auto; ook die breedte hebben gekregen, is elke <button> precies even breed als het bijbehorende submenu.

.menubalk ul li:last-of-type

Deze selector werkt alleen in browservensters minimaal 760 px breed. Voor andere vensters is de uitleg hieronder niet van belang.

Voor deze elementen is eerder css opgegeven. Deze wordt binnen dit blokje herhaald in de volgorde, waarin deze in de stylesheet staat, zodat alles hier overzichtelijk bij elkaar staat. (Alleen wat binnen deze media query geldig is, wordt binnen dit blokje herhaald.)

.menubalk ul li {background: #e4e4e4; color: black; width: 9em; margin-top: -1px; border: black solid 1px;}

.menubalk ul li:last-of-type {margin-bottom: -1px;}

.menubalk li {flex-grow: 1;}

.menubalk ul li {width: auto;}

Elke laatste <li> binnen een <ul> binnen ul.menubalk. Hierin zit de laatste link van elk submenu. Een uitgebreidere beschrijving van deze selector is te vinden bij .menubalk li:last-of-type ul.

margin-bottom: 0;

Als de submenu's te hoog worden voor het browservenster, is iets hierboven bij .menubalk ul voor bredere vensters opgegeven, dat gescrold kan worden. Afhankelijk van browser en besturingssysteem kan hierbij een verticale scrollbalk verschijnen.

Om dubbele borders te voorkomen is bij de laatste link van elk submenu eerder bij .menubalk ul li:last-of-type margin-bottom: -1px; opgegeven.

Deze combinatie levert in bredere browservensters problemen op, want de <ul> wordt nu 1 px te laag voor de erin zittende <li>'s. Waardoor soms een verticale scrollbalk verschijnt. Door de negatieve marge weg te halen wordt dit opgelost.

main

Deze selector werkt alleen in browservensters minimaal 760 px breed. Voor andere vensters is de uitleg hieronder niet van belang.

Voor dit element is eerder css opgegeven. Deze wordt binnen dit blokje herhaald in de volgorde, waarin deze in de stylesheet staat, zodat alles hier overzichtelijk bij elkaar staat. (Alleen wat binnen deze media query geldig is, wordt binnen dit blokje herhaald.)

main {background: white; color: black; width: 90vw; max-width: 758px; margin: 4em auto 0; border: black solid 1px; padding: 0 10px 10px;}

Hierbinnen staat de belangrijkste inhoud van de pagina (in dit voorbeeld is dat alleen een titel en Latijnse flauwekul tekst).

margin-top: 5em;

De eerder opgegeven marge aan de bovenkant van <main> iets vergroten, zodat de tekst iets onder de menubalk staat.

Als eenheid wordt de relatieve eenheid em gebruikt, zodat de marge mee verandert met de lettergrootte. Hierdoor blijft de tekst in <main> altijd onder de menubalk staan, ook bij een grotere letter. Bij gebruik van een absolute eenheid zoals px verandert de marge niet mee met de lettergrootte. Zoomen kan wel altijd, ongeacht welke eenheid voor de hoogte wordt gebruikt.

css voor als JavaScript wordt uitgevoerd

In dit voorbeeld worden nergens id's gebruikt, maar alleen classes. Als JavaScript wordt uitgevoerd, wordt aan een aantal elementen een id toegevoegd. Een selector met een id heeft altijd meer specificiteit, meer 'gewicht', dan een selector zonder id. Ongeacht het aantal elementen, classes, enzovoort dat in die selector staat. Hierdoor overrulen de hieronder staande selectors met een id áltijd de eerdere selectors met alleen elementen of classes.

De css is voor een groot deel hetzelfde als bij de eerdere selectors, vaak verschillen alleen de selectors. Normaal genomen zou je de hieronder staande selectors daarom vaak kunnen combineren met de selectors hierboven. Maar omdat dit een voorbeeld is, zijn de selectors met een id (die dus alleen werken als JavaScript wordt uitgevoerd) hieronder bij elkaar gezet. Dat is overzichtelijker.

(Als je deze veranderingen wilt zien, moet je niet naar de gewone code, maar naar de Gegenereerde code kijken.)

#skippy-js

Deze selector werkt alleen met JavaScript, omdat het script de id 'skippy-js' toevoegt.

Voor dit element is eerder css opgegeven. Deze wordt binnen dit blokje herhaald in de volgorde, waarin deze in de stylesheet staat, zodat alles hier overzichtelijk bij elkaar staat.

.skippy {background: white; width: 10.5em; height: 4.8em; font-size: 1.2em; line-height: 2.4em; text-align: center; border: red solid 3px; position: fixed; top: 20px; left: -20000px; z-index: 250;}

.skippy:focus {left: 50%; transform: translateX(-50%);}

Het element met class="skippy". Dat is er maar één: een skip-link, waarmee in één keer het hele menu gepasseerd kan worden.

(De id 'skippy' wordt door JavaScript toegevoegd. Om deze toegevoegde id te zien moet je niet de gewone code, maar de Gegenereerde code bekijken.)

display: none;

Als JavaScript wordt uitgevoerd, moet je er in smallere browservensters voor kiezen om de menubalk met de zes <button>'s met submenu's te tonen. Als je daar niet voor kiest, is er geen menu om in één keer te passeren. Daarom is een skip-link hier overbodig. Voor bredere vensters, waar de menubalk altijd wordt getoond, wordt de skip-link later weer ingeschakeld.

#voor-smal-js[aria-expanded="false"]::after

Deze selector werkt alleen met JavaScript, omdat het script de id 'voor-smal-js' toevoegt.

Voor dit element is eerder css opgegeven. Deze wordt binnen dit blokje herhaald in de volgorde, waarin deze in de stylesheet staat, zodat alles hier overzichtelijk bij elkaar staat.

.voor-smal::after {background: white; color: black; content: "openen"; box-sizing: border-box; display: inline-block; width: 4.8em; line-height: 2.6em; border-top: white solid 3px; border-right: black solid 1px; border-bottom: white solid 3px;}

.voor-smal:focus-visible::after, .voor-smal:focus-visible span:first-child {border-color: blue; border-width: 3px;}

nav:focus-within .voor-smal::after {content: "sluiten";}

Alleen op apparaten, waarvan de belangrijkste aanwijzer een onnauwkeurige is, zoals een vinger:

nav:hover .voor-smal::after {content: "sluiten";}

#voor-smal-js: het element met id="voor-smal-js". Dat is er maar één: een <button> voor smallere browservensters, waarmee de menubalk met de zes <button>'s met submenu's verborgen of getoond kan worden. In bredere vensters is voldoende ruimte voor een menubalk, waarin die zes <button>'s altijd getoond worden.

[aria-expanded="false"]: dit is een attribuut-selector: wat tussen de [] staat, moet precies zo aanwezig zijn. Als dat niet zo is, geldt de selector niet. De waarde 'false' geeft hier aan, dat de menubalk met de zes <button>'s met de submenu's niet zichtbaar is.

::after: met behulp van ::after wordt bij het element met id="voor-smal-js" een pseudo-element gemaakt. Dit pseudo-element wordt gebruikt om achter het woordje 'Menu' het woordje 'openen' of 'sluiten' te zetten.

De hele selector in gewone taal: doe iets met het met behulp van ::after bij button#voor-smal-js gemaakte pseudo-element, maar alleen als bij button#voor-smal-js het attribuut aria-expanded="false" aanwezig is.

(De id 'voor-smal-js' en de WAI-ARIA-code aria-expanded="false" worden door JavaScript toegevoegd. Om deze toegevoegde id en code te zien moet je niet de gewone code, maar de Gegenereerde code bekijken.)

content: "openen";

Als het attribuut aria-expanded="false" bij button#voor-smal-js aanwezig is, is de menubalk met de zes <button>'s met de submenu's niet zichtbaar. Achter het woordje 'Menu' moet dan het woordje 'openen' staan.

#voor-smal-js[aria-expanded="false"] ~ .menubalk

Deze selector werkt alleen met JavaScript, omdat het script de id 'voor-smal-js' toevoegt.

Voor dit element is eerder css opgegeven. Deze wordt binnen dit blokje herhaald in de volgorde, waarin deze in de stylesheet staat, zodat alles hier overzichtelijk bij elkaar staat.

.menubalk {display: none; flex-direction: column; max-height: 80vh; overflow: auto; overscroll-behavior-y: contain; position: fixed; top: 3.2rem;}

nav:focus-within .menubalk {display: flex;}

Alleen op apparaten, waarvan de belangrijkste aanwijzer een onnauwkeurige is, zoals een vinger:

nav:hover .menubalk {display: flex;}

Weer op alle apparaten, alleen in browservensters minimaal 760 px breed:

.menubalk {display: flex; flex-direction: row; width: 90vw; margin: 20px auto 0; pointer-events: none; top: 0; right: 0; left: 0;}

#voor-smal-js: het element met id="voor-smal-js". Dat is er maar één: een <button> voor smallere browservensters, waarmee de menubalk met de zes <button>'s met submenu's verborgen of getoond kan worden. In bredere vensters is voldoende ruimte voor een menubalk, waarin die zes <button>'s altijd getoond worden.

[aria-expanded="false"]: dit is een attribuut-selector: wat tussen de [] staat, moet precies zo aanwezig zijn. Als dat niet zo is, geldt de selector niet. De waarde 'false' geeft hier aan, dat de menubalk met de zes <button>'s met de submenu's niet zichtbaar is.

~: het hierachter staande element moet ergens in de html staan, na het hiervoor staande element. Het hoeft er, anders dan bij de +, niet gelijk op te volgen, als het maar ergens na het eerste element in de html staat.

De enige voorwaarde is verder dat het voor en het na de ~ staande element dezelfde ouder hebben. Dat is hier het geval: button#voor-smal-js voor de ~ en .menubalk na de ~ hebben beide als ouder de <nav>, waar het hele menu in zit.

.menubalk: de elementen met class="menubalk. Dat is er maar één: de <ul> waarbinnen vrijwel het hele menu zit.

De hele selector in gewone taal: doe iets met de elementen met class="menubalk" die in de html ergens op het element met id="voor-smal-js" volgen, maar alleen als bij button#voor-smal-js het attribuut aria-expanded="false" aanwezig is. Hier is dat element alleen de menubalk met de zes <button>'s met submenu's.

(De id 'voor-smal-js' en de WAI-ARIA-code aria-expanded="false" worden door JavaScript toegevoegd. Om deze toegevoegde id en code te zien moet je niet de gewone code, maar de Gegenereerde code bekijken.)

display: none;

Als het attribuut aria-expanded="false" aanwezig is bij button#voor-smal-js, moet ul.menubalk met de zes <button>'s met de submenu's niet zichtbaar zijn.

Omdat deze selector een id heeft, heeft deze altijd meer specificiteit (meer 'gewicht') dan eerdere selectors met alleen een of meer classes en/of elementen. Hierdoor hebben pseudo-classes als :hover en :focus uit eerdere selectors geen effect meer: ul.menubalk wordt met deze regel altijd verborgen, hoeveel je ook hovert of focust.

Dit maakt het mogelijk het tonen en verbergen van de menubalk volledig te regelen met behulp van de WAI-ARIA-code aria-expanded.

#voor-smal-js span:last-child

Deze selector werkt alleen met JavaScript, omdat het script de id 'voor-smal-js' toevoegt.

Voor dit element is eerder css opgegeven. Deze wordt binnen dit blokje herhaald in de volgorde, waarin deze in de stylesheet staat, zodat alles hier overzichtelijk bij elkaar staat.

.voor-smal span:last-child {display: inline-block; width: 34px; height: 34px; font-size: 1.2em; line-height: 30px; padding-left: 5px; pointer-events: none; position: absolute; left: 5.3em; transform: rotate(270deg);}

nav:focus-within .voor-smal span:last-child {line-height: 24px; transform: rotate(90deg) translate(18px, 4px);}

Alleen op apparaten, waarvan de belangrijkste aanwijzer een onnauwkeurige is, zoals een vinger:

nav:hover .voor-smal span:last-child {line-height: 24px; transform: rotate(90deg) translate(18px, 4px);}

#voor-smal-js: het element met id="voor-smal-js". Dat is er maar één: een <button> voor smallere browservensters, waarmee de menubalk met de zes <button>'s met submenu's verborgen of getoond kan worden. In bredere vensters is voldoende ruimte voor een menubalk, waarin die zes <button>'s altijd getoond worden.

span: alle <span>'s binnen button#voor-smal-js.

:last-child: alleen de <span>'s die een laatste kind zijn. Binnen deze <span> zit het teken '«'. Een langere beschrijving van :last-child staat hierboven bij .voor-smal span:first-child. Het enige verschil: daar gaat het om het eerste kind, hier om het laatste kind.

De hele selector in gewone taal: doe iets met de <span>'s in het element met id="voor-smal-js", maar alleen met de <span>'s die een laatste kind zijn. Dit is de <span> waarin het teken '«' zit, dat in smallere browservensters achter 'Menu openen' of 'Menu sluiten' staat.

(De id 'voor-smal-js' wordt door JavaScript toegevoegd. Om deze toegevoegde id te zien moet je niet de gewone code, maar de Gegenereerde code bekijken.)

line-height: 30px; transform: rotate(270deg);

Regelhoogte 30 px. 270° met de klok meedraaien, waardoor de punten van het teken '«' naar beneden komen te staan.

Dit zijn de regelhoogte en de draaiing die nodig zijn, als de menubalk met de zes <button>'s met submenu's niet wordt getoond.

Omdat deze selector een id heeft, heeft deze altijd meer specificiteit (meer 'gewicht') dan eerdere selectors met alleen een of meer classes en/of elementen. Hierdoor hebben pseudo-classes als :hover en :focus uit eerdere selectors geen effect meer: de regelhoogte is altijd 30 px en de draaiing altijd 270°, hoeveel je ook hovert of focust.

Dit maakt het mogelijk de regelhoogte en de draaiing van de '«' volledig te regelen met behulp van de WAI-ARIA-code aria-expanded.

#voor-smal-js[aria-expanded="true"]::after

Deze selector werkt alleen met JavaScript, omdat het script de id 'voor-smal-js' toevoegt.

Voor dit element is eerder css opgegeven. Deze wordt binnen dit blokje herhaald in de volgorde, waarin deze in de stylesheet staat, zodat alles hier overzichtelijk bij elkaar staat.

.voor-smal::after {background: white; color: black; content: "openen"; box-sizing: border-box; display: inline-block; width: 4.8em; line-height: 2.6em; border-top: white solid 3px; border-right: black solid 1px; border-bottom: white solid 3px;}

.voor-smal:focus-visible::after, .voor-smal:focus-visible span:first-child {border-color: blue; border-width: 3px;}

nav:focus-within .voor-smal::after {content: "sluiten";}

Alleen op apparaten, waarvan de belangrijkste aanwijzer een onnauwkeurige is, zoals een vinger:

nav:hover .voor-smal::after {content: "sluiten";}

Weer op alle apparaten:

#voor-smal-js[aria-expanded=false]::after {content: "sluiten"}

#voor-smal-js: het element met id="voor-smal-js". Dat is er maar één: een <button> voor smallere browservensters, waarmee de menubalk met de zes <button>'s met submenu's verborgen of getoond kan worden. In bredere vensters is voldoende ruimte voor een menubalk, waarin die zes <button>'s altijd getoond worden.

[aria-expanded="true"]: dit is een attribuut-selector: wat tussen de [] staat, moet precies zo aanwezig zijn. Als dat niet zo is, geldt de selector niet. De waarde 'true' geeft hier aan dat de zes <button>'s met de submenu's zichtbaar zijn.

::after: het bij .voor-smal::after gemaakte pseudo-element, waarin 'openen' of 'sluiten' staat.

De hele selector in gewone taal: doe iets met het met behulp van ::after bij button#voor-smal-js gemaakte pseudo-element, maar alleen als bij button#voor-smal-js het attribuut aria-expanded="true" aanwezig is.

(De id 'voor-smal-js' en de WAI-ARIA-code aria-expanded="true" worden door JavaScript toegevoegd. Om deze toegevoegde id en code te zien moet je niet de gewone code, maar de Gegenereerde code bekijken.)

content: "sluiten";

Als het attribuut aria-expanded="true" bij button#voor-smal-js aanwezig is, is de menubalk met de zes <button>'s met de submenu's zichtbaar. Achter het woordje 'Menu' moet dan het woordje 'sluiten' staan.

#voor-smal-js[aria-expanded="true"] ~ .menubalk

Deze selector werkt alleen met JavaScript, omdat het script de id 'voor-smal-js' toevoegt.

Voor dit element is eerder css opgegeven. Deze wordt binnen dit blokje herhaald in de volgorde, waarin deze in de stylesheet staat, zodat alles hier overzichtelijk bij elkaar staat.

.menubalk {display: none; flex-direction: column; max-height: 80vh; overflow: auto; overscroll-behavior-y: contain; position: fixed; top: 3.2rem;}

nav:focus-within .menubalk {display: flex;}

Alleen op apparaten, waarvan de belangrijkste aanwijzer een onnauwkeurige is, zoals een vinger:

nav:hover .menubalk {display: flex;}

Weer op alle apparaten, alleen in browservensters minimaal 760 px breed:

.menubalk {display: flex; flex-direction: row; width: 90vw; margin: 20px auto 0; pointer-events: none; top: 0; right: 0; left: 0;}

Weer in alle browservensters:

#voor-smal-js[aria-expanded="false"] ~ .menubalk {display: none;}

#voor-smal-js: het element met id="voor-smal-js". Dat is er maar één: een <button> voor smallere browservensters, waarmee de menubalk met de zes <button>'s met submenu's verborgen of getoond kan worden. In bredere vensters is voldoende ruimte voor een menubalk, waarin die zes <button>'s altijd getoond worden.

[aria-expanded="true"]: dit is een attribuut-selector: wat tussen de [] staat, moet precies zo aanwezig zijn. Als dat niet zo is, geldt de selector niet. De waarde 'true' geeft hier aan, dat de menubalk met de zes <button>'s met de submenu's zichtbaar is.

~: het hierachter staande element moet ergens in de html staan, na het hiervoor staande element. Het hoeft er, anders dan bij de +, niet gelijk op te volgen, als het maar ergens na het eerste element in de html staat.

De enige voorwaarde is verder dat het voor en het na de ~ staande element dezelfde ouder hebben. Dat is hier het geval: button#voor-smal-js voor de ~ en ul.menubalk na de ~ hebben beide als ouder de <nav>, waar het hele menu in zit.

.menubalk: de elementen met class="menubalk. Dat is er maar één: de <ul> waarbinnen vrijwel het hele menu zit.

De hele selector in gewone taal: doe iets met de elementen met class="menubalk" die in de html ergens op het element met id="voor-smal-js" volgen, maar alleen als bij button#voor-smal-js het attribuut aria-expanded="true" aanwezig is. Dat is hier dus alleen maar de menubalk met de zes <button>'s met submenu's.

(De id 'voor-smal-js' en de WAI-ARIA-code aria-expanded="true" worden door JavaScript toegevoegd. Om deze toegevoegde id en code te zien moet je niet de gewone code, maar de Gegenereerde code bekijken.)

display: flex;

Omdat in smallere browservensters geen ruimte is voor een menubalk met zes <button>'s, is deze eerder bij #voor-smal-js[aria-expanded="false"] ~ .menubalk met display: none; verborgen. Als button#voor-smal-js het attribuut aria-expanded="true" heeft, moet de menubalk worden getoond.

Dat zichtbaar maken gebeurt hier met display: flex; waarmee ul.menubalk in een zogenaamde 'flex container' verandert. Dit maakt het veel makkelijker om de directe kinderen van dit element, de 'flex items', op een bepaalde plaats neer te zetten. De directe kinderen van de menubalk zijn hier zes <li>'s. In elk van die <li>'s zit weer een <button> met bijbehorend submenu.

#voor-smal-js[aria-expanded="true"] span:last-child

Deze selector werkt alleen met JavaScript, omdat het script de id 'voor-smal-js' toevoegt.

Voor dit element is eerder css opgegeven. Deze wordt binnen dit blokje herhaald in de volgorde, waarin deze in de stylesheet staat, zodat alles hier overzichtelijk bij elkaar staat.

.voor-smal span:last-child {display: inline-block; width: 34px; height: 34px; font-size: 1.2em; line-height: 30px; padding-left: 5px; pointer-events: none; position: absolute; left: 5.3em; transform: rotate(270deg);}

nav:focus-within .voor-smal span:last-child {line-height: 24px; transform: rotate(90deg) translate(18px, 4px);}

Alleen op apparaten, waarvan de belangrijkste aanwijzer een onnauwkeurige is, zoals een vinger:

nav:hover .voor-smal span:last-child {line-height: 24px; transform: rotate(90deg) translate(18px, 4px);}

Weer op alle apparaten:

#voor-smal-js span:last-child {line-height: 30px; transform: rotate(270deg);}

#voor-smal-js: het element met id="voor-smal-js". Dat is er maar één: een <button> voor smallere browservensters, waarmee de zes menubalk met de <button>'s met submenu's verborgen of getoond kan worden. In bredere vensters is voldoende ruimte voor een menubalk, waarin die zes <button>'s altijd getoond worden.

[aria-expanded="true"]: dit is een attribuut-selector: wat tussen de [] staat, moet precies zo aanwezig zijn. Als dat niet zo is, geldt de selector niet. De waarde 'true' geeft hier aan, dat de menubalk met de zes <button>'s met de submenu's zichtbaar is.

:last-child: alleen de <span>'s die een laatste kind zijn. Binnen deze <span> zit het teken '«'. Een langere beschrijving van :last-child staat eerder bij .voor-smal span:first-child. Het enige verschil: daar gaat het om het eerste kind, hier om het laatste kind.

De hele selector in gewone taal: doe iets met de <span>'s in het element met id="voor-smal-js", maar alleen met de <span>'s die een laatste kind zijn, en alleen als button#voor-smal-js het attribuut aria-expanded="true" heeft. Dit is de <span> waarin het teken '«' zit, dat in smallere browservensters achter 'Menu openen' of 'Menu sluiten' staat.

(De id 'voor-smal-js' en de WAI-ARIA-code aria-expanded="true" worden door JavaScript toegevoegd. Om deze toegevoegde id en code te zien moet je niet de gewone code, maar de Gegenereerde code bekijken.)

line-height: 24px; transform: rotate(90deg) translate(18px, 4px);

Als button#voor-smal-js het attribuut aria-expanded="true" heeft, wordt de menubalk met de zes <button>'s met submenu's getoond. Het teken '«' moet dan met de punten naar boven worden gedraaid. Tevens is een correctie nodig om het teken op de juiste plaats te houden.

Dit zijn precies dezelfde correcties en draaiing als bij nav:focus-within .voor-smal span:last-child, waar een uitgebreidere beschrijving hiervan staat.

#menubalk-js ul

Deze selector werkt alleen met JavaScript, omdat het script de id 'menubalk-js' toevoegt.

Voor deze elementen is eerder css opgegeven. Deze wordt binnen dit blokje herhaald in de volgorde, waarin deze in de stylesheet staat, zodat alles hier overzichtelijk bij elkaar staat.

ul {list-style-type: none; margin: 0; padding: 0;}

.menubalk li:last-of-type ul {border-bottom: black solid 1px;}

.menubalk ul {height: 0; visibility: hidden; transform: translateY(-2000px); transition-delay: 0.1s;}

.menubalk button:hover + ul, .menubalk button:focus +ul, .menubalk ul:hover, .menubalk button + ul:focus-within {height: auto; visibility: visible; transform: translateY(0);}

.menubalk button:focus + ul {transition-delay: 0s;)}

Alleen op apparaten waarvan de belangrijkste aanwijzer een nauwkeurige is, zoals een muis:

.menubalk button:hover + ul, .menubalk button:hover::after {transition-delay: 0.3s;}

Weer op alle apparaten, alleen in browservensters minimaal 760 px breed:

.menubalk ul {height: auto; max-height: 80vh; overflow: auto; overscroll-behavior-y: contain; margin-left: ‑1px; pointer-events: auto;}

.menubalk li:first-of-type ul {margin-left: 0;}

.menubalk li:last-of-type ul {border-bottom: none;}

#menubalk-js: het element met id="menubalk-js". De <ul> waarbinnen vrijwel het hele menu zit.

ul: de <ul>'s binnen ul#menubalk-js. Dit zijn de zes <ul>'s, waarbinnen de submenu's zitten.

(De id 'menubalk-js' wordt door JavaScript toegevoegd. Om deze toegevoegde id te zien moet je niet de gewone code, maar de Gegenereerde code bekijken.)

visibility: hidden; transform: translateY(-2000px);

De <ul>'s met de submenu's verbergen door ze onzichtbaar te maken en boven het scherm te zetten. Waarom ze op deze manier worden verborgen en niet met iets simpeler als display: none;, is te vinden bij .menubalk ul.

Omdat deze selector een id heeft, heeft deze altijd meer specificiteit (meer 'gewicht') dan eerdere selectors met alleen een of meer classes en/of elementen. Hierdoor hebben pseudo-classes als :hover en :focus uit eerdere selectors geen effect meer: de <ul>'s worden altijd verborgen, hoeveel je ook hovert of focust.

Dit maakt het mogelijk het tonen en verbergen van de <ul>'s vrijwel volledig te regelen met behulp van de WAI-ARIA-code aria-expanded. Vrijwel, want voor :hover wordt later een selector zonder aria-expanded gebruikt.

#menubalk-js button::after

Deze selector werkt alleen met JavaScript, omdat het script de id 'menubalk-js' toevoegt.

Voor deze elementen is eerder css opgegeven. Deze wordt binnen dit blokje herhaald in de volgorde, waarin deze in de stylesheet staat, zodat alles hier overzichtelijk bij elkaar staat.

.menubalk button::after {content: "«"; display: inline-block; width: 20px; font-size: 1.2em; text-align: center; margin-left: 20px; transform: rotate(270deg) translate(-2px, -4px);}

Alleen in browsers die content: "«" / ""; ondersteunen:

.menubalk button::after {content: "«" / "";}

Weer in alle browsers:

.menubalk button:hover::after, .menubalk li:focus-within button::after {transform: rotate(90deg) translate(2px, 0);}

.menubalk li:has(ul:hover) button::after {transform: rotate(90deg) translate(2px, 0);}

Alleen op apparaten waarvan de belangrijkste aanwijzer een nauwkeurige is, zoals een muis:

.menubalk button:hover + ul, .menubalk button:hover::after {transition-delay: 0.3s;}

Alle <button>'s binnen het element met id="menubalk". Dit zijn de zes <button>'s met de submenu's. Met behulp van ::after wordt bij elk van die <button>'s een pseudo-element gemaakt. Dit pseudo-element wordt gebruikt om het teken '«' weer te geven.

(De id 'menubalk-js' wordt door JavaScript toegevoegd. Om deze toegevoegde id te zien moet je niet de gewone code, maar de Gegenereerde code bekijken.)

transform: rotate(270deg) translate(-2px, -4px);

Als een submenu wordt getoond, moeten de punten van het teken '«' naar boven worden gedraaid. Bovendien is een kleine correctie van de positie nodig. Hier worden de punten naar beneden gedraaid en die kleine correctie wordt ongedaan gemaakt: het teken '«' staat weer net zo, als wanneer de submenu's zijn gesloten.

Omdat deze selector een id heeft, heeft deze altijd meer specificiteit (meer 'gewicht') dan eerdere selectors met alleen een of meer classes en/of elementen. Hierdoor hebben pseudo-classes als :hover en :focus uit eerdere selectors geen effect meer: punten van het teken '«' staan altijd naar beneden, hoeveel je ook hovert of focust.

Dit maakt het mogelijk de richting van de punten vrijwel volledig te regelen met behulp van de WAI-ARIA-code aria-expanded. Vrijwel, want voor :hover wordt later een selector zonder aria-expanded gebruikt.

@media (pointer: fine)

De css die binnen deze 'media query' staat, geldt alleen voor apparaten, waarvan de belangrijkste aanwijzer een nauwkeurige is, zoals een muis. Deze media query is precies hetzelfde als de eerdere bij @media (pointer: fine), waar een beschrijving staat.

De css binnen deze media query zorgt ervoor dat een submenu wordt getoond bij hoveren over een <button> in de menubalk. Dat werkt prima met een muis, maar niet op een touchscreen. :hover op een touchscreen wordt afgehandeld alsof het een aanraking is.

Daardoor kan het submenu niet worden gesloten door de <button> nogmaals aan te raken, want dat wordt nog steeds behandeld als een aanraking. Daarom wordt het tonen met behulp van :hover beperkt tot apparaten, waarbij de belangrijkste aanwijzer een nauwkeurige is, zoals een muis. Touchscreens negeren de css binnen deze media query.

#menubalk-js button:hover + ul, #menubalk-js ul:hover

Deze selector werkt alleen met JavaScript, omdat het script de id 'menubalk-js' toevoegt.

Deze selector werkt alleen op apparaten, waarvan de belangrijkste aanwijzer een nauwkeurige is, zoals een muis. Voor apparaten is de uitleg hieronder niet van belang.

Voor deze elementen is eerder css opgegeven. Deze wordt binnen dit blokje herhaald in de volgorde, waarin deze in de stylesheet staat, zodat alles hier overzichtelijk bij elkaar staat.

ul {list-style-type: none; margin: 0; padding: 0;}

.menubalk li:last-of-type ul {border-bottom: black solid 1px;}

.menubalk ul {height: 0; visibility: hidden; transform: translateY(-2000px); transition-delay: 0.1s;}

.menubalk button:hover + ul, .menubalk button:focus +ul, .menubalk ul:hover, .menubalk button + ul:focus-within {height: auto; visibility: visible; transform: translateY(0);}

.menubalk button:focus + ul {transition-delay: 0s;}

Alleen op apparaten, waarvan de belangrijkste aanwijzer een nauwkeurige is, zoals een muis:

.menubalk button:hover + ul, .menubalk button:hover::after {transition-delay: 0.3s;}

Weer op alle apparaten, alleen in browservensters minimaal 760 px breed:

.menubalk ul {height: auto; max-height: 80vh; overflow: auto; overscroll-behavior-y: contain; margin-left: ‑1px; pointer-events: auto;}

.menubalk li:first-of-type ul {margin-left: 0;}

.menubalk li:last-of-type ul {border-bottom: none;}

Weer in alle browservensters:

#menubalk-js ul {visibility: hidden; transform: translateY(-2000px);}

Dit zijn twee selectors, gescheiden door een komma.

De eerste selector #menubalk-js button:hover + ul, voor de komma:

#menubalk-js: het element met id="menubalk-js": de <ul> waarbinnen vrijwel het hele menu zit.

button: de <button>'s binnen ul#menubalk-js. Dit zijn de zes <button>'s met submenu's.

:hover: maar alleen als er over een <button> wordt gehoverd.

+: het element achter de + moet in de html direct volgen op het element voor de +. In dit geval gaat het om de <ul> die gelijk op de <button> volgt. Beide elementen moeten ook nog dezelfde ouder hebben. Dat is hier het geval, ze hebben beide als ouder dezelfde <li>.

ul: de <ul> die gelijk op de <button> volgt. Dit is de <ul> met het submenu bij die <button>.

De eerste selector in gewone taal: doe iets met de <ul> die gelijk op een <button> binnen ul#menubalk-js volgt, maar alleen als over die <button> wordt gehoverd.

(De id 'menubalk-js' wordt door JavaScript toegevoegd. Om deze toegevoegde id te zien moet je niet de gewone code, maar de Gegenereerde code bekijken.)

De tweede selector #menubalk-js ul:hover, na de komma:

#menubalk-js: het element met id="menubalk-js": de <ul> waarbinnen vrijwel het hele menu zit.

ul: de <ul>'s binnen ul#menubalk-js. De zes <ul>'s met de submenu's.

:hover: maar alleen als er over een <ul> wordt gehoverd.

De tweede selector in gewone taal: doe iets met de <ul>'s binnen ul#menubalk-js, maar alleen als over een <ul> wordt gehoverd.

(De id 'menubalk-js' wordt door JavaScript toegevoegd. Om deze toegevoegde id te zien moet je niet de gewone code, maar de Gegenereerde code bekijken.)

Hieronder wordt een submenu zichtbaar gemaakt, als over een van de zes <button>'s wordt gehoverd. Daarvoor zorgt de eerste selector. Maar zodra je niet meer over de <button> hovert, maar over het geopende submenu, sluit het submenu weer. Waardoor je dus nooit op een van die prachtige links kunt klikken. De tweede selector voorkomt dat: ook als je over de <ul> met het submenu hovert, blijft het submenu zichtbaar.

visibility: visible; transform: translateY(0);

Eerder zijn bij .menubalk ul de <ul>'s met de submenu's verborgen met visibility: hidden; en transform: translateY(-2000px);. Hier wordt één van die <ul>'s, en daarmee de erin zittende links, zichtbaar gemaakt.

Waarom de <ul>'s op deze manier zijn verborgen, staat beschreven bij .menubalk ul.

#menubalk-js button:hover::after

Deze selector werkt alleen met JavaScript, omdat het script de id 'menubalk-js' toevoegt.

Deze selector werkt alleen op apparaten, waarvan de belangrijkste aanwijzer een nauwkeurige is, zoals een muis. Voor andere apparaten is de uitleg hieronder niet van belang.

Voor deze elementen is eerder css opgegeven. Deze wordt binnen dit blokje herhaald in de volgorde, waarin deze in de stylesheet staat, zodat alles hier overzichtelijk bij elkaar staat.

.menubalk button::after {content: "«"; display: inline-block; width: 20px; font-size: 1.2em; text-align: center; margin-left: 20px; transform: rotate(270deg) translate(-2px, -4px);}

Alleen in browsers die content: "«" / ""; ondersteunen:

.menubalk button::after {content: "«" / "";}

Weer in alle browsers:

.menubalk button:hover::after, .menubalk li:focus-within button::after {transform: rotate(90deg) translate(2px, 0);}

.menubalk li:has(ul:hover) button::after {transform: rotate(90deg) translate(2px, 0);}

Alleen op apparaten waarvan de belangrijkste aanwijzer een nauwkeurige is, zoals een muis:

.menubalk button:hover + ul, .menubalk button:hover::after {transition-delay: 0.3s;}

Weer op alle apparaten:

#menubalk-js button::after {transform: rotate(270deg) translate(-2px, -4px);}

#menubalk-js: het element met id="menubalk-js": de <ul> waarbinnen vrijwel het hele menu zit.

button: de <button>'s binnen ul#menubalk-js. De zes <button>'s met de submenu's.

:hover: maar alleen als er over een <button> wordt gehoverd.

::after: met behulp van ::after wordt bij elk van die <button>'s een pseudo-element gemaakt. Dit pseudo-element wordt gebruikt om het teken '«' weer te geven.

De hele selector in gewone taal: doe iets met het met behulp van ::after gemaakte pseudo-element, maar alleen als over een in ul#menubalk-js zittende <button> wordt gehoverd.

(De id 'menubalk-js' wordt door JavaScript toegevoegd. Om deze toegevoegde id te zien moet je niet de gewone code, maar de Gegenereerde code bekijken.)

transform: rotate(90deg) translate(2px, 0);

Met behulp van transform kan een element worden gemanipuleerd. Met behulp van de bij transform horende functie rotate() kun je een element draaien. rotate(90deg) draait het element, in dit geval de '«', 90° met de klok mee. Hierdoor worden de punten naar boven gericht.

Om het teken op de juiste plaats te houden, blijkt ook nog een kleine aanpassing met behulp van de bij transform horende functie translate() nodig. Deze functie verplaatst een element ten opzichte van zichzelf.

Er staan twee waarden. De eerste waarde 2px voor de komma verplaatst 2 px in horizontale richting. Naar rechts, omdat de waarde positief is. De tweede waarde 0 na de komma verplaatst in verticale richting. Omdat hier 0 staat, verwijdert deze alleen maar de eerder bij .menubalk button::after opgegeven verticale verplaatsing van -4px.

(Horizontaal, verticaal en rechts moeten hier niet letterlijk worden genomen. Omdat het pseudo-element 90° is gedraaid, veranderen horizontaal, verticaal en rechts ook van richting.)

#menubalk-js li:has(ul:hover) button::after

Deze selector werkt alleen met JavaScript, omdat het script de id 'menubalk-js' toevoegt.

Deze selector werkt alleen op apparaten, waarvan de belangrijkste aanwijzer een nauwkeurige is, zoals een muis. Voor apparaten is de uitleg hieronder niet van belang.

Voor deze elementen is eerder css opgegeven. Deze wordt binnen dit blokje herhaald in de volgorde, waarin deze in de stylesheet staat, zodat alles hier overzichtelijk bij elkaar staat.

.menubalk button::after {content: "«"; display: inline-block; width: 20px; font-size: 1.2em; text-align: center; margin-left: 20px; transform: rotate(270deg) translate(-2px, -4px);}

Alleen in browsers die content: "«" / ""; ondersteunen:

.menubalk button::after {content: "«" / "";}

Weer in alle browsers:

.menubalk button:hover::after, .menubalk li:focus-within button::after {transform: rotate(90deg) translate(2px, 0);}

.menubalk li:has(ul:hover) button::after {transform: rotate(90deg) translate(2px, 0);}

Alleen op apparaten waarvan de belangrijkste aanwijzer een nauwkeurige is, zoals een muis:

.menubalk button:hover + ul, .menubalk button:hover::after {transition-delay: 0.3s;}

Weer op alle apparaten:

#menubalk-js button::after {transform: rotate(270deg) translate(-2px, -4px);}

Alleen op apparaten waarvan de belangrijkste aanwijzer een nauwkeurige is, zoals een muis:

#menubalk-js button:hover::after {transform: rotate(90deg) translate(2px, 0);}

#menubalk-js: het element met id="menubalk-js": de <ul> waarbinnen vrijwel het hele menu zit.

li: alle <li>'s binnen ul#menubalk-js.

:has(): maar alleen als die <li>'s voldoen aan de voorwaarde die tussen de haakjes staat.

:has(ul:hover): dit is de voorwaarde, waaraan de <li>'s moeten voldoen. Er moet worden gehoverd over een <ul> die binnen de <li> staat.

button: de <button>'s binnen de <li>'s. De zes <button>'s met de submenu's.

::after: met behulp van ::after wordt bij elk van die <button>'s een pseudo-element gemaakt. Dit pseudo-element wordt gebruikt om het teken '«' weer te geven.

De hele selector in gewone taal: doe iets met het bij <button> met behulp van ::after gemaakte pseudo-element, maar alleen als binnen de <li> binnen ul.menubalk, waar de <button> in zit, een <ul> zit, waarover wordt gehoverd.

(De id 'menubalk-js' wordt door JavaScript toegevoegd. Om deze toegevoegde id te zien moet je niet de gewone code, maar de Gegenereerde code bekijken.)

Dat is een hele mondvol. Er zijn zes <ul>'s die onder de selector #menubalk-js li ul vallen: de zes <ul>'s met de submenu's. Als over een link binnen een van deze <ul>'s wordt gehoverd, wordt automatisch ook over ouder <ul> gehoverd. In dat geval moet het teken '«' naar boven wijzen. Zonder deze regel zou, als over een submenu wordt gehoverd, het teken '«' weer naar beneden wijzen, terwijl het submenu nog is geopend.

Hier gebeurt precies hetzelfde als gelijk hierboven bij #menubalk-js button:hover::after, wanneer over een van de zes <button>'s met submenu wordt gehoverd, of wanneer een link binnen een submenu de focus heeft.

Omdat :has() een tamelijk nieuwe selector is, werkt deze nog niet in Firefox. Als je in Firefox over een submenu hovert, wijst de '«' inderdaad weer naar beneden.

Firefox is bezig de ondersteuning voor :has() te implementeren, dus binnenkort zal dit zijn opgelost. Bovendien gaat het om een kleinigheid, dus er is verder niet gezocht naar een oplossing hiervoor.

Je zou deze selector ook bij de eerdere selector kunnen zetten, maar dan negeert Firefox beide selectors. Als een browser een selector niet ondersteunt, worden álle andere selectors in dezelfde regel genegeerd. Daarom staat deze selector op een aparte regel.

transform: rotate(90deg) translate(2px, 0);

Hiermee wordt het teken '«' naar boven gedraaid. Dit gebeurt op precies dezelfde manier als hierboven bij .transform: rotate(90deg) translate(2px, 0);.

#menubalk-js button[aria-expanded="true"] + ul

Deze selector werkt alleen met JavaScript, omdat het script de id 'menubalk-js' toevoegt.

Voor deze elementen is eerder css opgegeven. Deze wordt binnen dit blokje herhaald in de volgorde, waarin deze in de stylesheet staat, zodat alles hier overzichtelijk bij elkaar staat.

ul {list-style-type: none; margin: 0; padding: 0;}

.menubalk li:last-of-type ul {border-bottom: black solid 1px;}

.menubalk ul {height: 0; visibility: hidden; transform: translateY(-2000px); transition-delay: 0.1s;}

.menubalk button:hover + ul, .menubalk button:focus +ul, .menubalk ul:hover, .menubalk button + ul:focus-within {height: auto; visibility: visible; transform: translateY(0);}

.menubalk button:focus + ul {transition-delay: 0s;}

Alleen op apparaten waarvan de belangrijkste aanwijzer een nauwkeurige is, zoals een muis:

.menubalk button:hover + ul, .menubalk button:hover::after {transition-delay: 0.3s;}

Weer op alle apparaten, alleen in browservensters minimaal 760 px breed:

.menubalk ul {height: auto; max-height: 80vh; overflow: auto; overscroll-behavior-y: contain; margin-left: ‑1px; pointer-events: auto;}

.menubalk li:first-of-type ul {margin-left: 0;}

.menubalk li:last-of-type ul {border-bottom: none;}

Weer in alle browservensters:

#menubalk-js ul {visibility: hidden; transform: translateY(-2000px);}

Alleen op apparaten waarvan de belangrijkste aanwijzer een nauwkeurige is, zoals een muis:

#menubalk-js button:hover + ul, #menubalk-js ul:hover {visibility: visible; transform: translateY(0);}

#menubalk-js: het element met id="menubalk-js". De <ul> waarbinnen vrijwel het hele menu zit.

button: de <button>'s binnen de elementen met class="menubalk". De zes <button>'s met submenu's binnen de menubalk.

[aria-expanded="true"]: dit is een attribuut-selector: wat tussen de [] staat, moet precies zo aanwezig zijn. Als dat niet zo is, geldt de selector niet. De waarde 'true' geeft hier aan, dat het bij de betreffende <button> horende submenu zichtbaar is.

+: het element achter de + moet in de html direct volgen op het element voor de +. In dit geval gaat het om de <ul> die gelijk op de <button> volgt. Beide elementen moeten ook nog dezelfde ouder hebben. Dat is hier het geval, ze hebben beide als ouder dezelfde <li>.

ul: de <ul> die gelijk op de <button> volgt. Dit is de <ul> met het submenu bij die <button>.

De hele selector in gewone taal: doe iets met de <ul> die gelijk op een <button> binnen ul#menubalk-js volgt, maar alleen als die <button> het attribuut aria-expanded="true" heeft.

(De id 'menubalk-js' en de WAI-ARIA-code aria-expanded="true" worden door JavaScript toegevoegd. Om deze toegevoegde id en code te zien moet je niet de gewone code, maar de Gegenereerde code bekijken.)

visibility: visible; transform: translateY(0);

Eerder zijn bij #menubalk-js ul de <ul>'s met de submenu's verborgen met visibility: hidden; en transform: translateY(-2000px);. Hier wordt één van die <ul>'s, en daarmee de erin zittende links, zichtbaar gemaakt.

Waarom de <ul>'s op deze manier zijn verborgen, staat beschreven bij .menubalk ul.

#menubalk-js button[aria-expanded="true"]::after

Deze selector werkt alleen met JavaScript, omdat het script de id 'menubalk-js' toevoegt.

Voor deze elementen is eerder css opgegeven. Deze wordt binnen dit blokje herhaald in de volgorde, waarin deze in de stylesheet staat, zodat alles hier overzichtelijk bij elkaar staat.

.menubalk button::after {content: "«"; display: inline-block; width: 20px; font-size: 1.2em; text-align: center; margin-left: 20px; transform: rotate(270deg) translate(-2px, -4px);}

Alleen in browsers die content: "«" / ""; ondersteunen:

.menubalk button::after {content: "«" / "";}

Weer in alle browsers:

.menubalk button:hover::after, .menubalk li:focus-within button::after {transform: rotate(90deg) translate(2px, 0);}

.menubalk li:has(ul:hover) button::after {transform: rotate(90deg) translate(2px, 0);}

Alleen op apparaten waarvan de belangrijkste aanwijzer een nauwkeurige is, zoals een muis:

.menubalk button:hover + ul, .menubalk button:hover::after {transition-delay: 0.3s;}

Weer op alle apparaten:

#menubalk-js button:hover::after {transform: rotate(270deg) translate(-2px, -4px);}

Alleen op apparaten waarvan de belangrijkste aanwijzer een nauwkeurige is, zoals een muis:

#menubalk-js button:hover::after {transform: rotate(90deg) translate(2px, 0);}

#menubalk-js li:has(ul:hover) button::after {transform: rotate(90deg) translate(2px, 0);}

#menubalk-js: het element met id="menubalk-js". De <ul> waarbinnen vrijwel het hele menu zit.

button: de <button>'s binnen de elementen met class="menubalk". De zes <button>'s met submenu's binnen de menubalk.

[aria-expanded="true"]: dit is een attribuut-selector: wat tussen de [] staat, moet precies zo aanwezig zijn. Als dat niet zo is, geldt de selector niet. De waarde 'true' geeft hier aan, dat het bij de betreffende <button> horende submenu zichtbaar is.

::after: met behulp van ::after wordt bij elk van die <button>'s een pseudo-element gemaakt. Dit pseudo-element wordt gebruikt om het teken '«' weer te geven.

De hele selector in gewone taal: doe iets met het met behulp van ::after gemaakte pseudo-element, maar alleen als een in ul#menubalk-js zittende <button> het attribuut aria-expanded="true" heeft.

(De id 'menubalk-js' en de WAI-ARIA-code aria-expanded="true" worden door JavaScript toegevoegd. Om deze toegevoegde id en code te zien moet je niet de gewone code, maar de Gegenereerde code bekijken.)

transform: rotate(90deg) translate(2px, 0);

Hiermee wordt het teken '«' naar boven gedraaid. Dit gebeurt op precies dezelfde manier als iets hierboven bij transform: rotate(90deg) translate(2px, 0);.

css voor vensters maximaal 759 px breed

@media screen and (max-width: 759px)

De opbouw van deze regel staat beschreven bij @media screen and (min-width: 760px). Er is één verschil: de breedte van het browservenster mag maximaal 759 px zijn. De css binnen deze media query werkt alleen in browservensters die maximaal 759 px breed zijn.

#menubalk-js button[aria-expanded="false"]:focus + ul

Deze selector werkt alleen met JavaScript, omdat het script de id 'menubalk-js' toevoegt.

Voor deze elementen is eerder css opgegeven. Deze wordt hier niet allemaal herhaald, omdat het nogal veel is. Hier wordt slechts een eerder bij .menubalk button:hover + ul ... opgegeven height: auto; veranderd.

#menubalk-js: het element met id="menubalk-js". De <ul> waarbinnen vrijwel het hele menu zit.

button: de <button>'s binnen de elementen met id="menubalk-js". De zes <button>'s met submenu's binnen de menubalk.

[aria-expanded="false"]: dit is een attribuut-selector: wat tussen de [] staat, moet precies zo aanwezig zijn. Als dat niet zo is, geldt de selector niet. De waarde 'false' geeft hier aan, dat het bij de betreffende <button> horende submenu niet zichtbaar is.

:focus: maar alleen als de <button> de focus heeft.

+: het element achter de + moet in de html direct volgen op het element voor de +. In dit geval gaat het om de <ul> die gelijk op de <button> volgt. Beide elementen moeten ook nog dezelfde ouder hebben. Dat is hier het geval, ze hebben beide als ouder dezelfde <li>.

ul: de <ul> die gelijk op de <button> volgt. Dit is de <ul> met het submenu bij die <button>.

De hele selector in gewone taal: doe iets met de <ul> die gelijk op een <button> binnen ul#menubalk-js volgt, maar alleen als die <button> het attribuut aria-expanded="false" heeft én de focus heeft.

(De id 'menubalk-js' en de WAI-ARIA-code aria-expanded="false" worden door JavaScript toegevoegd. Om deze toegevoegde id en code te zien moet je niet de gewone code, maar de Gegenereerde code bekijken.)

height: 0; Submenu is onzichtbaar maar veroorzaakt nog wel een gat.

Bij .menubalk ul hebben de <ul>'s met de submenu's met height: 0; een hoogte van 0 px gekregen. (Waarom dat nodig is, staat daar beschreven.)

Als een submenu getoond moet worden, wordt bij .menubalk button:hover + ul, .menubalk button:focus +ul, ... veranderd in height: auto;: zo hoog als nodig is om het in de <ul> zittende submenu weer te geven. Als JavaScript uitstaat, is dat geen probleem, want dan kan het submenu niet worden gesloten door een <button> nogmaals aan te raken of te klikken. (Sluiten kan` dan alleen door een ander submenu te openen of door een aanraking of klik buiten het menu.)

Met JavaScript aan is het een ander verhaal: dan kan een submenu ook worden gesloten door nogmaals een <button> aan te raken of aan te klikken.

Maar die <button> heeft dan nog steeds de focus, en dus heeft de <ul> nog height: auto;. Je ziet het submenu niet meer, maar de ruimte van het submenu wordt nog steeds ingenomen. Dat levert een gat op, als een submenu weer wordt gesloten door nogmaals een <button> aan te raken of aan te klikken, zoals op de afbeelding is te zien.

Als JavaScript werkt, is aan de <button> aria-expanded="false" toegevoegd, als het submenu weer wordt gesloten. Daardoor kan dat hier worden gebruikt om in dat geval de hoogte ook weer terug te brengen naar 0 px.

css voor vensters minimaal 760 px breed

@media screen and (min-width: 760px)

De opbouw van deze regel staat beschreven bij @media screen and (min-width: 760px). De css binnen deze media query werkt alleen in browservensters minimaal 760 px breed.

#skippy-js

Deze selector werkt alleen met JavaScript, omdat het script de id 'skippy-js' toevoegt.

Deze selector werkt alleen in browservensters minimaal 760 px breed. Voor andere vensters is de uitleg hieronder niet van belang.

Voor dit element is eerder css opgegeven. Deze wordt binnen dit blokje herhaald in de volgorde, waarin deze in de stylesheet staat, zodat alles hier overzichtelijk bij elkaar staat. (Alleen wat binnen deze media query geldig is, wordt binnen dit blokje herhaald.)

.skippy {background: white; width: 10.5em; height: 4.8em; font-size: 1.2em; line-height: 2.4em; text-align: center; border: red solid 3px; position: fixed; top: 20px; left: -20000px; z-index: 250;}

.skippy:focus {left: 50%; transform: translateX(-50%);}

#skippy-js {display: none;}

Het element met id="skippy-js". Dat is er maar één: een skip-link, waarmee in één keer het hele menu gepasseerd kan worden.

(De id 'skippy-js' wordt door JavaScript toegevoegd. Om deze toegevoegde id te zien moet je niet de gewone code, maar de Gegenereerde code bekijken.)

display: block;

De skip-link is eerder bij #skippy-js verborgen, omdat deze in smallere browservensters niet nodig is: daarin moet je kiezen om de menubalk te tonen. In deze bredere vensters is de menubalk altijd zichtbaar, en daarmee ook de zes daarin zittende <button>'s. Met de skip-link kunnen schermlezers en gebruikers van de Tab-toets in één keer die zes <button>'s passeren.

#voor-smal-js[aria-expanded="false"] ~ .menubalk

Deze selector werkt alleen met JavaScript, omdat het script de id 'voor-smal-js' toevoegt.

Deze selector werkt alleen in browservensters minimaal 760 px breed. Voor andere vensters is de uitleg hieronder niet van belang.

Voor dit element is eerder css opgegeven. Deze wordt binnen dit blokje herhaald in de volgorde, waarin deze in de stylesheet staat, zodat alles hier overzichtelijk bij elkaar staat. (Alleen wat binnen deze media query geldig is, wordt binnen dit blokje herhaald.)

.menubalk {display: none; flex-direction: column; max-height: 80vh; overflow: auto; overscroll-behavior-y: contain; position: fixed; top: 3.2rem;}

nav:focus-within .menubalk {display: flex;}

Alleen op apparaten, waarvan de belangrijkste aanwijzer een onnauwkeurige is, zoals een vinger:

nav:hover .menubalk {display: flex;}

Weer op alle apparaten, alleen in browservensters minimaal 760 px breed:

.menubalk {display: flex; flex-direction: row; width: 90vw; margin: 20px auto 0; pointer-events: none; top: 0; right: 0; left: 0;}

#voor-smal-js: het element met id="voor-smal-js". Dat is er maar één: een <button> voor smallere browservensters, waarmee de zes <button>'s met submenu's verborgen of getoond kunnen worden. In bredere vensters is voldoende ruimte voor een menubalk, waarin die zes <button>'s altijd getoond worden.

[aria-expanded="false"]: dit is een attribuut-selector: wat tussen de [] staat, moet precies zo aanwezig zijn. Als dat niet zo is, geldt de selector niet. De waarde 'false' geeft hier aan, dat de menubalk met de zes <button>'s met de submenu's niet zichtbaar is.

~: het hierachter staande element moet ergens in de html staan, na het hiervoor staande element. Het hoeft er, anders dan bij de +, niet gelijk op te volgen, als het maar ergens na het eerste element in de html staat.

De enige voorwaarde is verder dat het voor en het na de ~ staande element dezelfde ouder hebben. Dat is hier het geval: button#voor-smal-js voor de ~ en .menubalk na de ~ hebben beide als ouder de <nav>, waar het hele menu in zit.

.menubalk: de elementen met class="menubalk. Dat is er maar één: de <ul> waarbinnen vrijwel het hele menu zit.

De hele selector in gewone taal: doe iets met de elementen met class="menubalk" (de menubalk) die in de html ergens op het element met id="voor-smal-js" volgen, maar alleen als bij button#voor-smal-js het attribuut aria-expanded="false" aanwezig is.

(De id 'voor-smal-js' en de WAI-ARIA-code aria-expanded="false" worden door JavaScript toegevoegd. Om deze toegevoegde id en code te zien moet je niet de gewone code, maar de Gegenereerde code bekijken.)

display: flex;

In browservensters minder dan 760 px breed is bij gebrek aan ruimte de menubalk verborgen, tot deze door het aanraken of -klikken van button#voor-smal-js wordt getoond. Zolang de menubalk is verborgen, heeft button#voor-smal-js het attribuut aria-expanded="false".

Als nu de breedte van het browservensters van minder dan 760 px naar 760 px of meer wijzigt, bijvoorbeeld door het draaien van een mobieltje of tablet, terwijl de menubalk verborgen is, blijft deze ook verborgen in een venster van 760 px en meer breed. Dat levert een schitterende leegte op, waar je fantastisch op kunt mediteren, maar zoals Boeddha al wist: het volgen van links in een leegte is wat lastig.

Daarom wordt in deze bredere vensters de menubalk altijd getoond, ook als button#voor-smal-js het attribuut aria-expanded="false" heeft.

JavaScript

De code is geschreven in een afwijkende lettersoort. De code die te maken heeft met de basis van dit voorbeeld (essentiële code) is in de hele uitleg onderstippeld blauw. (In dit voorbeeld is dat alleen JavaScript die nodig is voor toegankelijkheid.) Alle niet-essentiële code is bruin.

Bij de uitleg van deze code zijn allerlei haakjes en dergelijke grotendeels weggelaten, want dat voert hier te ver. Als je je in dat soort dingen wilt verdiepen, kun je beter naar sites gaan die meer voor JavaScript zijn bedoeld.

Zonder JavaScript is dit menu bijzonder lastig toegankelijk voor schermlezers en met Toetsenbordnavigatie. Het werkt wel, maar vraag niet hoe. Met behulp van JavaScript wordt het veel toegankelijker. Daarom wordt een groot deel van het JavaScript tot de essentiële code gerekend. Ook andere gebruikers kunnen er voordeel van hebben, maar dat is eigenlijk min of meer toeval.

Een volledig overzicht van wat dit script doet, is te vinden bij Kort overzicht van problemen die met JavaScript worden opgelost.

Omdat het om een iets groter script gaat, is dit in een apart bestand gezet: menu-066-dl.js in de map 066-js-dl. Het script wordt met behulp van <script src="066-js-dl/menu-066-dl.js"></script> aan de pagina gekoppeld.

Als je onderstaande code ergens aanraakt of ‑klikt, ga je rechtstreeks naar de bijbehorende uitleg.

// menu-066-dl.js
(function () { "use strict";
const buttons = document.querySelectorAll(".menubalk button"); const voorSmal = document.querySelector(".voor-smal");
document.addEventListener("keydown", sluiten); document.querySelector(".menubalk").addEventListener("click", verbergToon); document.querySelector("body").addEventListener("click", verbergToonMenubalk);
voorSmal.setAttribute("id", "voor-smal-js"); voorSmal.setAttribute("aria-expanded", "false");
document.querySelector(".skippy").setAttribute("id", "skippy-js");
document.querySelector(".menubalk").setAttribute("id", "menubalk-js");
buttons.forEach(item => { item.setAttribute("aria-expanded", "false"); item.addEventListener("click", function() { item.focus() }); item.addEventListener("click", verbergToon); });
function verbergToonMenubalk(e) { if (e.target.id === "voor-smal-js") { voorSmal.setAttribute("aria-expanded", voorSmal.getAttribute("aria-expanded") === "true" ? "false" : "true"); } else { voorSmal.setAttribute("aria-expanded", "false"; } buttons.forEach(item => { item.setAttribute("aria-expanded", "false"); }); }
function verbergToon(e) { e.stopPropagation(); for (let i = 0 i < buttons.length i++) { if (buttons[i] !== e.target) { buttons[i].setAttribute("aria-expanded", "false"); } else { e.target.setAttribute("aria-expanded", e.target.getAttribute("aria-expanded") === "true" ? "false" : "true"); } } }
function sluiten(e) { let subMenuOpen = false; if (e.key === "Escape") { buttons.forEach(item => { if(item.getAttribute("aria-expanded") === "true") { item.closest("button").focus(); subMenuOpen = true; } item.setAttribute("aria-expanded", "false"); });
if (subMenuOpen === false) { voorSmal.setAttribute("aria-expanded", "false"); voorSmal.focus(); } }
}
}) ();

Als je bovenstaande code ergens aanraakt of ‑klikt, ga je rechtstreeks naar de bijbehorende uitleg.

// menu-066-dl.js

Om vergissingen te voorkomen is het een goede gewoonte bovenaan het script even de naam neer te zetten. Voor je het weet, zit je anders in het verkeerde bestand te werken.

function () {

function: het sleutelwoord waarmee het begin van een functie wordt aangegeven. Een functie is een stukje bij elkaar horende code. (Het haakje helemaal aan het begin wordt iets verderop beschreven.)

(): achter een functie moeten twee haakjes staan. Behalve dat het gewoon zo hoort, kun je hier ook van alles in stoppen om door te geven aan de code in het binnenste van de functie, maar bij deze functie gebeurt dat niet.

{: geeft het begin van de code binnen de functie aan. Aan het eind van de functie, dat is dit geval helemaal onderaan in de laatste regel van het script, staat een bijbehorende }.

In die laatste regel staat in dit geval nog meer dan alleen de } die het einde van de code aangeeft: }) ();

}: dit is de eerder genoemde afsluitende }, waarmee het einde van de code in de functie wordt aangegeven.

): dit afsluitende haakje is de tegenhanger van het haakje dat voor de functie staat. Samen zorgen ze ervoor dat de hele functie, met alles erop en eraan, tussen haakjes staat. De reden van deze haakjes wordt iets hieronder besproken.

(): dit is weer gewoon een taalregel van JavaScript. In dit geval moeten er achter de }) nog twee haakjes staan. Een computer is gewoon niet zo slim, anders weet de ziel niet wat er moet gebeuren.

;: de puntkomma geeft het einde van de regel aan. In gewone tekst zou je hier een punt gebruiken.

We hebben hier een script, waarin alle code binnen een functie is geplaatst. Alle code staat tussen { en }, zodat de computer het begin en einde van de functie kan herkennen.

Een functie is een stukje bij elkaar horende code dat je, zo vaak als je wilt, kunt uitvoeren. Als je de tafel van twaalf op het scherm wilt zetten, hoef je niet twaalf vermenigvuldigingen in JavaScript te schrijven, maar schrijf je 'n functie voor één vermenigvuldiging, die je vervolgens tien keer (op steeds iets andere wijze) uitvoert.

Een functie heeft echter één probleem: de code in de functie wordt pas uitgevoerd, als de functie wordt aangeroepen. Dat aanroepen kan op allerlei manieren gebeuren, bijvoorbeeld als de gebruiker 'n toets indrukt, als de pagina is geladen, als het 13:13 is op vrijdag de dertiende, noem maar op. Maar zonder te worden aangeroepen doet een functie helemaal niets.

In dit script is dat ook zo. Er zit bijvoorbeeld een functie in die reageert op het indrukken van een toets. Om die functie goed te laten werken, moet de computer eerst wat voorbereidend werk verrichten. Daarvoor moet het script worden gelezen. Maar dat hele script zit in een functie, en die functie doet dus pas wat, als die wordt aangeroepen.

Om te zorgen dat de buitenste functie, die waar alle code in zit, toch vanzelf wordt uitgevoerd, zet je er een ( voor. En helemaal aan het eind, achter de afsluitende }, zet je de tegenhanger ). Om te zorgen dat de functie echt vanzelf wordt uitgevoerd, moeten hierachter nog twee haakjes () worden gezet. Nu wordt de functie automatisch uitgevoerd, zonder dat deze hoeft te worden aangeroepen. En kan de code in het script worden gelezen, waardoor het benodigde voorbereidende werk kan worden uitgevoerd.

(Je kunt de code ook in een niet alles omvattende functie zetten. Dan wordt de code ook gelijk uitgevoerd. Maar dat brengt belangrijke risico's met zich mee. In dit script worden namen voor variabelen gebruikt als 'buttons' en 'voorSmal'. Als nou een ander JavaScript toevallig dezelfde namen zou gebruiken, gaat het gruwelijk mis. Door het hele script in een functie te stoppen, voorkom je dat. Als je hier meer over wilt weten, kun je op internet zoeken naar 'name conflict' of 'name clash'.)

"use strict";

Deze regel zorgt ervoor dat bepaalde slordigheden in de code, die makkelijk tot grote problemen kunnen leiden, niet meer mogen. Een validator (die controleert op fouten in de code) is nu strenger en keurt meer dingen af.

const buttons = document.querySelectorAll(".menubalk button");

Als je in de html een andere class dan 'menubalk' hebt gebruikt bij ul.menubalk, moet je die class ook in bovenstaande regel aanpassen.

const buttons: met het sleutelwoord const wordt aangegeven dat het erop volgende woord de naam van een 'variabele' is: de variabele wordt hier 'aangemaakt' of 'gedeclareerd'.

Gelijk na const volgt de naam van één of meer variabelen. Hier staat maar één variabele achter const: 'buttons'. Een variabele is een soort portemonnee: er kan van alles in zitten, en de inhoud kan veranderen.

In buttons wordt dus iets opgeborgen. Omdat de variabele een naam heeft, kan de variabele worden aangeroepen bij deze naam. Net zoals je iemand die 'Marie' heet kunt aanroepen met 'Marie', ongeacht of Marie aardig, onaardig, muzikaal of arrogant is, ongeacht de 'inhoud' van Marie.

Een gebruikelijke manier om de namen van variabelen weer te geven is 'camelCase'. Hierbij staat aan het begin geen hoofdletter, maar bij elk nieuw woord in de naam wel: voorSmal (de variabele waarin gelijk hieronder de <button> voor smallere browservensters wordt opgeslagen). In css zou je dat kunnen schrijven als 'voor-smal', maar koppeltekens mogen in JavaScript niet worden gebruikt in namen van variabelen. De hoofdletters zorgen dat de namen toch leesbaar zijn.

Dingen als het gebruik van hoofdletters zijn geen absolute eisen, maar het zijn wel 'n soort afspraken, waar vrijwel elke programmeur zich aan houdt. Door je aan dit soort afspraken te houden, is je code ook leesbaarder voor anderen (en voor jezelf over acht jaar...).

Tot een aantal jaren geleden kon je maar op één manier variabelen aanmaken: met var. Die manier wordt vrijwel niet meer gebruikt, maar je komt var nog heel vaak tegen in iets oudere code. Het grootste nadeel van var: het was uitermate makkelijk om per ongeluk de inhoud van een met var aangemaakte variabele te veranderen. En die fout was vaak bijzonder moeilijk op te sporen.

In dit geval mag de inhoud van buttons niet veranderen: het aantal <button>'s in het menu verandert ook niet, dus de variabele hoeft ook niet te veranderen. Daarom wordt voor het declareren het sleutelwoord const gebruikt. (Als de variabele wel moet kunnen veranderen, maak je de variabele aan met let in plaats van const.)

Als je const gebruikt, moet je gelijk bij het aanmaken van de variabele een waarde aan de variabele geven, want later kan die waarde niet meer worden veranderd. Dat opgeven van de waarde gebeurt hieronder.

Met const buttons wordt de variabele hier aangemaakt: er wordt een naam aan gegeven. Dat aanmaken gebeurt in de volgorde, waarin ze in het script worden gebruikt. Die volgorde hoeft niet, dat is een kwestie van voorkeur. Je hoeft ook niet alle variabelen aan het begin van hun scope aan te maken, maar dat is wel overzichtelijker. Pardon? Scope?

Een met const (of, als de inhoud van de variabele nog moet kunnen veranderen, let) aangemaakte variabele kan niet overal worden aangeroepen, kan niet overal worden gebruikt: de variabele heeft een 'scope' en kan alleen binnen die scope worden aangeroepen.

Als een variabele binnen een functie wordt aangemaakt, kan de variabele alleen binnen die functie worden aangeroepen. Hier wordt buttons binnen de buitenste functie (function () { aangemaakt. Binnen die buitenste functie staat het hele script. Daardoor kan buttons binnen het hele script worden gebruikt: de scope van buttons is het hele script.

=: hiermee geef je in JavaScript aan dat in de voor het isgelijkteken staande variabele het resultaat van wat achter het isgelijkteken staat moet worden opgeslagen.

document.querySelectorAll(".menubalk button"): het middelste stukje querySelectorAll is een zogenaamde 'functie'. Een functie is een stukje in de browser ingebakken code, waarmee je iets kunt doen. Deze functie is te vinden in het object document, vandaar dat document ervoor staat.

Een object is een bij elkaar horende verzameling van functies en andere code. Een van die objecten heeft de naam 'document'. Bij een object werkt een functie alleen een klein beetje anders dan een gewone functie, daarom heet een functie uit een object 'methode'.

JavaScript heeft een groot aantal ingebakken objecten, waar je gebruik van kunt maken. In document bijvoorbeeld zit heel veel informatie over de pagina, en er zitten heel veel methodes in om met die informatie te kunnen werken.

Met de methode querySelectorAll() uit document kan JavaScript alle elementen opzoeken, die aan een bepaalde voorwaarde voldoen, zoals alle <div>'s, alle elementen met een class="hoera", en dergelijke. Het gegeven waarnaar wordt gezocht, staat tussen aanhalingstekens tussen de haakjes:

querySelectorAll(".menubalk button")

Hierbij is de syntax van het deel tussen de aanhalingstekens precies hetzelfde als bij een selector in css. In bovenstaande regel wordt naar alle <buttons>'s binnen een element met class="menubalk" gezocht. Net zoals je in css in een selector .menubalk button {...} zou gebruiken.

Zou je naar alle <span>'s zoeken die een eerste kind zijn, dan zou je de volgende regel gebruiken:

querySelectorAll("span:first-child")

Precies zoals selectors in css werken.

Er zijn zes <button>'s die door deze selector worden gevonden. De gevonden <button>'s worden in een zogenaamde 'nodelist' gestopt: een soort adressenlijst met zes ingangen, voor elke <button> één. Met de <button>'s in zo'n nodelist kun je van alles doen: css toevoegen, het uiterlijk veranderen, noem maar op.

;: de puntkomma geeft het einde van de regel aan. In gewone tekst zou je hier een punt gebruiken.

De hele declaratie const buttons = document.querySelectorAll(".menubalk button"): sla de <button>'s binnen het element met class="menubalk" op in buttons. Dat wil zeggen dat in variabele buttons een ongelooflijke hoeveelheid informatie over de zes <button>'s wordt gestopt, waar het script later gebruik van kan maken. Zo zit bijvoorbeeld alle css, die aan de <button>'s is gegeven, ook in buttons. Maar niet alleen de in de stylesheet opgegeven css, ook alle standaardwaarden, en ook alle css die van voorouders wordt geërfd. Alle nakomelingen van de zes <button>'s en hun css, attributen, enzovoort, zitten ook in buttons.

Van al deze informatie in buttons kan het script gebruik maken. En ook kunnen veel van deze dingen worden veranderd door het script, zoals een kleur veranderen, of een element verwijderen, of juist toevoegen.

Feitelijk zijn de zes <button>'s elk in de vorm van een object in buttons opgeslagen. Naast allerlei informatie die in die objecten zelf in buttons wordt opgeslagen, kun je daardoor ook gebruik maken van allerlei methoden, die JavaScript gratis en voor niets toevoegt aan de objecten in buttons.

Eerder werd bij beweerd dat een met const aangemaakte variabele niet veranderd kan worden. Dat is ook zo voor wat er in buttons zélf is opgeslagen: je kunt geen <button> meer toevoegen of verwijderen of zoiets. De zes <button>'s zijn echter in de vorm van een object opgeslagen, en bínnen dat object kun je wel van alles wijzigen. Het script gebruikt de objecten in variabele buttons onder andere om bij de <button>'s de WAI-ARIA-code 'aria-expanded' toe te voegen en te veranderen.

In plaats van het gebruik van variabele buttons zou je in de rest van het script ook elke keer document.querySelectorAll(".menubalk button") kunnen gebruiken. maar dat maakt code al snel onleesbaar. Bovendien hoeft het nu maar één keer opgezocht te worden. Maar dat laatste speelt hier eigenlijk geen rol, omdat het opzoeken snel gaat en het maar om zes <button>'s gaat.

const voorSmal = document.querySelector(".voor-smal");

Als je in de html een andere class dan '.voor-smal' hebt gebruikt bij button.voor-smal, moet je die class ook in bovenstaande regel aanpassen. Dit is de enige plaats in het script, waar je dit moet aanpassen.

const voorSmal: met het sleutelwoord const wordt aangegeven dat het erop volgende woord de naam van een 'variabele' is, waarin iets kan worden opgeslagen: de variabele wordt hier 'aangemaakt' of 'gedeclareerd'.

Gelijk na const volgt de naam van één of meer variabelen. Hier staat maar één variabele achter const: 'voorSmal'. Een variabele is een soort portemonnee: er kan van alles in zitten, en de inhoud kan veranderen.

Meer over const en het geven van een naam aan een variabele staat gelijk hierboven bij const buttons = document.querySelectorAll(".menubalk button");.

=: hiermee geef je in JavaScript aan dat in de voor het isgelijkteken staande variabele het resultaat van wat achter het isgelijkteken staat moet worden opgeslagen. Omdat een met const aangemaakte variabele niet van waarde mag veranderen, moet de waarde gelijk hier bij het aanmaken van de variabele worden opgegeven.

document.querySelector(".voor-smal"): het middelste stukje querySelector is een zogenaamde 'functie'. Deze functie is een stukje in de browser ingebakken code, waarmee je iets kunt doen. Deze functie is te vinden in het object document, vandaar dat document ervoor staat.

Een object is een bij elkaar horende verzameling van functies en andere code. Een van die objecten heeft de naam 'document'. Bij een object werkt een functie alleen een klein beetje anders dan een gewone functie, daarom heet een functie uit een object 'methode'.

JavaScript heeft een groot aantal ingebakken objecten, waar je gebruik van kunt maken. In document bijvoorbeeld zit heel veel informatie over de pagina, en er zitten heel veel methodes in om met die informatie te kunnen werken.

Met de methode querySelector() uit document kan JavaScript het eerste element van 'n bepaalde soort opzoeken, zoals de eerste <div>, het eerste element met een class="hoera", en dergelijke. Het gegeven waarnaar wordt gezocht, staat tussen aanhalingstekens tussen de haakjes:

querySelector(".voor-smal")

Hierbij is de syntax van het deel tussen de aanhalingstekens precies hetzelfde als bij een selector in css. In bovenstaande regel wordt naar de elementen met class="voor-smal" gezocht, net zoals je in css in een selector .voor-smal {...} zou gebruiken.

Zou je naar de eerste <span> zoeken die een eerste kind is, dan zou je de volgende regel gebruiken:

querySelector("span:first-child")

Precies zoals selectors in css werken.

;: de puntkomma geeft het einde van de regel aan. In gewone tekst zou je hier een punt gebruiken.

De hele declaratie const voorSmal = document.querySelector(".voorSmal"): sla het eerste element met class="voor-smal" op in voorSmal. In dit geval is er maar één element class="voor-smal": de <button> waarmee in smallere browservensters de menubalk kan worden getoond en verborgen.

In variabele voorSmal wordt een ongelooflijke hoeveelheid informatie over button.voor-smal gestopt, waar het script later gebruik van kan maken. Zo zit bijvoorbeeld alle css, die aan button.voor-smal gegeven, ook in voorSmal. Maar niet alleen de in de stylesheet opgegeven css, ook alle standaardwaarden, en ook alle css die van voorouders wordt geërfd. Alle nakomelingen van button.voor-smal en hun css, attributen, enzovoort, zitten ook in voorSmal.

Van al deze informatie in voorSmal kan het script gebruik maken. En ook kunnen veel van deze dingen worden veranderd door het script, zoals een kleur veranderen, of een element verwijderen, of juist toevoegen.

Feitelijk is button.voor-smal in de vorm van een object in voorSmal opgeslagen. Naast allerlei informatie die in dat object zelf in voorSmal wordt opgeslagen, kun je daardoor ook gebruik maken van allerlei methoden, die JavaScript gratis en voor niets toevoegt aan het object in voorSmal.

Eerder werd bij beweerd dat een met const aangemaakte variabele niet veranderd kan worden. Dat is ook zo voor wat er in voorSmal zélf is opgeslagen: je kunt geen ander element meer toevoegen of de naam van voorSmal veranderen of zoiets. button.voor-smal is echter in de vorm van een object opgeslagen, en bínnen dat object kun je wel van alles wijzigen. Het script gebruikt het object in variabele voorSmal onder andere om bij button.voor-smal de WAI-ARIA-code 'aria-expanded' toe te voegen en te veranderen.

In plaats van het gebruik van variabele voorSmal zou je in de rest van het script ook elke keer document.querySelector(".voor-smal") kunnen gebruiken. maar dat maakt code al snel onleesbaar. Bovendien hoeft het nu maar één keer opgezocht te worden. Maar dat laatste speelt hier eigenlijk geen rol, omdat het opzoeken snel gaat en de variabele maar zeven keer wordt gebruikt.

document.addEventListener("keydown", sluiten);

document: dit is een zogenaamd 'object'. Een object in JavaScript is iets, waarin onder andere allerlei gegevens zijn opgeslagen. In document is de hele pagina opgeslagen op een voor JavaScript toegankelijke manier. (Je hebt hele series objecten voor de wildste dingen, maar de meeste zijn hier verder niet van belang.)

Het voor JavaScript toegankelijke model van de pagina heet 'Document Object Model', afgekort tot 'DOM'. Dit wordt automatisch door de browser gemaakt, als de pagina wordt geladen. Door de regel te beginnen met document, weet het script waar de rest van de code op deze regel bij hoort: bij de hele pagina. (Je kunt ook code koppelen aan één specifieke <div>, of aan één specifieke <input>, of ... Dat gebeurt in dit script verderop ook.)

addEventListener: er wordt een zogenaamde 'eventlistener' gekoppeld aan het voor de punt staande document, aan de pagina.

Een eventlistener luistert naar een gebeurtenis. Die gebeurtenis, de 'event', kan van alles zijn: het indrukken van een toets, klikken, scrollen, de video is afgespeeld, van alles. Tussen de haakjes van addEventListener() staat, naar welke soort gebeurtenis moet worden geluisterd, en wat er moet gebeuren, als die gebeurtenis zich voordoet. Zeg maar 'n soort rampenplan: áls gebeurtenis is 'doodsmak', dán handeling is 'bel 112'.

"keydown": tussen aanhalingstekens, zodat het script weet dat dit een letterlijke naam is, een 'string' (dit is gewoon een van de taalkundige regels van JavaScript). Dit is de naam van de gebeurtenis, waarnaar wordt geluisterd, waarop wordt gewacht: 'keydown': als een toets wordt ingedrukt. Maakt niet uit welke toets.

Deze regel roept de functie aan, die het eigenlijke werk gaat doen.

sluiten: deze naam staat niet tussen aanhalingstekens, omdat het hier niet om een letterlijke naam of zo, een 'string', gaat. De naam verwijst naar een 'functie', iets wat moet gebeuren. Die functie staat verderop bij function (function () {.

;: de puntkomma geeft het einde van de regel aan. In gewone tekst zou je hier een punt gebruiken.

De hele regel in gewone taal: als een toets wordt ingedrukt, maakt niet uit welke toets of waar, voer dan de functie 'sluiten' uit. Deze functie zorgt dat bij het indrukken van Escape de submenu's weer sluiten (en in smallere browservensters ook dat de menubalk weer wordt verborgen).

De eventlistener wordt hier aan document gekoppeld: de hele pagina. Dat betekent dat de functie overal wordt aangeroepen, waar de pagina wordt aangeraakt, -geklikt, enzovoort. Je zou de eventlistener ook kunnen koppelen aan een deel van de pagina, maar dat is hier niet handig: de Escape-toets moet overal op de pagina een submenu (of in smallere browservensters de menubalk) sluiten.

document.querySelector(".menubalk").addEventListener("click", verbergToon);

document.querySelector(".menubalk"): met de methode querySelector() uit document kan JavaScript het eerste element van 'n bepaalde soort opzoeken, zoals de eerste <div>, het eerste element met een class="hoera", en dergelijke. Het gegeven waarnaar wordt gezocht, staat tussen aanhalingstekens tussen de haakjes:

querySelector(".menubalk")

In dit geval wordt gezicht naar het eerste element met class 'menubalk'. Je zou kunnen zeggen dat dit eerste deel dus eigenlijk gewoon ul.menubalk betekent: er moet iets gebeuren met de ul.menubalk van de pagina.

Een uitgebreidere beschrijving van querySelector() staat iets hierboven bij document.querySelector(".voor-smal").

addEventListener: er wordt een zogenaamde 'eventlistener' gekoppeld aan het voor de punt staande document.querySelector(".menubalk"), dus eigenlijk aan ul.menubalk. Een eventlistener luistert naar een gebeurtenis. Die gebeurtenis, de 'event', kan van alles zijn: het indrukken van een toets, klikken, scrollen, de video is afgespeeld, van alles. Tussen de haakjes van addEventListener() staat, naar welke soort gebeurtenis moet worden geluisterd, en wat er moet gebeuren, als die gebeurtenis zich voordoet. Zeg maar 'n soort rampenplan: áls gebeurtenis is 'doodsmak', dán handeling is 'bel 112'.

Omdat deze eventlistener aan ul.menubalk is gekoppeld, werkt deze eventlistener binnen ul.menubalk, ook bij de nakomelingen daarvan.

"click": tussen aanhalingstekens, zodat het script weet dat dit een letterlijke naam is, een 'string' (dit is gewoon een van de taalkundige regels van JavaScript). Dit is de naam van de gebeurtenis, waarnaar wordt geluisterd, waarop wordt gewacht: 'click': als de muis wordt ingedrukt, of als een touchscreen wordt aangeraakt (toen 'click' werd bedacht, bestonden er nog geen touchscreens. Je kon alleen maar 'click' doen met de muis, vandaar de wat achterhaalde naam.)

Deze regel roept de functie aan, die het eigenlijke werk gaat doen.

verbergToon: deze naam staat niet tussen aanhalingstekens, omdat het hier niet om een letterlijke naam of zo, een 'string' gaat. De naam verwijst naar een 'functie', iets wat moet gebeuren. Die functie staat iets hieronder bij function verbergToon(e) {.

;: de puntkomma geeft het einde van de regel aan. In gewone tekst zou je hier een punt gebruiken.

De hele regel in gewone taal: als het scherm ergens binnen ul.menubalk wordt aangeraakt of ‑geklikt, voer dan de functie 'verbergToon' uit. Deze functie regelt het tonen en verbergen van de submenu's.

Deze functie moet worden aangeroepen als een van de zes <button>'s met een submenu wordt aangeraakt of -geklikt. Je zou ook bij elk van die <button>'s een eigen eventlistener kunnen aanbrengen (en in een menu met 92 <button>'s 92 keer...). Maar dat is niet nodig. Deze eventlistener reageert op een aanraking of klik. Als je een van de <button>'s in ul.menubalk aanraakt of -klikt, wordt eerst gekeken, of er een eventlistener bij die <button> is. Vervolgens wordt gekeken of bij de ouder van die <button> een eventlistener aanwezig is. Of bij de ouder van die ouder. Enzovoort, tot het buitenste element <html> is bereikt.

In plaats van zes aparte eventlisteners kun je ook één eventlistener bij een gemeenschappelijke voorouder van de zes <button>'s aanbrengen, en in die eventlistener kijken welk van de zes <button>'s is aangeraakt of -geklikt. Dit is veel efficiënter dan zes aparte eventlisteners.

document.querySelector("body").addEventListener("click", verbergToonMenubalk);

Deze regel is bijna hetzelfde als die gelijk hierboven bij document.querySelector(".menubalk").addEventListener("click", verbergToon);, alleen wordt hier niet gezocht naar .menubalk, maar naar <body>, en er wordt een andere functie aangeroepen: functie verbergToonMenubalk(). Deze functie regelt het tonen en verbergen van de menubalk in smallere browservensters.

Omdat deze eventlistener aan <body> wordt gekoppeld, werkt deze eventlistener op de hele pagina. Waar je het scherm ook aanraakt of -klikt, deze eventlistener reageert erop.

Althans: dat is in dit voorbeeld zo, want <body> is hoger dan het venster van de browser, omdat er tekst onder het menu staat. Als dat niet zo is en <body> is lager dan het venster, moet je <body> op een of andere manier hoger maken. Als je dat niet doet, reageert het onderste deel van het scherm niet op deze eventlistener, omdat <body> daar mist.

voorSmal.setAttribute("id", "voor-smal-js");

voorSmal: in de variabele voorSmal is bij const voorSmal = document.querySelector(".voor-smal"); button.voor-smal opgeslagen in de vorm van een object. Een object is een bij elkaar horende verzameling van functies en andere code, waarin veel informatie over het opgeslagen element, de kinderen daarvan, enzovoort zit. Elk object heeft ook een aantal ingebakken functies, die gratis en voor niets gebruikt kunnen worden. Bij een object werkt een functie alleen een klein beetje anders dan een gewone functie, daarom heet een functie uit een object 'methode'.

setAttribute(): dit is zo'n methode. Met setAttribute() kun je een attribuut aan een element in de html toevoegen. Dat kan van alles zijn: tabindex, een WAI-ARIA-code, noem maar op. Tussen de haakjes komen de naam en de waarde van het attribuut te staan.

Omdat voor de punt voorSmal staat, werkt deze methode op het in de variabele voorSmal opgeslagen object, op het daarin zittende element.

("id", "voor-smal-js"): voor de komma staat tussen aanhalingstekens de naam van het attribuut dat wordt toegevoegd, achter de komma staat tussen aanhalingstekens de waarde van het attribuut.

In dit geval levert dat id="voor-smal-js" op:

<button id="voor-smal-js" class="voor-smal" type="button">

(Als je de aan de <button> toegevoegde id wilt bekijken, moet je niet in de gewone code, maar in de Gegenereerde code kijken.)

;: de puntkomma geeft het eind van de regel aan. In gewone tekst zou je hier een punt gebruiken.

De hele regel in gewone taal: voeg aan button.voor-smal de id "voor-smal-js" toe. Hierdoor gaan de selectors uit de css, waarin #voor-smal-js voorkomt, werken. Zonder JavaScript wordt de id niet toegevoegd en werken deze selectors dus niet.

voorSmal.setAttribute("aria-expanded", "false");

Deze regel is bijna hetzelfde als die gelijk hierboven bij voorSmal.setAttribute("id", "voor-smal-js");, alleen wordt hier de WAI-ARIA-code aria-expanded="false" toegevoegd aan button.voor-smal, waardoor de attribuut-selector [aria-expanded="false"] gaat werken. Deze geeft aan dat de menubalk is verborgen.

(Als je het aan de <button> toegevoegde attribuut wilt bekijken, moet je niet in de gewone code, maar in de Gegenereerde code kijken.)

document.querySelector(".skippy").setAttribute("id", "skippy-js");

Als je in de html een andere class dan 'skippy' hebt gebruikt bij a.skippy, moet je die class ook in bovenstaande regel aanpassen. Dit is de enige plaats in het script, waar je dit moet aanpassen.

document.querySelector(".skippy"): met de methode querySelector() uit document kan JavaScript het eerste element van 'n bepaalde soort opzoeken, zoals de eerste <div>, het eerste element met een class="hoera", en dergelijke. Het gegeven waarnaar wordt gezocht, staat tussen aanhalingstekens tussen de haakjes:

querySelector(".skippy")

In dit geval wordt gezicht naar de elementen met class="skippy". Dat is er maar één: skip-link a.skippy.

Een uitgebreidere beschrijving van querySelector() staat iets hierboven bij document.querySelector(".voor-smal").

setAttribute("id", "skippy-js"): voeg aan de hierboven gevonden skip-link de id "skippy-js" toe. Een uitgebreidere beschrijving van setAttribute() staat bij setAttribute().

;: de puntkomma geeft het eind van de regel aan. In gewone tekst zou je hier een punt gebruiken.

De hele regel in gewone taal: vind het eerste element met class="skippy" (dat is skip-link a.skippy) en voeg de id "skippy-js" toe:

<a class="skippy" href="#content" id="skippy-js">Skip menu</a>

Hierdoor gaan de selectors uit de css, waarin #skippy-js voorkomt, werken. Zonder JavaScript wordt de id niet toegevoegd en werken deze selectors dus niet. (Als je de aan a.skippy toegevoegde id wilt bekijken, moet je niet in de gewone code, maar in de Gegenereerde code kijken.)

document.querySelector(".menubalk").setAttribute("id", "menubalk-js");

Als je in de html een andere class dan 'menubalk' hebt gebruikt bij ul.menubalk, moet je die class ook in bovenstaande regel aanpassen.

Deze regel is bijna hetzelfde als die hierboven bij document.querySelector(".skippy").setAttribute("id", "skippy-js");, alleen wordt hier gezocht naar het eerste element met class="menubalk" en wordt daar id "menubalk-js" aan toegevoegd:

<ul id="menubalk-js" class="menubalk">

Hierdoor gaan de selectors uit de css, waarin #menubalk-js voorkomt, werken. Zonder JavaScript wordt de id niet toegevoegd en werken deze selectors dus niet. (Als je de aan ul.menubalk toegevoegde id wilt bekijken, moet je niet in de gewone code, maar in de Gegenereerde code kijken.)

buttons.forEach(item => {

buttons: in de variabele buttons zijn bij const buttons = document.querySelectorAll(".menubalk button"); de zes <button>'s uit de menubalk opgeslagen in de vorm van een object. Een object is een bij elkaar horende verzameling van functies en andere code, waarin veel informatie over het opgeslagen element, de kinderen daarvan, enzovoort zit. Elk object heeft ook een aantal ingebakken functies, die gratis en voor niets gebruikt kunnen worden. Bij een object werkt een functie alleen een klein beetje anders dan een gewone functie, daarom heet een functie uit een object 'methode'.

forEach(item =>: doe iets met elk van de <button>'s in variabele buttons.

forEach(): dit is zo'n hierboven genoemde methode. Omdat voor de punt buttons staat, wordt de methode uitgevoerd op de <button>'s die in de vorm van een object in buttons zitten.

forEach zorgt ervoor dat elk van die <button>'s wordt verwerkt. Van de eerste tot en met de laatste. Als een <button> in variabele buttons is verwerkt, wordt met de volgende <button> verder gegaan, tot de hele lijst met <button>'s is verwerkt.

item: om de <button> die aan de beurt is te kunnen verwerken, moet het beestje 'n naam hebben: item. Dit is een veel gebruikte naam in dit soort bewerkingen. Ook element wordt vaak gebruikt. Maar je kunt desnoods ook rottigeKerstboom gebruiken als naam, alleen maak je je dan niet populair bij degene die ooit je code moet bekijken (en bij je toekomstige zelf van over twintig jaar, want zelfs begrijp je dit dan ook niet meer).

=>: een vereenvoudigde manier om te zeggen dat hierop een functie volgt. De functie wordt hier gelijk uitgevoerd en kan, omdat de functie geen naam heeft, niet worden aangeroepen: het is een 'anonieme' functie. Of eigenlijk: het is een speciaal soort anonieme functie, een 'arrow function'. De code die de functie moet uitvoeren staat tussen de {}. De { staat op deze regel, de bijbehorende } staat aan het eind van de functie.

De hele regel: voer bij elk van de <button>'s in variabele buttons de code uit die tussen de { en de } achter => staat.

item.setAttribute("aria-expanded", "false");

Deze regel is onderdeel van de anonieme functie die begint bij buttons.forEach(item => {

Deze regel wordt voor elk item één keer uitgevoerd.

item: om iets te kunnen doen met elk van de in variabele buttons zittende <button>'s, is hierboven een naam gegeven aan de <button> die aan de beurt is: 'item'. Die naam wordt hier herhaald, zodat duidelijk is waarmee iets moet worden gedaan. item verwijst steeds naar een andere <button>, van de eerste tot de laatste, tot ze allemaal aan de beurt zijn geweest.

De <button>'s zijn opgeslagen in de vorm van een object. Een object is een bij elkaar horende verzameling van functies en andere code, waarin veel informatie over het opgeslagen element, de kinderen daarvan, enzovoort zit. Elk object heeft ook een aantal ingebakken functies, die gratis en voor niets gebruikt kunnen worden. Bij een object werkt een functie alleen een klein beetje anders dan een gewone functie, daarom heet een functie uit een object 'methode'.

setAttribute(): dit is zo'n methode. Met setAttribute() kun je een attribuut aan een element in de html toevoegen. Dat kan van alles zijn: tabindex, een WAI-ARIA-code, noem maar op. Tussen de haakjes komen de naam en de waarde van het attribuut te staan.

Omdat voor de punt item staat, werkt deze methode op de in de vorm van een object in buttons opgeslagen <button>, die aan de beurt is.

("aria-expanded", "false"): voor de komma staat tussen aanhalingstekens de naam van het attribuut dat wordt toegevoegd, achter de komma staat tussen aanhalingstekens de waarde van het attribuut.

In dit geval levert dat aria-expanded="false" op:

<button type="button" aria-controls="menu-1" aria-expanded="false">

(Als je het aan de <button> toegevoegde attribuut wilt bekijken, moet je niet in de gewone code, maar in de Gegenereerde code kijken.)

;: de puntkomma geeft het eind van de regel aan. In gewone tekst zou je hier een punt gebruiken.

De hele regel in gewone taal: voeg aan het item uit variabele buttons dat aan de beurt is de WAI-ARIA-code aria-expanded="false" toe, waardoor de attribuut-selector [aria-expanded="false"] gaat werken.

Omdat deze regel onderdeel is van de anonieme functie die begint bij buttons.forEach(item => {, worden alle in <button>'s in de vorm van een object opgeslagen <button>'s afgelopen. Aan het eind van de functie hebben alle <button>'s hierdoor het attribuut aria-expanded="false" gekregen. Wat precies de bedoeling is, want dit attribuut geeft aan dat de submenu's niet zijn geopend, en dat is gelijk na het openen van de pagina het geval.

item.addEventListener("click", function() {

Deze regel is onderdeel van de anonieme functie die begint bij buttons.forEach(item => {

Deze regel wordt voor elk item één keer uitgevoerd.

item: om iets te kunnen doen met elk van de in variabele buttons zittende <button>'s, is hierboven een naam gegeven aan de <button> die aan de beurt is: 'item'. Die naam wordt hier herhaald, zodat duidelijk is waarmee iets moet worden gedaan. item verwijst steeds naar een andere <button>, van de eerste tot de laatste, tot ze allemaal aan de beurt zijn geweest.

De <button>'s zijn opgeslagen in de vorm van een object. Een object is een bij elkaar horende verzameling van functies en andere code, waarin veel informatie over het opgeslagen element, de kinderen daarvan, enzovoort zit. Elk object heeft ook een aantal ingebakken functies, die gratis en voor niets gebruikt kunnen worden. Bij een object werkt een functie alleen een klein beetje anders dan een gewone functie, daarom heet een functie uit een object 'methode'.

addEventListener: één van die hierboven genoemde methodes is 'addEventListener'.

Een eventlistener luistert naar een gebeurtenis. Die gebeurtenis, de 'event', kan van alles zijn: het indrukken van een toets, klikken, scrollen, de video is afgespeeld, van alles. Tussen de haakjes van addEventListener() staat, naar welke soort gebeurtenis moet worden geluisterd, en wat er moet gebeuren, als die gebeurtenis zich voordoet. Zeg maar 'n soort rampenplan: áls gebeurtenis is 'doodsmak', dán handeling is 'bel 112'.

Omdat voor de punt item staat, werkt deze methode op de in de vorm van een object in buttons opgeslagen <button>, die aan de beurt is.

"click": tussen aanhalingstekens, zodat het script weet dat dit een letterlijke naam is (dit is gewoon een van de taalkundige regels van JavaScript). Dit is de naam van de gebeurtenis, waarnaar wordt geluisterd, waarop wordt gewacht: 'click': als de muis wordt ingedrukt, of als een touchscreen wordt aangeraakt (toen 'click' werd bedacht, bestonden er nog geen touchscreens. Je kon alleen maar 'click' doen met de muis, vandaar de wat achterhaalde naam.)

Hierachter staat de functie, die bij een klik of aanraking wordt aangeroepen en het eigenlijke werk gaat doen.

function(): de functie die bij een klik of aanraking wordt aangeroepen. De functie wordt hier gelijk uitgevoerd en kan, omdat de functie geen naam heeft, niet worden aangeroepen: het is een 'anonieme' functie. De code die de functie moet uitvoeren staat tussen de {}. De { staat op deze regel, de bijbehorende } staat aan het eind van de functie. Omdat het hier om een heel simpele functie gaat, wordt die gelijk hier afgehandeld.

De hele regel in gewone taal: als een van de zes <button>'s met een submenu wordt aangeraakt of -geklikt, voer dan de code uit die gelijk na function() { begint.

item.focus();

Deze regel is onderdeel van de anonieme functie die begint bij buttons.forEach(item => {

Deze regel is onderdeel van de anonieme functie die begint bij item.addEventListener("click", function() {

Deze regel wordt voor elk item één keer uitgevoerd.

item: de anonieme functie waar deze regel een onderdeel van is, zit zelf ook weer binnen een anonieme functie:

buttons.forEach(item => { (...) item.addEventListener("click", function() { (...) } (...) });

Aan de buitenste anonieme functie (die gelijk na => begint) wordt item doorgegeven. De binnenste anonieme functie kan hier ook gebruik van maken, want een functie binnen een (of meer) andere functie(s) kan altijd gebruik maken van de variabelen en dergelijke van de buitenste functie(s).

Het verhaal over item bij item.addEventListener("click", function() { geldt ook hier: werk achtereenvolgens alle in de vorm van een object in variabele buttons opgeslagen <button>'s af.

focus(): geef de focus aan het element, aan de <button>, die aan de beurt is. Maar omdat deze regel binnen een anonieme functie zit, die alleen bij een klik of aanraking wordt uitgevoerd, gebeurt dit alleen als een van de <button>'s wordt aangeraakt of -geklikt.

Dit is nodig voor Safari, omdat die browser geen focus geeft aan een <button> die wordt aangeraakt of -geklikt. Omdat browsers op iOS en iPadOS verplicht de weergave-machine van Safari moeten gebruiken, geldt dit ook voor alle andere browsers op iOS en iPadOS. Door deze regel krijgen de <button>'s ook in deze browsers bij aanraken of klikken de focus.

item.addEventListener("click", verbergToon);

Deze regel is onderdeel van de anonieme functie die begint bij buttons.forEach(item => {

Deze regel wordt voor elk item één keer uitgevoerd.

item: om iets te kunnen doen met elk van de in variabele buttons zittende <button>'s, is hierboven een naam gegeven aan de <button> die aan de beurt is: 'item'. Die naam wordt hier herhaald, zodat duidelijk is waarmee iets moet worden gedaan. item verwijst steeds naar een andere <button>, van de eerste tot de laatste, tot ze allemaal aan de beurt zijn geweest.

De <button>'s zijn opgeslagen in de vorm van een object. Een object is een bij elkaar horende verzameling van functies en andere code, waarin veel informatie over het opgeslagen element, de kinderen daarvan, enzovoort zit. Elk object heeft ook een aantal ingebakken functies, die gratis en voor niets gebruikt kunnen worden. Bij een object werkt een functie alleen een klein beetje anders dan een gewone functie, daarom heet een functie uit een object 'methode'.

addEventListener: één van die hierboven genoemde methodes is 'addEventListener'. Een eventlistener luistert naar een gebeurtenis. Die gebeurtenis, de 'event', kan van alles zijn: het indrukken van een toets, klikken, scrollen, de video is afgespeeld, van alles. Tussen de haakjes van addEventListener() staat, naar welke soort gebeurtenis moet worden geluisterd, en wat er moet gebeuren, als die gebeurtenis zich voordoet. Zeg maar 'n soort rampenplan: áls gebeurtenis is 'doodsmak', dán handeling is 'bel 112'.

Omdat voor de punt item staat, werkt deze methode op de in de vorm van een object in buttons opgeslagen <button>, die aan de beurt is.

"click": tussen aanhalingstekens, zodat het script weet dat dit een letterlijke naam is, een 'string' (dit is gewoon een van de taalkundige regels van JavaScript). Dit is de naam van de gebeurtenis, waarnaar wordt geluisterd, waarop wordt gewacht: 'click': als de muis wordt ingedrukt, of als een touchscreen wordt aangeraakt (toen 'click' werd bedacht, bestonden er nog geen touchscreens. Je kon alleen maar 'click' doen met de muis, vandaar de wat achterhaalde naam.)

Deze regel roept de functie aan, die het eigenlijke werk gaat doen.

verbergToon: deze naam staat niet tussen aanhalingstekens, omdat het hier niet om een letterlijke naam of zo, een 'string' gaat. De naam verwijst naar een 'functie', iets wat moet gebeuren. Die functie staat iets hieronder bij function verbergToon(e) { en regelt het tonen en verbergen van de submenu's.

;: de puntkomma geeft het einde van de regel aan. In gewone tekst zou je hier een punt gebruiken.

De hele regel in gewone taal: als een van de zes <button>'s met een submenu wordt aangeraakt of ‑geklikt, voer dan de functie 'verbergToon' uit. Deze functie regelt het tonen en verbergen van de submenu's.

function verbergToonMenubalk(e) {

Ook deze functie is, zoals elke functie, weer een stukje bij elkaar horende code. Maar anders dan de buitenste functie wordt deze niet automatisch uitgevoerd. (Waarom de buitenste functie wel automatisch wordt uitgevoerd, is te vinden bij (function () {.)

De code in deze functie wordt alleen uitgevoerd, als de functie wordt aangeroepen. Dat aanroepen gebeurt, als het scherm ergens wordt aangeraakt of -geklikt. Het luisteren daarnaar wordt geregeld bij document.querySelector("body").addEventListener("click", verbergToonMenubalk);.

De code in deze functie regelt, wat er gebeurt bij een aanraking of klik. Dat zijn twee dingen:

– In smallere browservensters regelt button#voor-smal-js het tonen en verbergen van de menubalk. Als deze <button> wordt aangeraakt of -geklikt, verandert de status van de menubalk. Als de menubalk al wordt getoond, wordt deze verborgen. Als de menubalk niet wordt getoond, wordt deze getoond.

– Als in smallere browservensters het scherm ergens buiten bovenstaande <button> wordt aangeraakt of -geklikt, wordt de menubalk verborgen.

function: het sleutelwoord waarmee het begin van een functie wordt aangegeven.

verbergToonMenubalk: de naam van de functie. Als het beestje geen naam heeft, kun je het ook niet aanroepen en heb je er dus niets aan. (Dit klopt niet helemaal. JavaScript kent ook equivalenten van 'hé!', 'hé, jij daar!', 'hé, jij daar in die zwarte jas', en dergelijke, maar die worden hier niet gebruikt. En het is zo al ingewikkeld genoeg.)

Het is bij namen in JavaScript gebruikelijk om nieuwe woorden met een hoofdletter te beginnen, omdat spaties, koppeltekens, en dergelijke niet gebruikt mogen worden in een naam. In css zou je hier bijvoorbeeld verberg-toon-menubalk kunnen gebruiken in plaats van verbergToonMenubalk.

(e): die haakjes horen nou eenmaal zo na het sleutelwoord function. Behalve dat het gewoon zo hoort, kun je hier ook van alles in stoppen om door te geven aan de code in het binnenste van de functie. Zoals de plaats waar het scherm is aangeraakt of ‑geklikt. In dit geval wordt e doorgegeven.

e is een zogenaamd object. In een object zitten allerlei gegevens over hoe de functie is aangeroepen (over dat aanroepen later meer). In dit geval wordt deze functie aangeroepen door een aanraking of klik. In e zit bijvoorbeeld, waar het scherm precies is aangeraakt of -geklikt, welk element is aangeraakt, en nog veel meer.

Los hiervan voegt JavaScript aan e allerlei methodes toe: functies binnen het object, waarmee je allerlei dingen kunt doen.

Heel formeel is e eigenlijk geen object, maar is e een parameter, iets dat wordt doorgegeven aan de functie, zodat het binnen die functie gebruikt kan worden. e is de naam van het object, en de inhoud van e is een object. Om het object iets te kunnen vragen, of het iets te laten doen, moet het beestje 'n naam hebben: e.

De naam e voor het object is niet verplicht, maar 'n soort afspraak, zodat code makkelijker door anderen is te begrijpen. Maar als je het object niet e, maar 'hetIsStervenskoud' wilt noemen, is daar technisch geen enkel bezwaar tegen. Het is dan wel verstandig een cursus zelfverdediging te volgen, voor het geval iemand anders ooit je code moet bekijken.

(e is een afkorting van 'event', gebeurtenis. De functie reageert op een gebeurtenis, in dit geval een aanraking of klik. In e zit het object dat bij díé gebeurtenis hoort. Bij bijvoorbeeld een muisklik krijg je een ander object met andere informatie dan bij het indrukken van een toets.)

{: geeft het begin van de code binnen de functie aan. Aan het eind van de functie staat een bijbehorende }.

if (e.target.id === "voor-smal-js") {

Deze regel is onderdeel van function verbergToonMenubalk(e) {

Als de id van het aangeraakte of -geklikte element 'voor-smal-js' is, wordt de WAI-ARIA-code aria-expanded aangepast.

if: dat betekent gewoon 'als': als er aan de voorwaarde hierachter is voldaan. Die voorwaarde staat tussen haakjes, omdat dat nou eenmaal zo hoort. Het is het hele deel tussen de twee buitenste haakjes achter de if.

e: in e zit een zogenaamd object, waarin allerlei informatie zit. Bij function verbergToonMenubalk(e) {, de functie waar deze regel een onderdeel van is, is dit object aan de functie doorgegeven. Hierdoor kan de code in de functie de informatie uit dit object gebruiken.

target: dit is zo'n stukje informatie uit het hierboven genoemde object. Hierin zit, ook weer in de vorm van een object met veel informatie, het element dat werd aangeraakt of -geklikt.

id: dit is zo'n stukje informatie uit target: de id van het aangeraakte of -geklikte element.

===: wat hiervoor staat moet precies, echt helemaal precies, hetzelfde zijn, als wat hierachter staat. Om dat echt helemaal volkomen precies hetzelfde aan te geven, worden drie isgelijktekens gebruikt.

"voor-smal-js": dit is de id die bij voorSmal.setAttribute("id", "voor-smal-js"); aan button.voor-smal is toegevoegd. (Dit is de <button> waarmee in smallere browservensters de menubalk kan worden getoond of verborgen.)

{: de code die wordt uitgevoerd, als aan deze if-voorwaarde wordt voldaan, wordt tussen accolades gezet. Het script weet dan, wat bij deze if hoort. Aan het eind van de code bij deze if staat de afsluitende }.

De hele regel in gewone taal: als de id van het aangeraakte of -geklikte element 'voor-smal-js' is, voer dan de code tussen de {} achter de if uit.

voorSmal.setAttribute("aria-expanded", voorSmal.getAttribute("aria-expanded") === "true" ? "false" : "true");

Deze regel is onderdeel van function verbergToonMenubalk(e) {

Deze regel wordt alleen uitgevoerd, als aan deze eerder bij if gestelde voorwaarde is voldaan:

– de id van het aangeraakte of -geklikte element moet 'voor-smal-js' zijn.

Een tamelijk ingewikkelde regel, maar als je de regel in stukjes hakt, valt het hopelijk mee.

voorSmal: in de variabele voorSmal is bij const voorSmal = document.querySelector(".voor-smal"); button.voor-smal opgeslagen in de vorm van een object. Een object is een bij elkaar horende verzameling van functies en andere code, waarin veel informatie over het opgeslagen element, de kinderen daarvan, enzovoort zit. Elk object heeft ook een aantal ingebakken functies, die gratis en voor niets gebruikt kunnen worden. Bij een object werkt een functie alleen een klein beetje anders dan een gewone functie, daarom heet een functie uit een object 'methode'.

setAttribute(): dit is zo'n methode. Met setAttribute() kun je een attribuut aan een element in de html toevoegen. Dat kan van alles zijn: tabindex, een WAI-ARIA-code, noem maar op. Tussen de haakjes komen de naam en de waarde van het attribuut te staan.

Omdat voor de punt voorSmal staat, werkt deze methode op het in de variabele voorSmal opgeslagen object, op het daarin zittende element.

"aria-expanded": tussen de haakjes bij setAttribute() staat voor de komma tussen aanhalingstekens de naam van het attribuut dat wordt toegevoegd. In dit geval is dat aria-expanded.

Achter de komma staat de waarde van het attribuut dat wordt toegevoegd. Vaak is dat een vaste waarde, bijvoorbeeld een kleur voor een border. Maar hier is die op te geven waarde afhankelijk, van een eerdere waarde. Daarom is die waarde hier wat ingewikkelder.

voorSmal.getAttribute("aria-expanded"):

voorSmal.getAttribute(): dit is bijna hetzelfde als het hierboven besproken voorsmal.setAttribute(). Alleen is de gebruikte methode hier getAttribute(): vraag de waarde van een attribuut op.

"aria-expanded": tussen de haakjes bij getAttribute() staat voor de komma tussen aanhalingstekens de naam van het attribuut dat wordt opgevraagd. In dit geval is dat aria-expanded.

voorSmal.getAttribute("aria-expanded") bij elkaar: vraag de waarde van het in button.voor-smal opgeslagen attribuut aria-expanded op. Deze waarde kan alleen maar "true" of "false" zijn.

Als om een of andere reden het attribuut aria-expanded niet aanwezig is, of als er een andere waarde dan "true" of "false" aanwezig zou zijn, is dat geen ramp. Als het attribuut niet aanwezig is, gebeurt er gewoon niets.

Als het attribuut wel aanwezig is, maar de waarde is niet "true" of "false", werkt het gewoon. Er wordt namelijk later in de regel gekeken of de waarde wel of niet "true" is. Als je te veel whisky op had uit vreugde dat Rutte eindelijk weg is en je hebt als waarde "rutte-is-eindelijk-weg" opgegeven, moet je minder drinken als je feest wilt vieren. Maar wat het script betreft is "rutte-is-eindelijk-weg" niet hetzelfde als "true", en wat er dan verschillend is, maakt geen fluit uit.

===: wat hiervoor staat moet precies, echt helemaal precies, hetzelfde zijn, als wat hierachter staat. Om dat echt helemaal volkomen precies hetzelfde aan te geven, worden drie isgelijktekens gebruikt.

"true": wat voor de === staat moet hetzelfde zijn als wat hier staat: "true".

voorSmal.getAttribute("aria-expanded") === "true" bij elkaar: met het deel voor de === wordt de waarde van aria-expanded bij button.voor-smal opgevraagd. Die waarde wordt vergeleken met het deel achter de ===. Als die waarden beide "true" zijn, zijn ze hetzelfde. Als de opgevraagde waarde ook maar íéts anders is dan "true", zijn ze niet hetzelfde. Heel zwart-wit: wel of niet.

Het hele deel achter de komma van setAttribute() ziet er zo uit:

voorSmal.getAttribute("aria-expanded") === "true" ? "false" : "true")

Dit is de waarde die aria-expanded uiteindelijk moet krijgen. Dit deel bestaat eigenlijk maar uit vier delen. Als je die vier delen even door letters vervangt, wordt het een stuk overzichtelijker:

(a === "a" ? b : c)

Dit is een verkorte manier om een if else te beschrijven: áls iets zo is, doe dan iets, en doe anders iets anders. Dit kan worden gebruikt bij heel eenvoudige if's.

De voorwaarde voor de áls staat voor het vraagteken: a === "a". Als dat zo is, als die voorwaarde waar is, wordt de waarde van aria-expanded de waarde van wat tussen vraagteken en dubbele punt staat: b. Als de voorwaarde niet waar is, wordt de waarde van aria-expanded de waarde van wat achter de dubbele punt staat: c. De nieuwe waarde van aria-expanded hangt dus af van het resultaat van de vergelijking a === "a".

Nu de echte regel:

voorSmal.getAttribute("aria-expanded") === "true": dit is het deel voor de ?, bij de simpele voorstelling met letters hierboven is het a === "a". Het is de voorwaarde, die bepaalt welke waarde aria-expanded gaat krijgen.

Als het deel voor de ===, waarin de waarde van aria-expanded bij button.voor-smal wordt opgevraagd, "true" oplevert, is dat hetzelfde als wat achter de === staat: de voorwaarde is waar. Als de opgevraagde waarde bij aria-expanded iets anders is dan "true", is het niet hetzelfde als de "true" achter de === en is de voorwaarde niet waar.

?: voor het vraagteken staat de voorwaarde, waarop wordt getest. Achter het vraagteken staat, wat er moet gebeuren. Afhankelijk van of aan die voorwaarde wordt voldaan of niet, of de voorwaarde waar is of niet.

"false" : "true": als aan de voorwaarde voor de ? wordt voldaan, wordt het stukje voor de : uitgevoerd: als de waarde van aria-expanded bij button.voor-smal "true" is, gebruik dan "false".

Als aan de voorwaarde niet wordt voldaan, wordt het stukje achter de : uitgevoerd: gebruik dan "true".

De stukjes voor en na de : zijn hier heel eenvoudig, maar ze kunnen heel ingewikkeld zijn (hoewel het dan vaak leesbaarder is om een andere manier gebruiken).

voorSmal.getAttribute("aria-expanded") === "true" ? "false" : "true"): dit hele stuk is uiteindelijk terug te brengen tot: als de waarde van aria-expanded bij button.voor-smal "true" is, gebruik dan "false", en omgekeerd.

Je zou het haast vergeten, maar voorSmal.getAttribute("aria-expanded") === "true" ? "false" : "true") staat achter de komma bij setAttribute("aria-expanded", ...): het is de waarde die aan aria-expanded wordt gegeven. Die waarde is dus de hierboven gevonden "true" of "false", afhankelijk van de waarde die aria-expanded al had.

De hele regel in gewone taal: verander bij button#voor-smal-js de WAI-ARIA-code aria-expanded="false" in aria-expanded="true", of omgekeerd. Hierdoor gaat de attribuut-selector [aria-expanded="false"] of [aria-expanded="true"] werken. (Als je het aan button#voor-smal-js toegevoegde attribuut wilt bekijken, moet je niet in de gewone code, maar in de Gegenereerde code kijken.)

Deze regel is onderdeel van function verbergToonMenubalk(e) {, die wordt aangeroepen door een klik of aanraking van het scherm. Deze specifieke regel binnen die functie wordt vervolgens weer alleen uitgevoerd, als button.voor-smal is aangeraakt of -geklikt (dat is een eerder bij if (e.target.id === "voor-smal-js") { gestelde voorwaarde). Alles bij elkaar doet deze ingewikkelde instructie uiteindelijk iets heel simpels: met behulp van aria-expanded in smallere browservensters bij klikken op button.voor-smal de menubalk verbergen of tonen, met behulp van de waarde bij aria-expanded.

} else {

Deze regel is onderdeel van function verbergToonMenubalk(e) {

Deze else hoort bij een eerdere if. De code bij deze else wordt alleen uitgevoerd, als aan onderstaande voorwaarde is voldaan:

– De id van het element dat is aangeraakt of -geklikt mag niet 'voor-smal-js' zijn.

}: dit is de afsluitende accolade van de code die bij de hierboven genoemde if hoort. Die heeft dus eigenlijk niets met deze else te maken.

else: als de voorwaarde bij die if niet waar is, voer dan de code tussen de {} bij deze else uit. Er staat hier verder geen voorwaarde of zo: deze code wordt gewoon altijd uitgevoerd, als de voorwaarde bij de if niet waar is.

{: de code die wordt uitgevoerd, als aan deze else wordt voldaan, wordt tussen tussen accolades gezet. Het script weet dan, wat bij deze else hoort. Aan het eind van de code bij deze else staat de afsluitende }.

De hele regel in gewone taal: voer de code tussen de {} bij de else uit, als de id van het aangeraakte of -geklikte element niet 'voor-smal-js' is.

voorSmal.setAttribute("aria-expanded", "false");

Deze regel is onderdeel van function verbergToonMenubalk(e) {

Deze regel hoort bij de iets hierboven staande else De regel wordt alleen uitgevoerd, als aan onderstaande voorwaarde is voldaan:

– De id van het element dat is aangeraakt of -geklikt mag niet 'voor-smal-js' zijn.

voorSmal: in de variabele voorSmal is bij const voorSmal = document.querySelector(".voor-smal"); button.voor-smal opgeslagen in de vorm van een object. Een object is een bij elkaar horende verzameling van functies en andere code, waarin veel informatie over het opgeslagen element, de kinderen daarvan, enzovoort zit. Elk object heeft ook een aantal ingebakken functies, die gratis en voor niets gebruikt kunnen worden. Bij een object werkt een functie alleen een klein beetje anders dan een gewone functie, daarom heet een functie uit een object 'methode'.

setAttribute(): dit is zo'n methode. Met setAttribute() kun je een attribuut aan een element in de html toevoegen. Dat kan van alles zijn: tabindex, een WAI-ARIA-code, noem maar op. Tussen de haakjes komen de naam en de waarde van het attribuut te staan.

Omdat voor de punt voorSmal staat, werkt deze methode op het in de variabele voorSmal opgeslagen object, op het daarin zittende element.

("aria-expanded", "false"): voor de komma staat tussen aanhalingstekens de naam van het attribuut dat wordt toegevoegd, achter de komma staat tussen aanhalingstekens de waarde van het attribuut.

In dit geval levert dat aria-expanded="false" op:

<button id="voor-smal-js" class="voor-smal" type="button" aria-expanded"false">

;: de puntkomma geeft het eind van de regel aan. In gewone tekst zou je hier een punt gebruiken.

De hele regel in gewone taal: voeg aan button#voor-smal-js de WAI-ARIA-code aria-expanded="false" toe, waardoor de attribuut-selector [aria-expanded="false"] gaat werken. (Als je het aan button#voor-smal-js toegevoegde attribuut wilt bekijken, moet je niet in de gewone code, maar in de Gegenereerde code kijken.)

Als de id van het aangeraakte of -geklikte element niet 'voor-smal-js' is, dan is het aangeraakte of -geklikte element niet button#voor-smal-js, de <button> waarmee in smallere browservensters de menubalk kan worden getoond of verborgen. In dat geval is het scherm dus ergens buiten die <button> aangeraakt of -geklikt en moet de menubalk worden verborgen. Daar zorgt de attribuut-selector [aria-expanded="false"] voor.

buttons.forEach(item => {

item.setAttribute("aria-expanded", "false");

Deze twee regels zijn onderdeel van function verbergToonMenubalk(e) {

Deze twee regels zijn precies hetzelfde als die bij buttons.forEach(item => { en item.setAttribute("aria-expanded", "false");. Bij elk van de zes <button>'s in variabele buttons wordt de WAI-ARIA-code aria-expanded="false" aangebracht.

(Als je het aan de <button>'s toegevoegde attribuut wilt bekijken, moet je niet in de gewone code, maar in de Gegenereerde code kijken.)

Twee submenu's staan over elkaar heen.

Eerder is in deze functie geregeld dat in smallere browservensters bij het aanraken of -klikken van button.voor-smal de menubalk wordt getoond of verborgen. Als de menubalk wordt verborgen, terwijl een submenu is geopend, en daarna wordt de menubalk weer geopend, is dat submenu nog steeds zichtbaar.

Op de afbeelding is het tweede submenu geopend, daarna is de menubalk met een aanraking van de bovenste witte knop verborgen, en met nog een aanraking weer geopend. Het tweede submenu is nog steeds geopend, wat duidelijk niet de bedoeling is.

Om dit te voorkomen wordt in deze regels bij elk van de zes <button>'s het attribuut aria-expanded="false" toegevoegd, waardoor de submenu's sluiten. (Dat er maar hoogstens eentje is geopend, maakt niets uit: bij de <button>'s die dit attribuut al hebben, verandert er gewoon niets.)

function verbergToon(e) {

Ook deze functie is, zoals elke functie, weer een stukje bij elkaar horende code. Maar anders dan de buitenste functie wordt deze niet automatisch uitgevoerd. (Waarom de buitenste functie wel automatisch wordt uitgevoerd, is te vinden bij (function () {.)

De code in deze functie wordt alleen uitgevoerd, als de functie wordt aangeroepen. Dat aanroepen gebeurt, als het scherm ergens binnen ul.menubalk wordt aangeraakt of ‑geklikt. Het luisteren daarnaar wordt geregeld bij document.querySelector(".menubalk").addEventListener("click", verbergToon);.

De code in deze functie regelt, wat er gebeurt bij een aanraking of klik. Dat zijn twee dingen:

– Als een van de zes <button>'s met een submenu wordt aangeraakt of -geklikt, wordt het bij die <button> horende submenu getoond, als dat verborgen was. Als het al getoond werd, wordt het juist verborgen. Dit gebeurt door het bij de aangeraakte of ‑geklikte <button> veranderen van de WAI-ARIA-code aria-expanded="false" in aria-expanded="true", of juist omgekeerd.

– De vijf andere submenu's worden verborgen. Dit gebeurt door het toevoegen van aria-expanded="false" aan de andere vijf, niet aangeraakte of -geklikte, <button>'s.

function: het sleutelwoord waarmee het begin van een functie wordt aangegeven.

verbergToon: de naam van de functie. Als het beestje geen naam heeft, kun je het ook niet aanroepen en heb je er dus niets aan. (Dit klopt niet helemaal. JavaScript kent ook equivalenten van 'hé!', 'hé, jij daar!', 'hé, jij daar in die zwarte jas', en dergelijke, maar die worden hier niet gebruikt. En het is zo al ingewikkeld genoeg.)

Het is bij namen in JavaScript gebruikelijk om nieuwe woorden met een hoofdletter te beginnen, omdat spaties, koppeltekens, en dergelijke niet gebruikt mogen worden in een naam. In css zou je hier bijvoorbeeld verberg-toon kunnen gebruiken in plaats van verbergToon.

(e): die haakjes horen nou eenmaal zo na de naam van een functie. Behalve dat het gewoon zo hoort, kun je hier ook van alles in stoppen om door te geven aan de code in het binnenste van de functie. Zoals de plaats waar het scherm is aangeraakt of -geklikt. In dit geval wordt e doorgegeven.

In e zit een zogenaamd object, waarin onder andere informatie zit over welke toets is ingedrukt. Omdat het tussen de haakjes achter function () staat, is de informatie uit e door de code binnen de functie te gebruiken.

Een uitgebreidere omschrijving over dit soort objecten is te vinden bij (e).

{: geeft het begin van de code binnen de functie aan. Aan het eind van de functie staat een bijbehorende }.

e.stopPropagation();

Deze regel is onderdeel van function verbergToon(e) {

e: dit is het object, dat bij het aanroepen van de functie aan de code is doorgegeven. Hierin zit onder andere, welk element is aangeraakt of -geklikt (en nog veel meer informatie, maar dat wordt hier allemaal niet gebruikt). Los hiervan voegt JavaScript aan e allerlei methodes toe: functies binnen het object, waarmee je allerlei dingen kunt doen. Bij (e) staat een iets uitgebreidere beschrijving van dit soort objecten.

stopPropagation(): dit is zo'n hierboven genoemde methode. function verbergToon(e) { is aangeroepen met behulp van een eventlistener, die luisterde naar een aanraking of klik binnen ul.menubalk. Bij zo'n aanraking of klik wordt de code in eze functie uitgevoerd. Wat precies de bedoeling is.

Maar als de code van deze functie is afgehandeld, wordt vervolgens bij de ouder van ul.menubalk gekeken, of daar toevallig ook nog 'n eventlistener zit, die luistert naar een aanraking of klik. Die ouder is hier <nav>, en die heeft geen eventlistener.

Vervolgens wordt gekeken of bij de ouder van <nav> een eventlistener zit, die luistert naar een aanraking of klik. Die ouder is <body> en daar zit inderdaad zo'n eventlistener. Zo wordt elke voorouder afgewerkt, tot en met het buitenste element <html>, en als laatste document: het object waar de hele pagina in zit. In dit geval heeft alleen voorouder <body> een eventlistener.

<body> heeft dus zo'n eventlistener die luistert naar een aanraking of klik. En die verpest het volledig. Want die eventlistener bij <body> roept function verbergToonMenubalk(e) { aan, en die functie sluit alle submenu's. Terwijl de code in de functie hier juist het tonen en verbergen van submenu's regelt.

Als je een <button> met een submenu aanraakt of -klikt en het submenu wordt niet getoond, wordt het door die aanraking of klik getoond. Dat regelt de functie hier. Maar daarna wordt dus ook nog de functie bij <body> aangeroepen, die het submenu weer vrolijk sluit. Kortom: je kunt aanraken en klikken wat je wilt, het is onmogelijk om een submenu te tonen, want de functie bij <body> wint, omdat die als laatste wordt aangeroepen.

stopPropagation voorkomt dat bij de voorouders van een element met een eventlistener wordt gekeken, of daar ook nog een gelijksoortige eventlistener aanwezig is: de functie die bij een aanraking of klik van <body> wordt aangeroepen, wordt nu niet uitgevoerd.

for (let i = 0; i < buttons.length; i++) {

Deze regel is onderdeel van function verbergToon(e) {

for: doe iets met iets, zolang iets waar is. Nou, is dat lekker vaag of niet? Wat er waarmee moet gebeuren, komt later. Dat staat tussen de {} aan het eind van de regel. (De } staat in werkelijkheid niet aan het eind van de regel, maar lager, achter de code die bepaalt, wat er waarmee moet gebeuren.)

Hier eerst het deel dat ervoor zorgt dat elk van de zes in de vorm van een object in variabele buttons opgeslagen <button>'s met een submenu netjes aan de beurt komt.

(: met dit haakje opent het deel dat ervoor zorgt dat elke <button> aan de beurt komt. Aan het eind van de regel staat de bijbehorende ), waardoor het script weet dat dit het einde van dit deel van de code is.

let i = 0;: met het sleutelwoord let wordt aangegeven dat het erop volgende woord de naam van een 'variabele' is: de variabele wordt hier 'aangemaakt' of 'gedeclareerd'.

Gelijk na let volgt de naam van de variabele: 'i'. Een variabele is een soort portemonnee: er kan van alles in zitten, en de inhoud kan veranderen.

In i wordt dus iets opgeborgen. Omdat de variabele een naam heeft, kan de variabele worden aangeroepen bij deze naam. Net zoals je iemand die 'Marie' heet kunt aanroepen met 'Marie', ongeacht of Marie aardig, onaardig, muzikaal of arrogant is, ongeacht de 'inhoud' van Marie.

Omdat variabele i met het sleutelwoord let is aangemaakt, kan de variabele alleen worden gebruikt binnen de code bij deze for, binnen de { op deze regel en de } die het eind van de code bij deze for aangeeft.

Meer over variabelen is te vinden bij const buttons = document.querySelectorAll(".menubalk button");.

Met het isgelijkteken geef je in JavaScript aan dat in de voor het isgelijkteken staande variabele het resultaat van wat achter het isgelijkteken staat, moet worden opgeslagen. In dit geval is dat resultaat heel simpel: het is het getal 0.

De variabele heet hier 'i'. Deze variabele wordt als teller gebruikt (waarover later meer), en bij dit soort tellers is het gebruikelijk ze de korte naam 'i' (van 'index') te geven. Dat soort gewoontes maakt het voor mensen een stuk makkelijker elkaars code te lezen en te begrijpen.

Omdat i een variabele is, kan de waarde ervan veranderen, variëren. Wat later in de regel ook gaat gebeuren: elke keer als er een <button>-object is afgehandeld, wordt i met 1 verhoogd. Dat gebeurt in het laatste deel van de regel.

De ; geeft aan dat dit stukje code, waarin de variabele i een beginwaarde krijgt, hier eindigt.

i++: eerst het laatste deel van de regel, het middelste deel komt gelijk hieronder.

Elke keer als een in buttons zittend <button>-object is afgehandeld, 1 optellen bij i. Omdat programmeurs liederlijk lui zijn, wordt 'er 1 bij optellen' ingekort tot ++.

Als i 0 is, betekent i++: i wordt 0 + 1 ('1' dus).

Als i 1 is, betekent i++: i wordt 1 + 1 ('2' dus).

Als i 2 is, betekent i++: i wordt 2 + 1 ('3' dus).

Als i 5 is, betekent i++: i wordt 5 + 1 ('6' dus).

En meer dan 6 wordt het niet, vanwege redenen die gelijk hieronder staan.

(Op een pagina met een ander aantal <buttons>'s dan 6 zal eerder of later worden gestopt met het verhogen van i.)

i < buttons.length;: het middelste deel van de regel. In variabele buttons zijn eerder bij const buttons = document.querySelectorAll(".menubalk button"); de zes <button>'s uit ul.menubalk in de vorm van objecten opgeslagen.

In het sleutelwoord length zit het aantal in buttons opgeslagen elementen. Dat zijn er hier zes. buttons.length is hier dus eigenlijk gewoon het getal '6'.

i bevat een getal. Voordat het eerste <button>-object is verwerkt, is dat getal 0, want dat is hierboven opgegeven.

Het teken < betekent: kleiner dan.

De ; aan het einde geeft aan dat dit stukje code, de voorwaarde waaraan moet worden voldaan, hier eindigt.

In gewone taal staat hier: zolang teller i kleiner is dan buttons.length, kleiner dan 6.

Als niet meer aan deze voorwaarde wordt voldaan, als teller i niet meer kleiner is dan 6, stop dan met het uitvoeren van de code die tussen de {} van de for-lus staat. Omdat length even groot is als het aantal <button>-objecten in buttons, geeft dit een mogelijkheid om elk <button>-object precies één keer te verwerken. En er, als ze allemaal verwerkt zijn, mee te stoppen.

): dit haakje hoort bij de ( gelijk achter for. Tussen deze twee haakjes staat de code die ervoor zorgt dat elk <button>-object aan de beurt komt.

{: het laatste teken op de regel. Hiermee geef je aan dat hierna de code volgt die uitgevoerd moet worden. Tot nu toe is alleen gezorgd dat er bij elk <button>-object íéts moet gebeuren, maar nog niet wát. Dat wát volgt na deze {. Na de code die moet worden uitgevoerd staat nog een afsluitende }. Hiermee geef je aan dat het uitvoerende deel van de code bij de for-lus hier stopt.

Dat uitvoerende deel is op een nieuwe regel gezet, net zoals de afsluitende }. Dat soort dingen zijn informele afspraken, omdat het de code voor mensen leesbaarder maakt. Wat de computer betreft zou je alles ook achter elkaar kunnen zetten op één onwijs lange regel. Alleen is niet alleen die regel dan onwijs, ook de gemiddelde programmeur zou heel snel bijzonder onwijs worden, als alles op één lange regel wordt gezet.

Elke keer als een <button>-object is behandeld (met het stukje code tussen de {}, wat hieronder aan de beurt komt), wordt i door middel van i++ met 1 verhoogd.

Aan het begin heeft i de waarde 0. Na de eerste ronde heeft i de waarde 1. Na de tweede ronde heeft i de waarde 2. Na de derde ronde heeft i de waarde 3. Na de zesde ronde heeft i de waarde 6. Waarmee i niet meer kleiner is dan buttons.length, dan het aantal <button>-objecten.

Omdat niet meer aan de voorwaarde i < buttons.length wordt voldaan, wordt gestopt met het behandelen van de <button>-objecten. En dat komt goed uit, want alle <button>-objecten zijn allemaal precies één keer aan de beurt geweest. Niet meer, niet minder. Bij allemaal is de tussen de { } staande code één keer toegepast.

De code tussen de haakjes in gewone taal: zet teller i op 0 als de for-lus de eerste keer wordt uitgevoerd. Herhaal de for-lus zolang teller i lager is dan het aantal <button>-objecten. Verhoog teller i elke keer als de for-lus wordt uitgevoerd met 1.

if (buttons[i] !== e.target) {

Deze regel is onderdeel van function verbergToon(e) {

Deze regel zit binnen een for-lus en wordt voor elke <button> binnen ul.menubalk één keer uitgevoerd.

Als de <button> die aan de beurt is niet de <button> is die is aangeraakt of -geklikt.

if: dat betekent gewoon 'als': als er aan de voorwaarde hierachter is voldaan. Die voorwaarde staat tussen haakjes, omdat dat nou eenmaal zo hoort. Het is het hele deel tussen de twee buitenste haakjes achter de if.

buttons: in variabele buttons zitten in de vorm van objecten de zes <button>'s met submenu's.

[i]: in variabele buttons zitten zes objecten. In variabele i zit een teller, die van 0 tot 6 telt, zoals gelijk hierboven bij de for-lus is beschreven. Bij de eerste ronde van de for-lus is i 0, bij de tweede ronde is i 1, enzovoort. Bij de eerste ronde staat er dus eigenlijk buttons[0]: het eerste object, de eerste <button> uit buttons. Mensen beginnen meestal te tellen bij 1, maar een computer begint vaak te tellen bij 0. Vandaar dat buttons[0] het eerste object is.

Met behulp van deze teller i, die elke keer dat de for-lus wordt doorlopen met 1 wordt verhoogd, kunnen alle zes <button>'s met een submenu worden afgewerkt.

!==: als wat hiervoor staat niet helemaal precies, echt helemaal precies, hetzelfde is, als wat hierachter staat. Het uitroepteken betekent hier 'niet'.

e: dit is het object, dat bij het aanroepen van de functie aan de code is doorgegeven. Hierin zit onder andere, welk element is aangeraakt of -geklikt (en nog veel meer informatie, maar dat wordt hier allemaal niet gebruikt). Los hiervan voegt JavaScript aan e allerlei methodes toe: functies binnen het object, waarmee je allerlei dingen kunt doen. Bij (e) staat een iets uitgebreidere beschrijving van dit soort objecten.

target: dit is zo'n stukje informatie uit het hierboven genoemde object: hierin zit het element dat werd aangeraakt of -geklikt toen de functie werd aangeroepen. Hier is dat de <button> die werd aangeraakt of -geklikt.

Feitelijk is target ook weer een object. Hierdoor zit er allerlei informatie in over de <button> die aan de beurt is (en zou je ook van alles met die <button> kunnen doen, maar dat gebeurt hier niet).

De hele regel in gewone taal: als de <button> die aan de beurt is niet de <button> is die werd aangeraakt of -geklikt. Dan is het dus een van de andere vijf <button>'s.

buttons[i].setAttribute("aria-expanded", "false");

Deze regel is onderdeel van function verbergToon(e) {

Deze regel zit binnen een for-lus en wordt voor elke <button> binnen ul.menubalk één keer uitgevoerd.

Deze regel wordt alleen uitgevoerd, als aan deze eerder bij if gestelde voorwaarde is voldaan:

– De <button> is niet de aangeraakte of -geklikte <button> (het is een van de andere vijf <button>'s met een submenu).

buttons: in variabele buttons zitten in de vorm van objecten de zes <button>'s met submenu's.

[i]: in variabele buttons zitten zes objecten. In variabele i zit een teller, die van 0 tot 6 telt, zoals iets hierboven bij de for-lus wordt beschreven. Bij de eerste ronde van de for-lus is i 0, bij de tweede ronde is i 1, enzovoort. Bij de eerste ronde staat er dus eigenlijk buttons[0]: het eerste object, de eerste <button> uit buttons. Mensen beginnen meestal te tellen bij 1, maar een computer begint vaak te tellen bij 0. Vandaar dat buttons[0] het eerste object is.

Met behulp van deze teller i, die elke keer dat de for-lus wordt doorlopen met 1 wordt verhoogd, kunnen alle zes <button>'s met een submenu worden afgewerkt. Behalve de <button> die is aangeraakt of -geklikt, want dat was een voorwaarde van de if: deze regel geld niet voor die <button>

setAttribute("aria-expanded", "false"): bij de <button> die aan de beurt is het attribuut aria-expanded="false" toevoegen. Meer over setAttribute() is te vinden bij setAttribute().

De hele regel in gewone taal: voeg aan de <button>'s de WAI-ARIA-code aria-expanded="false" toe, waardoor de attribuut-selector [aria-expanded="false"] gaat werken. Een eventueel geopend submenu wordt hierdoor gesloten. (Als je het aan de <button>'s toegevoegde attribuut wilt bekijken, oet je niet in de gewone code, maar in de Gegenereerde code kijken.)

Omdat alleen de vijf <button>'s die níét zijn aangeraakt of -geklikt dit attribuut krijgen, worden alleen die bijbehorende vijf submenu's gesloten. De <button> die was aangeraakt of -geklikt wordt gelijk hieronder bij de else apart afgehandeld, want die moet mogelijk juist wel worden getoond.

(Overigens kon er maar hoogstens één submenu zijn geopend. Maar dat maakt niets uit: de vier <button>'s bij niet geopende submenu's hadden het attribuut aria-expanded="false" dan al, en daar verandert dan gewoon niets.)

} else {

Deze regel is onderdeel van function verbergToon(e) {

Deze regel zit binnen een for-lus en wordt voor elke <button> binnen ul.menubalk één keer uitgevoerd.

Deze else hoort bij een eerdere if. De code bij deze else wordt alleen uitgevoerd, als aan onderstaande voorwaarde is voldaan:

– De <button> die aan de beurt is, is de aangeraakte of -geklikte <button>.

}: dit is de afsluitende accolade van de code die bij de hierboven genoemde if hoort. Die heeft dus eigenlijk niets met deze else te maken.

else: als de voorwaarde bij die if niet waar is, voer dan de code tussen de {} bij deze else uit. Er staat hier verder geen voorwaarde of zo: deze code wordt gewoon altijd uitgevoerd, als de voorwaarde bij de if niet waar is.

{: de code die wordt uitgevoerd, als aan deze else wordt voldaan, wordt tussen accolades gezet. Het script weet dan, wat bij deze else hoort. Aan het eind van de code bij deze else staat de afsluitende }.

De hele regel in gewone taal: voer de code tussen de {} bij de else uit, als de <button> die aan de beurt is de aangeraakte of -geklikte <button> is.

e.target.setAttribute("aria-expanded", e.target.getAttribute("aria-expanded") === "true" ? "false" : "true");

Deze regel is onderdeel van function verbergToon(e) {

Deze regel zit binnen een for-lus en wordt voor elke <button> binnen ul.menubalk één keer uitgevoerd.

Deze regel hoort bij de iets hierboven staande else. De regel wordt alleen uitgevoerd, als aan onderstaande voorwaarde is voldaan:

– De <button> die aan de beurt is, is de aangeraakte of -geklikte <button>.

Deze regel is bijna hetzelfde als de eerdere bij voorSmal.setAttribute("aria-expanded", voorSmal.getAttribute("aria-expanded") === "true" ? "false" : "true");, waar een beschrijving van deze regel staat. Er is maar één verschil: die eerdere regel werkte voor variabele voorSmal, waarin in de vorm van een object button.voor-smal zit.

Deze regel werkt niet voor voorSmal, maar voor e.target, de aangeraakte of -geklikte <button>:

e: dit is het object, dat bij het aanroepen van de functie aan de code is doorgegeven. Hierin zit onder andere, welk element is aangeraakt of -geklikt (en nog veel meer informatie, maar dat wordt hier allemaal niet gebruikt). Los hiervan voegt JavaScript aan e allerlei methodes toe: functies binnen het object, waarmee je allerlei dingen kunt doen. Bij (e) staat een iets uitgebreidere beschrijving van dit soort objecten.

target: dit is zo'n stukje informatie uit het hierboven genoemde object: hierin zit het element dat werd aangeraakt of -geklikt toen de functie werd aangeroepen. Hier is dat de <button> die werd aangeraakt of -geklikt.

De hele regel in gewone taal: zoek de waarde van aria-expanded in de aangeraakte of -geklikte <button> op. Als die waarde 'true' is, verander dat dan in 'false', en omgekeerd. Hierdoor wordt het bij de <button> horende submenu getoond, als dat was verborgen. En als het getoond werd, wordt het juist verborgen. Oftewel: een aanraking of klik op een van de zes <button>'s met een submenu, verbergt of toont het submenu bij die <button>.

(Als je het veranderde attribuut wilt bekijken, moet je niet in de gewone code, maar in de Gegenereerde code kijken.)

function sluiten(e) {

Ook deze functie is, zoals elke functie, weer een stukje bij elkaar horende code. Maar anders dan de buitenste functie wordt deze niet automatisch uitgevoerd. (Waarom de buitenste functie wel automatisch wordt uitgevoerd, is te vinden bij (function () {.)

De code in deze functie wordt alleen uitgevoerd, als de functie wordt aangeroepen. Dat aanroepen gebeurt, als ergens een toets wordt ingedrukt. Het luisteren daarnaar wordt geregeld bij document.addEventListener("keydown", sluiten);.

De code in deze functie regelt, wat er gebeurt als een toets wordt ingedrukt. Dat zijn drie dingen:

– Er wordt gekeken of de ingedrukte toets Escape is. Als dat niet zo is, gebeurt er verder niets.

– Een eventueel geopend submenu wordt gesloten en de focus wordt op de bijbehorende <button> gezet. Dit gebeurt door het veranderen van de WAI-ARIA-code van aria-expanded="true" in aria-expanded="false".

– Als in smallere browservensters een submenu was geopend, sluit een tweede Escape de menubalk. Dit gebeurt door het veranderen van de WAI-ARIA-code van aria-expanded="true" in aria-expanded="false".

function: het sleutelwoord waarmee het begin van een functie wordt aangegeven.

sluiten: de naam van de functie. Als het beestje geen naam heeft, kun je het ook niet aanroepen en heb je er dus niets aan. (Dit klopt niet helemaal. JavaScript kent ook equivalenten van 'hé!', 'hé, jij daar!', 'hé, jij daar in die zwarte jas', en dergelijke, maar die worden hier niet gebruikt. En het is zo al ingewikkeld genoeg.)

(e): die haakjes horen nou eenmaal zo na de naam van een functie. Behalve dat het gewoon zo hoort, kun je hier ook van alles in stoppen om door te geven aan de code in het binnenste van de functie. Zoals de plaats waar het scherm is aangeraakt of -geklikt. In dit geval wordt e doorgegeven.

In e zit een zogenaamd object, waarin onder andere informatie zit over welke toets is ingedrukt. Omdat het tussen de haakjes achter function () staat, is de informatie uit e door de code binnen de functie te gebruiken.

Een uitgebreidere omschrijving over dit soort objecten is te vinden bij (e).

{: geeft het begin van de code binnen de functie aan. Aan het eind van de functie staat een bijbehorende }.

let subMenuOpen = false;

Deze regel is onderdeel van function sluiten(e) {

Met het sleutelwoord let wordt aangegeven dat het erop volgende woord de naam van een 'variabele' is: de variabele wordt hier 'aangemaakt' of 'gedeclareerd'.

Gelijk na let volgt de naam van de variabele: 'subMenuOpen'. Een variabele is een soort portemonnee: er kan van alles in zitten, en de inhoud kan veranderen.

In subMenuOpen wordt dus iets opgeborgen. Omdat de variabele een naam heeft, kan de variabele worden aangeroepen bij deze naam. Net zoals je iemand die 'Marie' heet kunt aanroepen met 'Marie', ongeacht of Marie aardig, onaardig, muzikaal of arrogant is, ongeacht de 'inhoud' van Marie.

Omdat variabele subMenuOpen met het sleutelwoord let is aangemaakt, kan de variabele alleen worden gebruikt binnen de function sluiten(e) {, de functie waarbinnen de variabele is aangemaakt. Meer over variabelen is te vinden bij const buttons = document.querySelectorAll(".menubalk button");.

Met het isgelijkteken geef je in JavaScript aan dat in de voor het isgelijkteken staande variabele het resultaat van wat achter het isgelijkteken staat, moet worden opgeslagen. In dit geval is dat resultaat heel simpel: false, 'onwaar'.

In subMenuOpen wordt bijgehouden, of een submenu is geopend of niet. In het begin van deze functie wordt met de waarde false aangegeven dat er geen submenu is geopend. Althans: daar wordt vanuit gegaan.

Omdat subMenuOpen een variabele is, kan de waarde ervan veranderen, variëren. Wat later in de functie mogelijk ook gaat gebeuren: als later in de functie blijkt dat er toch een submenu is geopend, wordt de waarde van subMenuOpen veranderd in true, 'waar'.

De waarde van subMenuOpen bepaalt, of in smallere browservensters een submenu moet worden gesloten (als er eentje was geopend), of dat de menubalk moet worden gesloten (als er geen submenu was geopend).

De ; geeft aan dat dit stukje code, waarin de variabele subMenuOpen een beginwaarde krijgt, hier eindigt.

if (e.key === "Escape") {

Deze regel is onderdeel van function sluiten(e) {

Als de Escape-toets is ingedrukt.

if: dat betekent gewoon 'als': als er aan de voorwaarde hierachter is voldaan. Die voorwaarde staat tussen haakjes, omdat dat nou eenmaal zo hoort. Het is het hele deel tussen de twee buitenste haakjes achter de if.

e: in e zit een zogenaamd object, waarin allerlei informatie zit. Bij function sluiten(e) {, de functie waar deze regel een onderdeel van is, is dit object aan de functie doorgegeven. Hierdoor kan de code in de functie de informatie uit dit object gebruiken.

key: dit is zo'n stukje informatie uit het hierboven genoemde object: hierin zit de naam van de ingedrukte toets.

===: wat hiervoor staat moet precies, echt helemaal precies, hetzelfde zijn, als wat hierachter staat. Om dat echt helemaal volkomen precies hetzelfde aan te geven, worden drie isgelijktekens gebruikt.

"Escape": dit is in JavaScript de naam van de Escape-toets. De inhoud van key, oftewel de naam van de ingedrukte toets moet 'Escape' zijn: de Escape-toets moet zijn ingedrukt.

{: de code die wordt uitgevoerd, als aan deze if-voorwaarde wordt voldaan, wordt tussen accolades gezet. Het script weet dan, wat bij deze if hoort. Aan het eind van de code bij deze if staat de afsluitende }.

De hele regel in gewone taal: als de Escape-toets is ingedrukt, voer dan de code tussen de {} achter de if uit.

buttons.forEach(item => {

Deze regel is onderdeel van function sluiten(e) {

Deze regel wordt alleen uitgevoerd, als aan deze eerder bij if gestelde voorwaarde is voldaan:

– De ingedrukte toets is Escape.

buttons: in de variabele buttons zijn bij const buttons = document.querySelectorAll(".menubalk button"); de zes <button>'s uit de menubalk opgeslagen in de vorm van een object. Een object is een bij elkaar horende verzameling van functies en andere code, waarin veel informatie over het opgeslagen element, de kinderen daarvan, enzovoort zit. Elk object heeft ook een aantal ingebakken functies, die gratis en voor niets gebruikt kunnen worden. Bij een object werkt een functie alleen een klein beetje anders dan een gewone functie, daarom heet een functie uit een object 'methode'.

forEach(item =>: doe iets met elk van de <button>'s in variabele buttons.

forEach(): dit is zo'n hierboven genoemde methode. Omdat voor de punt buttons staat, wordt de methode uitgevoerd op de <button>'s die in de vorm van een object in buttons zitten.

forEach zorgt ervoor dat elk van die <button>'s wordt verwerkt. Van de eerste tot en met de laatste. Steeds als een <button> in de is verwerkt, wordt met de volgende <button> verder gegaan, tot de hele lijst met <button>'s is verwerkt.

item: om de <button> die aan de beurt is te kunnen verwerken, moet het beestje 'n naam hebben: item. Dit is een veel gebruikte naam in dit soort bewerkingen. Ook element wordt vaak gebruikt. Maar je kunt desnoods ook rottigeKerstboom gebruiken als naam, alleen maak je je dan niet populair bij degene die ooit je code moet bekijken (en bij je toekomstige zelf van over twintig jaar, want zelfs begrijp je dit dan ook niet meer).

=>: een vereenvoudigde manier om te zeggen dat hierop een functie volgt. De functie wordt hier gelijk uitgevoerd en kan, omdat de functie geen naam heeft, niet worden aangeroepen: het is een 'anonieme' functie. Of eigenlijk: het is een speciaal soort anonieme functie, een 'arrow function'. De code die de functie moet uitvoeren staat tussen de {}. De { staat op deze regel, de bijbehorende } staat aan het eind van de functie.

if(item.getAttribute("aria-expanded") === "true") {

Deze regel is onderdeel van function sluiten(e) {

Deze regel wordt alleen uitgevoerd, als aan deze eerder bij if gestelde voorwaarde is voldaan:

– De ingedrukte toets is Escape.

Deze regel is onderdeel van de anonieme functie die begint bij buttons.forEach(item => {

Deze regel wordt voor elk item één keer uitgevoerd.

Als de <button> die aan de beurt is het attribuut aria-expanded = "true" heeft.

if: dat betekent gewoon 'als': als er aan de voorwaarde hierachter is voldaan. Die voorwaarde staat tussen haakjes, omdat dat nou eenmaal zo hoort. Het is het hele deel tussen de twee buitenste haakjes achter de if.

item: om iets te kunnen doen met elk van de in variabele buttons zittende <button>'s, is hierboven een naam gegeven aan de <button> die aan de beurt is: 'item'. Die naam wordt hier herhaald, zodat duidelijk is waarmee iets moet worden gedaan. item verwijst steeds naar een andere <button>, van de eerste tot de laatste, tot ze allemaal aan de beurt zijn geweest.

De <button>'s zijn opgeslagen in de vorm van een object. Een object is een bij elkaar horende verzameling van functies en andere code, waarin veel informatie over het opgeslagen element, de kinderen daarvan, enzovoort zit. Elk object heeft ook een aantal ingebakken functies, die gratis en voor niets gebruikt kunnen worden. Bij een object werkt een functie alleen een klein beetje anders dan een gewone functie, daarom heet een functie uit een object 'methode'.

getAttribute("aria-expanded"): dit is zo'n methode. Met getAttribute() kun je de waarde van een attribuut opvragen. Tussen de haakjes staat tussen aanhalingstekens de naam van het attribuut. Omdat voor de punt item staat, werkt deze methode op de in de vorm van een object in buttons opgeslagen <button>, die aan de beurt is.

De waarde van aria-expanded kan alleen maar "true" of "false" zijn. Als om een of andere reden het attribuut aria-expanded niet aanwezig is, of als er een andere waarde dan "true" of "false" aanwezig zou zijn, is dat geen ramp. Als het attribuut niet aanwezig is, gebeurt er gewoon niets.

Als het attribuut wel aanwezig is, maar de waarde is niet "true" of "false", werkt het gewoon. Er wordt namelijk later in de regel gekeken of de waarde wel of niet "true" is. Alles wat niet "true" is voldoet niet aan die voorwaarde, ongeacht of dat nou "false" is of iets anders.

===: wat hiervoor staat moet precies, echt helemaal precies, hetzelfde zijn, als wat hierachter staat. Om dat echt helemaal volkomen precies hetzelfde aan te geven, worden drie isgelijktekens gebruikt.

"true": wat voor de === staat moet hetzelfde zijn als wat hier staat: "true".

De hele regel in gewone taal: het deel voor de === wordt vergeleken met het deel achter de ===. Als item.getAttribute("aria-expanded") voor de === "true" oplevert, is het hetzelfde als de "true" na de ===. In alle andere gevallen is dat niet zo. Aan de voorwaarde van de if wordt dus alleen voldaan, als de <button> die aan de beurt is aria-expanded="true" heeft. In dat geval is het submenu bij deze <button> geopend.

item.closest("button").focus();

Deze regel is onderdeel van function sluiten(e) {

Deze regel wordt alleen uitgevoerd, als aan deze eerder bij if gestelde voorwaarde is voldaan:

– De ingedrukte toets is Escape.

Deze regel is onderdeel van de anonieme functie die begint bij buttons.forEach(item => {

Deze regel wordt alleen uitgevoerd, als aan deze eerder bij if gestelde voorwaarde is voldaan:

– De <button> die aan de beurt is, heeft het attribuut aria-expanded="true".

Deze regel wordt voor elk item één keer uitgevoerd.

item: om iets te kunnen doen met elk van de in variabele buttons zittende <button>'s, is bij buttons.forEach(item => { een naam gegeven aan de <button> die aan de beurt is: 'item'. Die naam wordt hier herhaald, zodat duidelijk is waarmee iets moet worden gedaan. item verwijst steeds naar een andere <button>, van de eerste tot de laatste, tot ze allemaal aan de beurt zijn geweest.

De <button>'s zijn opgeslagen in de vorm van een object. Een object is een bij elkaar horende verzameling van functies en andere code, waarin veel informatie over het opgeslagen element, de kinderen daarvan, enzovoort zit. Elk object heeft ook een aantal ingebakken functies, die gratis en voor niets gebruikt kunnen worden. Bij een object werkt een functie alleen een klein beetje anders dan een gewone functie, daarom heet een functie uit een object 'methode'.

closest("button"): dit is zo'n methode. Met closest() kun je zoeken naar een bepaald element. Waarnaar wordt gezocht, staat tussen aanhalingstekens binnen de haakjes. Hierbij is de syntax van het deel tussen de aanhalingstekens precies hetzelfde als bij een selector in css. Hier wordt naar een <button> gezocht. Net zoals je in css in een selector button {...} zou gebruiken.

Zou je naar een <span> zoeken die een eerste kind is, dan zou je de volgende regel gebruiken:

closest("span:first-child")

Precies zoals selectors in css werken.

closest() onderzoekt het element zelf en vervolgens de ouder ervan, de grootouder, de overgrootouder, enzovoort, tot een element is gevonden dat aan de definitie voldoet. In dit geval wordt naar een <button> gezocht.

Omdat voor de punt item staat, werkt deze methode op de in de vorm van een object in buttons opgeslagen <button>, die aan de beurt is. De <button> die aan de beurt is, voldoet dus zelf aan de definitie closest("button"). Een gelukkig element: het heeft zichzelf gevonden. En dat zonder jarenlange meditatie.

.focus(): dit is nog zo'n methode. Hiermee wordt de focus op het voor de punt staande deel gezet. Dat deel is hier de <button> die met het hierboven staande closest("button") is gevonden.

De hele regel in gewone taal: zet de focus op de <button> die aan de beurt is. Dit kan alleen een <button> zijn met het attribuut aria-expanded="true", want dat is een voorwaarde van de if, waarbinnen deze regel zit. Als niet aan die voorwaarde wordt voldaan, wordt deze regel niet uitgevoerd.

Als die <button> aria-expanded="true" heeft, is met behulp van css het bij deze <button> horende submenu geopend. De focus wordt door deze regel dus op de <button> gezet, die bij dat submenu hoort. Iets verderop in deze functie wordt dat submenu gesloten. Door de focus op de bijbehorende <button> te zetten, kunnen gebruikers van schermlezers en mensen die met behulp van het toetsenbord door het menu gaan, verdergaan vanaf het submenu dat was geopend. Bovendien wordt met behulp van css aangegeven, welke <button> de focus heeft, wat belangrijk is voor gebruikers van het toetsenbord.

subMenuOpen = true;

Deze regel is onderdeel van function sluiten(e) {

Deze regel wordt alleen uitgevoerd, als aan deze eerder bij if gestelde voorwaarde is voldaan:

– De ingedrukte toets is Escape.

Deze regel is onderdeel van de anonieme functie die begint bij buttons.forEach(item => {

Deze regel wordt alleen uitgevoerd, als aan deze eerder bij if gestelde voorwaarde is voldaan:

– De <button> die aan de beurt is, heeft het attribuut aria-expanded="true".

Deze regel wordt voor elk item één keer uitgevoerd.

subMenuOpen: aan het begin van deze functie is variabele subMenuOpen bij let subMenuOpen = false; aangemaakt en heeft daar de waarde false, 'onwaar', gekregen, want er werd vanuit gegaan dat er geen submenu open was.

=: hiermee geef je in JavaScript aan dat in de voor het isgelijkteken staande variabele het resultaat van wat achter het isgelijkteken staat moet worden opgeslagen.

true: deze regel wordt alleen uitgevoerd, als aan de eerder bij if gestelde voorwaarde is voldaan: de <button> die aan de beurt is, heeft het attribuut aria-expanded="true": het submenu bij de <button> is geopend. Daarom wordt hier de eerder gegeven waarde "false" veranderd in "true", 'waar'.

Verderop in de functie wordt deze waarde gebruikt om in smallere browservensters het submenu te sluiten. Als er geen submenu was geopend, houdt subMenuOpen de waarde false en wordt in plaats daarvan in smallere browservensters de menubalk gesloten.

item.setAttribute("aria-expanded", "false");

Deze regel is onderdeel van function sluiten(e) {

Deze regel wordt alleen uitgevoerd, als aan deze eerder bij if gestelde voorwaarde is voldaan:

– De ingedrukte toets is Escape.

Deze regel is onderdeel van de anonieme functie die begint bij buttons.forEach(item => {

Deze regel wordt voor elk item één keer uitgevoerd.

Deze regel voegt aan elke <button> die aan de beurt is het attribuut aria-expanded="false" toe. De regel werkt precies hetzelfde als die bij item.setAttribute("aria-expanded", "false");, waar een beschrijving staat.

(Als je het aan de <button>'s toegevoegde attribuut wilt bekijken, moet je niet in de gewone code, maar in de Gegenereerde code kijken.)

if (subMenuOpen === false) {

Deze regel is onderdeel van function sluiten(e) {

Deze regel wordt alleen uitgevoerd, als aan deze eerder bij if gestelde voorwaarde is voldaan:

– De ingedrukte toets is Escape.

Als de variabele subMenuOpen false is, was er bij het indrukken van de Escape-toets geen submenu geopend.

if: dat betekent gewoon 'als': als er aan de voorwaarde hierachter is voldaan. Die voorwaarde staat tussen haakjes, omdat dat nou eenmaal zo hoort. Het is het hele deel tussen de twee buitenste haakjes achter de if.

subMenuOpen: dit is de eerder bij let subMenuOpen = false; aangemaakte variabele, die bijhoudt of er een submenu was geopend of niet.

===: wat hiervoor staat moet precies, echt helemaal precies, hetzelfde zijn, als wat hierachter staat. Om dat echt helemaal volkomen precies hetzelfde aan te geven, worden drie isgelijktekens gebruikt.

false: als bij het indrukken van de Escape-toets een submenu was geopend, heeft subMenuOpen bij subMenuOpen = true; de waarde true gekregen. Als er geen submenu was geopend, heeft subMenuOpen nog steeds de waarde false.

{: de code die wordt uitgevoerd, als aan deze if-voorwaarde wordt voldaan, wordt tussen accolades gezet. Het script weet dan, wat bij deze if hoort. Aan het eind van de code bij deze if staat de afsluitende }.

De hele regel in gewone taal: als subMenuOpen de waarde false heeft, voer dan de code tussen de {} achter de if uit. Deze code wordt dus alleen uitgevoerd, als er bij het indrukken van de Escape-toets geen submenu was geopend.

Als in smallere browservensters een submenu was geopend tijdens het indrukken van de Escape-toets, wordt de code binnen deze if niet uitgevoerd. Het submenu is bij het indrukken van Escape gesloten, en dat is het. De menubalk blijft geopend.

Als er geen submenu was geopend bij het indrukken van Escape, wordt de code hieronder wel uitgevoerd: de menubalk wordt gesloten.

Voor bredere browservensters maakt dit allemaal niets uit, want daarin is de menubalk altijd zichtbaar.

voorSmal.setAttribute("aria-expanded", "false");

Deze regel is onderdeel van function sluiten(e) {

Deze regel wordt alleen uitgevoerd, als aan deze eerder bij if gestelde voorwaarde is voldaan:

– De ingedrukte toets is Escape.

Deze regel wordt alleen uitgevoerd, als aan deze eerder bij if gestelde voorwaarde is voldaan:

– variabele subMenuOpen is false (bij het indrukken van Escape was er geen submenu geopend).

Voeg aan button.voor-smal de WAI-ARIA-code aria-expanded="false" toe, waardoor de attribuut-selector [aria-expanded="false"] gaat werken en de menubalk wordt verborgen. (Als je het aan de <button> toegevoegde attribuut wilt bekijken, moet je niet in de gewone code, maar in de Gegenereerde code kijken.)

Deze regel is hetzelfde als die bij voorSmal.setAttribute("aria-expanded", "false";, waar een uitgebreidere beschrijving staat.

voorSmal.focus();

Deze regel is onderdeel van function sluiten(e) {

Deze regel wordt alleen uitgevoerd, als aan deze eerder bij if gestelde voorwaarde is voldaan:

– De ingedrukte toets is Escape.

Deze regel wordt alleen uitgevoerd, als aan deze eerder bij if gestelde voorwaarde is voldaan:

– variabele subMenuOpen is false (bij het indrukken van Escape was er geen submenu geopend).

voorSmal: in de variabele voorSmal is bij const voorSmal = document.querySelector(".voor-smal"); button.voor-smal opgeslagen in de vorm van een object. Een object is een bij elkaar horende verzameling van functies en andere code, waarin veel informatie over het opgeslagen element, de kinderen daarvan, enzovoort zit. Elk object heeft ook een aantal ingebakken functies, die gratis en voor niets gebruikt kunnen worden. Bij een object werkt een functie alleen een klein beetje anders dan een gewone functie, daarom heet een functie uit een object 'methode'.

.focus(): dit is zo'n methode. Hiermee wordt de focus op het voor de punt staande deel gezet. Dat deel is hier de <button>, waarmee in smallere browservensters de menubalk wordt getoond of verborgen: button.voor-smal.

De hele regel in gewone taal: zet de focus op de button.voor-smal.

Door bij het sluiten van de menubalk de focus op de <button> te zetten, waarmee de menubalk getoond en verborgen kan worden, kunnen gebruikers van schermlezers en mensen die met behulp van het toetsenbord door het menu gaan, verdergaan vanaf die <button> Bovendien wordt met behulp van css aangegeven, welke <button> de focus heeft, wat belangrijk is voor gebruikers van het toetsenbord.