Uitleg horizontaal responsief uitklapmenu, tot vier niveaus diep
Laatst aangepast: .
Korte omschrijving
Als je met de muis over een van de knoppen in de menubalk hovert of een knop aanraakt, opent een submenu. (In smallere vensters moet je eerst de menubalk aanraken om die knoppen te tonen) De items in dat submenu zijn een link of een kop met weer een dieper submenu.
BELANGRIJK
Deze uitleg hoort bij het voorbeeld dat in de download zit. Het voorbeeld uit de download verschilt behoorlijk van het voorbeeld hier op de site. Op de site is ook een menu aanwezig, en het menu uit het voorbeeld en dat van de site vonden elkaar helemaal niet aardig. Om te voorkomen dat ze voortdurend elkaar hinderden, moest de code van het voorbeeld op de site nogal worden aangepast.
Normaal genomen gaat het om kleine verschillen, maar in dit geval is dat niet zo. Het voorbeeld in de download is precies hetzelfde, als wat in de uitleg wordt behandeld. Als je het hele voorbeeld downloadt (bovenaan de pagina), speelt dit dus niet.
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 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
In principe werkt dit menu. Maar als je ziet, hoe ingewikkeld het is om het overál te laten werken, kun je je afvragen, of dit soort menu's nog wel handig is. Er zijn steeds meer mensen die adviseren dit soort menu's uit de Gouden Muistijd niet meer te gebruiken.
Maar het was wel 'n aardige uitdaging dit menu om te bouwen voor mobiel en om het toegankelijker te maken. Dus vandaar dat het hier toch staat.
En áls je zo'n soort menu wilt gebruiken, werkt dit absoluut beter dan het overgrote deel van de uitklapmenu's die op internet zijn te vinden.
Als je zo'n uitklapmenu wilt maken, stop er dan in ieder geval minder knoppen in. In dit menu zitten onwijs veel knoppen, zodat je kunt zien hoe het in allerlei omstandigheden werkt. Maar als je echt zoveel knoppen nodig hebt, kun je waarschijnlijk beter de structuur van je site aanpassen. Of biologisch tuinder worden. Of zoiets.
Met behulp van JavaScript worden twee classes aan <body> toegevoegd en verwijderd: 'xgeen-jsx' en 'xschermlezerx'. Deze namen zijn nogal vreemd. Dit verkleint de kans dat er al classes met deze naam in gebruik zijn. (Overigens zijn deze namen simpel aan te passen in het script.)
Veel selectors beginnen met nav. Meestal is dit eigenlijk niet nodig. Maar omdat veel elementen uit het menu heel veel voorkomen (<ul>, <li>, <a>, enzovoort), is het veiliger de werking van de selector te beperken tot binnen het uitklapmenu. Vandaar de nav aan het begin.
Bij selectors met id's en class-namen speelt dit niet of veel minder, daarom ontbreekt daar de nav aan het begin van de selector vaak.
Met alleen de essentiële css (de css die absoluut nodig is om het voorbeeld te laten werken), werkt alles wel. Maar omdat het grootste deel van de opmaak dan wegvalt, ziet het er niet uit.
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 (Kubuntu). 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
Bij de vorige versie van dit voorbeeld had je nog weinig touchscreens. Dit menu was dan ook niet aangepast voor touchscreens. Als je geluk had, werkte het. En als je pech had, werkte het niet. Zo heerlijk simpel kan de wereld zijn.
Wat betreft toegankelijkheid beperkte het zich tot de opmerking dat het voor gebruikers van de Tab-toets slecht toegankelijk was. Inmiddels wordt er veel meer aandacht aan toegankelijkheid besteed.
Het oorspronkelijke menu werkte prima op de desktop. Als je met een muis over een knop hoverde, opende een submenu met links. Dit werkt eigenlijk nog steeds hetzelfde. Alleen is de code ongeveer gehalveerd, omdat Internet Explorer 6 niet meer wordt ondersteund. (Dat de code uiteindelijk toch is toegenomen, ligt aan de ondersteuning van kleine schermen, touchscreens, toetsenbord, schermlezers, en dergelijke. En aan het toevoegen van wat toeters en bellen en het grotere aantal items in het menu.)
Voor gebruikers van de muis hoefde er eigenlijk weinig te veranderen. Het uiterlijk is wat opgeleukt, maar dat is het zo'n beetje.
Met de muis was dit menu redelijk goed te bedienen. Maar een muis kun je vrij precies richten. Op een touchscreen worden vingers gebruikt, en die zijn veel minder nauwkeurig. Daarom zijn de knoppen een stuk groter gemaakt. Niet alleen snoezige kleuters, maar ook minder snoezige bouwvakkers kunnen het menu nu bedienen. Wat wel prettig is, want 'n boze kleuter is niet zo erg, maar 'n woeste bouwvakker die de verkeerde site opende, waardoor hij de verkeerde bouwtekening heeft gebruikt en de parkeergarage zojuist is ingestort, die wil je toch liever niet achter je aan hebben.
Voor oudere versies van iOS is een heel klein beetje JavaScript gebruikt, omdat het menu bij aanraking anders niet opent (behalve in Firefox En Opera Mini). Voor Internet Explorer en Edge op touchscreens is om dezelfde reden een aantal WAI-ARIA-codes toegevoegd.
Zo'n uitklapmenu is leuk in grotere browservensters, maar op 'n smartphone leidt het slechts tot schuimbekken en razernij, omdat de meeste knoppen onbereikbaar zijn. Ze passen gewoon niet binnen het venster. Op sommige smartphones kun je de knoppen horizontaal scrollen, maar dat is echt beestachtig lastig. Bovendien sluiten ze dan soms door het aanraken, of een link wordt voortijdig gevolgd.
Daarom ziet het menu er in browservensters smaller dan 760 px heel anders uit. De hele menubalk is vervangen door één knop. Als je die knop aanraakt, opent het eerste niveau van het menu. Dat zijn de zes knoppen die in bredere vensters naast elkaar in de menubalk zitten. Alleen zitten ze nu niet naast elkaar, maar onder elkaar.
Pijltjes geven aan dat er submenu's aanwezig zijn. Als je zo'n pijltje aanraakt, opent het submenu, waarbij het pijltje van richting verandert. Binnen dat submenu kunnen links zitten, maar ook een nog dieper submenu. Als er niet wordt gezoomd, past het geheel makkelijk binnen de breedte van een smalle smartphone. In de hoogte kan het menu worden gescrold, zodat alle knoppen bereikbaar zijn.
Mensen die geen muis, maar de Tab-toets gebruiken om links, tekstvelden, knoppen, en dergelijke langs te gaan, hebben niets aan een uitklapmenu dat met een muis wordt bediend. Je kunt weliswaar alle 160 koppen en links één voor één aflopen met de Tab-toets, maar erg vrolijk wordt je daar niet van. Als je Enter indrukt, wordt de link gevolgd, maar je ziet op geen enkele manier, welke link focus heeft.
Ook de bestemming van de link is niet te zien. Onderin het browservenster wordt wel de naam van de pagina of het bestand achter de link getoond, maar als de naam van dat bestand iets is als 'ghx336.html', wordt alleen een astronoom daar nog blij van.
Kortom: zonder aanpassingen is dit menu volledig onbruikbaar voor mensen die een muis niet kunnen of willen gebruiken.
Als de Tab-toets de eerste keer wordt ingedrukt, opent daarom een klein venstertje. Dit venstertje sluit alleen weer, als Enter of de Tab-toets wordt ingedrukt. Bij Enter wordt het menu volledig gepasseerd en wordt gelijk naar de eigenlijke inhoud van de pagina gegaan. Als echter de Tab-toets wordt ingedrukt, wordt het menu volledig geopend.
Door het gebruik van de pijltjestoetsen kan het menu met grotere stappen worden doorlopen door bijvoorbeeld alle submenu's over te slaan. Door Escape in te drukken, sluit het menu weer. Met een speciale toetscombinatie kan het menu overal op de pagina weer worden geopend, zonder dat de gebruiker eerst weer helemaal naar dat eerste venstertje hoeft te tabben.
Die pijltjestoetsen werken met behulp van JavaScript. Zonder JavaScript is het menu nog steeds te gebruiken met de Tab-toets, maar dan werken de pijltjestoetsen en dergelijke niet.
Voor schermlezers ligt het weer anders. Alle schermlezers lezen een menu voor, ook als dit onzichtbaar buiten het venster van de browser staat. Maar niet alle schermlezers kunnen vervolgens een link volgen. Voor TalkBack op Android moet minimaal een plekje van 1 x 1 px van een link binnen het venster staan, niet verborgen onder een ander element. (Meer hierover is te vinden bij Probleem: in TalkBack op Android moet een link en dergelijke binnen het venster staan.)
Om goed te werken in schermlezers moet het menu dus volledig binnen het venster van de browser staan. Om dat voor elkaar te krijgen, staat buiten het venster een stukje tekst. Op het scherm zie je hier niets van, maar een schermlezer leest het gewoon voor. In deze tekst staat dat de gebruiker een aankruisvakje moet aanvinken om het menu toegankelijk voor schermlezers te maken.
Maar ook dat aankruisvakje moet binnen het browservenster staan en minimaal 1 x 1 px groot zijn, anders kun je het in TalkBack niet aanvinken. Dat is op zich niet zo ingewikkeld, maar als een argeloze bezoeker toevallig het scherm op die ene pixel aanraakt of -klikt, krijgt die 'n halve hartverzakking: het hele menu inclusief alle submenu's floept open.
Daarom is een tweede aankruisvakje gebruikt. Dit komt pas binnen het venster van de browser te staan, als het eerste aankruisvakje is aangevinkt. De kans dat iemand per ongeluk die twee plaatsen aanraakt, en dan ook nog in de juiste volgorde, is nihil. Tijdens het testen is het in ieder geval nooit gebeurd.
Nu kan het menu worden geopend voor schermlezers, terwijl andere gebruikers er geen last van hebben.
Het menu is opgebouwd uit geneste <ul>'s en boven elk submenu staat een <h>. Hierdoor kunnen schermlezers op de gebruikelijke manier snel door het menu navigeren. Bovendien zijn er ook nog oriëntatiepunten zoals <nav> en <main>.
De op schermlezers gerichte tekst en aankruisvakjes zijn de eerste niet voor schermlezers verborgen onderdelen op de pagina. Het zijn ook de eerste onderdelen binnen een <header> die bij <body> hoort. Bovendien is het begin van de tekst in een <h2> gezet. Welke methode de gebruiker van een schermlezer ook gebruikt om te navigeren, de tekst voor de schermlezers wordt altijd als eerste voorgelezen.
Omdat schermlezers altijd simpel naar het begin van de pagina kunnen, en dus naar de aankruisvakjes om het menu voor schermlezers te openen, is geen speciale toetscombinatie aangebracht om het menu later alsnog te kunnen openen.
Zonder JavaScript werkt het menu niet voor schermlezers. In theorie zou je het kunnen laten werken op een soortgelijke manier als voor gebruikers van de Tab-toets, als die JavaScript hebben uitstaan. Maar de css zou hierdoor enorm in omvang toenemen. Als JavaScript uitstaat, wordt een waarschuwing toegevoegd dat het menu niet of heel lastig werkt in schermlezers.
Omdat in de css en het JavaScript de positie van de <li>'s niet vastligt, maar wordt bepaald met dingen als :last-of-type, kan het aantal knoppen en items worden aangepast. (Uiteraard moet je dan wel zaken als de breedte van het menu en dergelijke aanpassen, anders passen dingen gewoon niet meer.)
Voorwaarden waaraan html en css moeten voldoen
Voor dit menu wordt gebruik gemaakt van JavaScript.
Zonder JavaScript werkt dit menu niet in iOS 9: de submenu's openen gewoon niet, behalve in Firefox en Opera Mini. In iOS 11 werkt het wel zonder script. (iOS 10 is onduidelijk, want daar wordt niet op getest.)
Voor gebruikers van de Tab-toets werkt het menu wel zonder JavaScript, maar de pijltjestoetsen om snel door het menu te navigeren werken dan niet.
Voor de meeste schermlezers is JavaScript absoluut noodzakelijk. Alle schermlezers lezen de links voor, hoewel je die niet ziet. Dat is uiterst onhandig. (Niet alle gebruikers van schermlezers zijn volledig blind.) Voor TalkBack op Android echter moet minimaal een plekje van 1 x 1 px van een link binnen het venster staan, niet verborgen onder een ander element. Hierdoor werkt dit menu niet in TalkBack, als JavaScript uitstaat. (Meer hierover is te vinden bij Probleem: in TalkBack op Android moet een link en dergelijke binnen het venster staan.)
Voor alle hierboven niet genoemde systemen en programma's is JavaScript niet nodig.
Om dit script goed te laten werken, moet de html aan een aantal voorwaarden voldoen. Dit lijkt mogelijk een wat lange lijst, maar het gaat om simpele voorwaarden. Voor een groot deel zijn dat al gebruikelijke en goede manieren om een navigatiemenu op te zetten, zoals het gebruik van <nav>, <ul> en <li>.
Structuur
Onderstaande ziet er mogelijk wat ingewikkeld uit. Als je de html erbij pakt, zul je zien dat dat enorm meevalt.
Het menu moet in een <nav id="uitklapmenu"> zitten. Een ander element is niet toegestaan. (Overigens is het altijd een goed idee om zo'n menu in een <nav> te zetten.)
De <nav> mag precies één <ul> als direct kind hebben. (Alle andere <ul>'s met submenu's zijn weer nakomelingen van de <li>'s in die ene <ul>.)
Elk submenu moet in een eigen <ul> zitten. Elke kop en elke link moeten in een eigen <li> zitten. Ook dit zijn weer gebruikelijke technieken.
De <li>'s waarbinnen een <ul> met een submenu staat, mogen geen link, geen <a> bevatten. Dit geldt ook voor de <li>'s die zichtbaar zijn in de menubalk. In het voorbeeld bevatten deze <li>'s een <h>, die gelijk het niveau van het submenu aangeeft.
(Het gebruik van een <h> maakt het gebruikers van schermlezers mogelijk snel van <h> naar <h> te springen, zodat ze niet alle 160 links en koppen hoeven te horen. Bovendien kun je hierin het onderwerp van het submenu kwijt.)
Als een <li> een submenu heeft, moet gelijk onder die <li> een <input type="checkbox"> zitten, met gelijk daaronder een bijbehorende <label>.
Elke link (elke <a>) moet als eerste element binnen een <li> zitten. Binnen de <a> kunnen gewoon <span>'s en dergelijke worden gebruikt, als de <a> maar het eerste element binnen de <li> is. Met onderstaande regel kan het script overweg, want de <a> is het eerste element binnen de <li>:
<body> moet een class="xgeen-jsx" bevatten: <body class="xgeen-jsx">. Deze class wordt verwijderd, als JavaScript aanstaat. Dit maakt het gebruik van selectors mogelijk, die alleen werken als JavaScript uitstaat. Hiernaast kunnen gewoon andere classes en/of een id bij <body> worden gebruikt.
Als je een andere class wilt gebruiken in de html, moet je die ook in het script en in de css wijzigen.
(Om het verwijderen van 'xgeen-jsx' te zien moet je niet de gewone broncode, maar de Gegenereerde code bekijken.)
In de html zit twee keer een <input type="checkbox"> voor schermlezers. Het script gaat ervan uit dat het eerste aankruisvakje een id="checkbox-voor-schermlezers-1" heeft, en de tweede id="checkbox-voor-schermlezers-2". Als je andere id's wilt gebruiken in de html, moet je die ook in het script en in de css wijzigen.
De <nav> waar het menu in zit, moet een id="uitklapmenu" hebben. Als je 'n andere id wilt gebruiken in de html, moet je de id ook in het script veranderen
Als na het openen van de pagina de Tab-toets wordt ingedrukt, krijgen gebruikers van de Tab-toets de mogelijkheid het menu volledig te openen. De tekst die hierbij verschijnt staat in <p>'s, die weer binnen een <a> met id="voor-tab" zitten.
Het enige verplichte hier is de id="voor-tab" bij de <a>. Als je deze id in de html wilt veranderen, moet je die ook in het script en in de css veranderen
Om het menu volledig te openen voor gebruikers van de Tab-toets en schermlezers, wordt gebruik gemaakt van een <input type="checkbox"> Dit aankruisvakje moet een id="checkbox-open-menu" hebben. Als je die id in de html wilt veranderen, moet je deze ook aanpassen in het script en in de css.
Aan het begin van de eigenlijke inhoud van de pagina (hier is dat Latijnse flauwekultekst) moet een element met id="content" zitten. In het voorbeeld is dat <main id="content">, maar het mag ook een andere element zijn. Als je deze id in de html wilt veranderen, moet je deze ook in het script aanpassen.
Het element met id="content" moet focus kunnen krijgen. Meer hierover staat iets lager onder het kopje Tabindex.
Om in browservensters smaller dan 760 px het menu te openen, wordt een <input type="checkbox"> gebruikt. Dit aankruisvakje moet een id="checkbox-voor-smal" hebben. Als je die id in de html wilt veranderen, moet je die ook in het script en in de css aanpassen.
In de css wordt regelmatig in selectors 'xschermlezerx' gebruikt. De class 'xschermlezerx' wordt aan <body> toegevoegd of verwijderd door het script. Als je een andere naam wilt gebruiken, moet je deze aanpassen in de css en in het script.
Tabindex
Op een fors aantal plaatsen wordt in de html het attribuut tabindex gebruikt. Dit attribuut wordt besproken bij Tabindex en Tab-toets. Je kunt ook in de html naar 'tabindex=' zoeken. Meestal zal gelijk duidelijk zijn, waarom dit attribuut wordt gebruikt.
Dit attribuut is niet absoluut noodzakelijk, maar maakt het leven voor gebruikers van de Tab-toets wel 'n stuk aangenamer.
Gebruikers van de Tab-toets kunnen met Escape in één keer naar het element met id="content" toe. In het voorbeeld is dat <main> met Latijnse flauwekultekst.
Het element met id="content" moet wel focus kunnen krijgen, dus het moet een <a>, een <input>, of een soortgelijk element zijn. Als dat niet zo is, kun je aan het element het attribuut tabindex="0" of tabindex="-1" toevoegen. In het voorbeeld is dat ook gedaan:
Elke <li> met een kopregel (een <h>) moet tabindex="0" hebben. Hierdoor kunnen ook gebruikers van de Tab-toets deze <li>'s zonder link bezoeken, wat een rustiger beeld oplevert, dan wanneer alleen de echte links door de Tab-toets worden bezocht.
WAI-ARIA-codes
Op een aantal plaatsen wordt voor schermlezers aria-hidden="true", gebruikt. Dit soort codes wordt besproken bij WAI-ARIA-codes. Je kunt ook in de html naar 'aria-hidden' zoeken. Meestal zal gelijk duidelijk zijn, waarom een bepaald element voor schermlezers wordt verborgen.
Daarnaast worden op een aantal plaatsen de codes aria-haspopup="true" en aria-haspopup="false" gebruikt. Deze hebben hier eigenlijk geen functie, maar zijn nodig voor Internet Explorer 11 en Edge op touchscreens. Ook deze codes worden besproken bij WAI-ARIA-codes.
aria-hidden is niet absoluut noodzakelijk voor een goede werking, maar het voorkomt veel verwarring voor gebruikers van schermlezers. aria-haspopup is wel noodzakelijk voor Internet Explorer en Edge op touchscreens, omdat de submenu's anders stomweg niet openen.
De voorvoegsels -moz-, -ms- en -webkit-
Voordat een nieuwe css-eigenschap wordt ingevoerd, is er in de regel een experimentele fase. Browsers passen het dan al toe, maar met een aangepaste naam. Tijdens deze fase kunnen problemen worden opgelost en worden veldslagen uitgevochten, over hoe de standaard precies moet worden toegepast.
Als iedereen het overal over eens is en alle problemen zijn opgelost, wordt de officiële naam uit de standaard gebruikt.
De belangrijkste browsers hebben elk een eigen voorvoegsel:
Firefox: -moz-, naar de maker: Mozilla.
Op webkit gebaseerde browsers, zoals Google Chrome, Opera, Safari en Android browser: -webkit-.
(Google Chrome is van webkit overgestapt op een eigen weergave-machine: Blink. Blink gaat geen voorvoegsels gebruiken. Het is echter een aftakking van webkit, dus het zal nog wel even duren voor -webkit- hier helemaal uit is verdwenen. Ook Opera gebruikt Blink.)
Internet Explorer: -ms-, naar de maker: Microsoft. (Edge gebruikt geen voorvoegsels, maar vanwege compatibiliteit met oudere sites kunnen er nog wat aanwezig zijn.)
Zodra de experimentele fase voorbij is, wordt het voorvoegsel weggelaten. Omdat dat moment niet bij alle browsers hetzelfde is, zet je nu ook al de officiële naam erbij. Deze wordt als laatste opgegeven. Bijvoorbeeld Android browser herkent -webkit-linear-gradient. Zodra Android browser linear-gradient gaat herkennen, zal dit -webkit-linear-gradient overrulen, omdat het er later in staat. Dat ze er beide in staan, is dus geen enkel probleem.
In dit voorbeeld worden appearance, transform, -ms-user-select, -webkit-overflow-scrolling, -webkit-animation en @-webkit-keyframes gebruikt.
Deze eigenschap is alleen nodig voor Internet Explorer en Edge op touchscreens op Windows. Voor andere browsers en systemen is deze overbodig, daarom wordt alleen de vorm met het voorvoegsel -ms- gebruikt.
-webkit-overflow-scrolling:
Deze eigenschap is alleen nodig op iOS in browservensters smaller dan 760 px. Op andere systemen is deze eigenschap overbodig, en bovendien werkt hij daar niet. Daarom is alleen de vorm met het voorvoegsel -webkit- gebruikt.
-webkit-animation en @-webkit-keyframes
Deze twee eigenschappen worden alleen maar gebruikt om een bug in sommige oudere op webkit gebaseerde browsers te repareren. Daarom hoeft alleen maar op browsers gelet te worden, die op webkit zijn gebaseerd. In dit geval is daarom het volgende voldoende:
{-webkit-animation: ...;}
Hetzelfde geldt voor @keyframes:
@-webkit-keyframes {...}
In nieuwere browsers is deze bug niet meer aanwezig, dus de versie zonder voorvoegsel hoeft in dit geval helemaal niet gebruikt te worden.
(In het algemeen is het een bijzonder slechte gewoonte om van een eigenschap alleen één bepaalde versie te gebruiken. Dit gebeurt nogal eens voor iOS, waarmee Apple actief wordt geholpen om sites en dergelijke ontoegankelijk te maken voor andere browsers dan Safari. Ontwikkelaars die dit doen, werken mee aan de totstandkoming van eenzelfde wantoestand als in het verleden met het monopolie van Internet Explorer 6 heeft bestaan.
Maar in dit geval maakt het niet uit, omdat het alleen om een bug gaat. Andere browsers hebben deze css helemaal niet nodig.)
Inmiddels is de algemene mening dat 'vendor prefixes', zoals deze voorvoegsels in het Engels heten, geen groot succes zijn. Eén van de grootste problemen: veel sitemakers gebruiken alleen de -webkit-variant. Daar kwamen ze in het verleden nog mee weg, omdat Apple op mobiel zo'n beetje 'n monopolie had. Inmiddels is dat niet meer zo, maar deze gewoonte bestaat nog steeds. Waardoor 'n site alleen in op webkit georiënteerde browsers goed is te bekijken.
Dit is zo'n groot probleem dat andere browsers soms de variant met -webkit- ook maar zijn gaan implementeren, naast de standaard. Want als 'n site het niet goed doet in 'n bepaalde browser, krijgt in de regel niet de site maar de browser de schuld.
Vanwege alle problemen met 'vendor prefixes' worden deze door steeds meer browsers niet meer gebruikt. Nieuwe, experimentele css-eigenschappen zitten inmiddels in bijvoorbeeld Firefox, Google Chrome en Safari achter een zogenaamde vlag: de gebruiker moet iets veranderen in de instellingen, waarna de eigenschap gebruikt (en getest) kan worden. Als alles werkt, zoals het hoort te werken, schakelt de browsermaker de vlag standaard in.
Voorlopig zijn we echter nog niet van deze voorvoegsels af. Als je ze gebruikt, gebruik dan álle varianten, en eindig met de variant zonder voorvoegsel, zoals die uiteindelijk ooit gebruikt gaat worden. Als je alleen de -webkit-variant gebruikt, ben je in feite 'n onbetaalde reclamemaker voor Apple. (Dit geldt dus niet voor de hierboven genoemde -ms-user-select, -webkit-overflow-scrolling, @-webkit-keyframes en -webkit-animation, want deze worden alleen op iOS, voor Internet Explorer en Edge, of om een probleem in oudere webkit-browsers op te lossen, gebruikt.)
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!).Eenzovoort.
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 <header>, <main> en <nav> gebruikt. Alle drie 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.
<header>
Bedoeld om een header in te zetten.
Een <header> mag vaker op één pagina worden gebruikt. De <header> hoort altijd bij de eerste voorouder, die een <main>, <article>, <aside>, <nav> of <section> is. Als de eerste voorouder <body> is, zoals hier het geval is, is de <header> de <header> van de hele pagina.
<main>
Hierbinnen staat de belangrijkste inhoud van de pagina (in dit voorbeeld is dat alleen Latijnse flauwekul-tekst).
<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 in de uitleg voor schermlezers bovenaan 'Structuur menu voorbeeld' in een <h2>. Deze uitleg wordt verborgen. Pas als wordt gekozen om het menu voor schermlezers te tonen, zie je deze uitleg.
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. Alleen hadden deze nieuwe elementen tot voor kort één probleem: ze hadden in de praktijk nog weinig nut, omdat schermlezers en dergelijke ze nog niet herkenden. Daarom werd een zogenaamde WAI-ARIA-code toegevoegd aan deze elementen. Dat is een al veel langer bestaande code, die schermlezers en dergelijke wel herkennen. Voor <main> ziet dat er zo uit:
<main role="main">
Inmiddels is dit behoorlijk veranderd. Het advies is nu om deze speciale toevoeging niet meer te gebruiken, omdat de meeste schermlezers en dergelijke dit soort nieuwe elementen inmiddels herkennen.
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 twee WAI-ARIA-codes gebruikt: aria-hidden en aria-haspopup.
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.
In dit voorbeeld staan op een aantal plaatsen teksten en knoppen, die voor gebruikers van schermlezers overbodig en verwarrend zijn. Deze worden met behulp van dit attribuut verborgen.
Onderstaande keuzemogelijkheid is bedoeld voor gebruikers van de Tab-toets:
<p aria-hidden="true"><strong>Kies: menu skippen of openen</strong></p>
<p aria-hidden="true"><span>Enter:</span> passeer menu en ga gelijk naar de eigenlijke inhoud.</p>
<p aria-hidden="true"><span>Tab-toets: </span> open menu voor gebruik met Tab-toets.</p>
<p aria-hidden="true">(Het menu kan later altijd alsnog worden geopend met Alt + Control + Shift + Home.)</p>
</a>
Eigenlijk zou de aria-hidden="true" in de <a> hierboven voldoende moeten zijn, om ook de tekst in de <p>'s te verbergen. VoiceOver op OS X leest de tekst in de <p>'s echter toch voor, vandaar dat ook in de drie <p>'s aria-hidden="true" staat.
Ook onderstaand aankruisvakje en <label> zijn bedoeld voor gebruikers van de Tab-toets, daarom worden ze voor schermlezers verborgen. (Schermlezers maken weliswaar gebruik van de hieronder staande input#checkbox-open-menu, maar deze wordt aan- en uitgevinkt via JavaScript.)
Onderstaand aankruisvakje en <label> worden gebruikt om het menu te openen in browservensters smaller dan 760 px. Voor schermlezers wordt het menu op een andere manier geopend, dus ook dit kan worden verborgen. Door de <div> te verbergen, wordt ook de in de <div> zittende <label> verborgen:
Op allerlei plaatsen in het menu zelf zitten ook nog aankruisvakjes en <label>'s, die worden gebruikt om op touchscreens submenu's te openen. Hieronder staat de eerste van deze aankruisvakjes en <label>'s:
Deze aankruisvakjes en <label>'s hebben allemaal een andere id en een andere for, maar ze worden allemaal op dezelfde manier verborgen voor schermlezers.
Als een kop een submenu heeft, wordt dit aangegeven door een pijltje. Dit pijltje is alleen verwarrend voor schermlezers, daarom wordt ook dat verborgen:
Als op een touchscreen de gelijk hieronder zittende <input type="checkbox"> wordt aangeraakt, moet het bijbehorende submenu worden getoond. Op Windows en Windows 10 Mobile opent het submenu echter niet op touchscreens in Internet Explorer 11 en Edge, en ook niet in UC browser op Windows 10 Mobile. Door het toevoegen van aria-haspopup="true" aan de <li> boven het submenu opent het submenu wel.
Nu moet je in deze browsers echter een link twee keer aanraken, voordat de link wordt gevolgd. De sukkels denken nu dat élke link een pop-up heeft. De eerste aanraking is om die (niet-bestaande) pop-up te openen, de tweede om de link te volgen. Dit wordt opgelost door aan de <ul> met het eigenlijke submenu aria-haspopup="false" toe te voegen:
<ul aria-haspopup="false">
Nu wordt een link met één aanraking gevolgd.
Tabindex en Tab-toets
Een uitklapmenu is een gruwel voor gebruikers van de Tab-toets. Bij het indrukken van de Tab-toets worden weliswaar netjes alle links gevolgd, maar je ziet die links niet. Daarom wordt gebruikers van de Tab-toets de mogelijkheid geboden het hele menu te openen. Als het menu eenmaal is geopend, werkt de Tab-toets op de gebruikelijke manier, die hieronder wordt beschreven. Een overzichtje van alle aanpassingen voor gebruikers van de Tab-toets is te vinden bij Bekende problemen (en oplossingen) onder het kopje Gebruikers Tab-toets.
Links, invoervelden in formulieren, en dergelijke kunnen met behulp van de Tab-toets (of een soortgelijke 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 het element volledig wordt genegeerd door de Tab-toets. Zelfs een link met een negatieve tabindex wordt volledig genegeerd. Normaal genomen heeft een tabindex="-1" maar één nut: je kunt dan met behulp van JavaScript toch focus aan het element geven, zonder dat gebruikers van de Tab-toets erdoor worden gehinderd.
Hier wordt het echter ook gebruikt om bepaalde <input>'s en dergelijke niet te laten bezoeken door de Tab-toets. Onderstaande <input> en <label> worden gebruikt om op touchscreens smaller dan 760 px het menu te openen:
Omdat deze <input> alleen voor aanraken is bedoeld, hoeft de Tab-toets hem niet te bezoeken.
Voor schermlezers zijn twee <input>'s aangebracht, waarmee het menu geopend kan worden. Ook deze zijn overbodig voor gebruikers van de Tab-toets. Hieronder staat de eerste van deze twee:
Boven elk submenu staan een <input> en bijbehorend <label>, waarmee het submenu op touchscreens geopend kan worden. Hieronder staan de <input> en <label> die bij de eerste knop horen:
Ook deze hebben weer geen enkel nut voor gebruikers van de Tab-toets. Als hier geen tabindex="-1" bij zou staan, zou de Tab-toets ook elke <input> bezoeken. Met 'n volgende tab wordt dan weer het volgende element bezocht. Omdat de <input> onzichtbaar is, is volstrekt onduidelijk, waarom je nog 'ns op de Tab-toets moet drukken.
Het element <main> wordt normaal genomen genegeerd door de Tab-toets. Hier moet het echter focus kunnen krijgen, als de bezoeker het menu in één keer wil passeren. Die focus wordt gegeven met behulp van JavaScript. Om het script focus te kunnen laten geven aan <main>, moet <main> ook een negatieve tabindex krijgen:
<main id="content" lang="la" 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 gewoonte van het platform wordt gevolgd. 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.
Elk submenu in dit voorbeeld zit in een eigen <li>. In die <li> zit ook een <h> met de titel van het submenu. Normaal genomen wordt een <li> genegeerd door de Tab-toets, maar dat is hier niet de bedoeling:
Door het toevoegen van tabindex="0" worden nu niet alleen de links (de <a>'s) in het menu bezocht door de Tab-toets, maar ook de bijbehorende koppen. Je kunt weliswaar verder niets doen met die kop, maar dit ziet er wat rustiger uit, dan wanneer alleen de links in het menu zouden worden bezocht.
Gebruikers van de Tab-toets kunnen met behulp van speciale toetsen snel door het menu navigeren. Zo kunnen ze bijvoorbeeld van <h> naar <h> gaan. Die toetsen worden aangestuurd met behulp van JavaScript, en ook voor dat script is tabindex="0" nodig, omdat anders geen focus aan de <li> kan worden gegeven.
tabindex="..."
Op de plaats van de puntjes moet een positief getal worden ingevuld: het volgnummer. Positieve tabindexen worden in dit voorbeeld niet 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 dit 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.
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. Omdat deze combinatie niet wordt gebruikt in dit voorbeeld, spelen deze problemen niet. Een aanraking op een touchscreen werkt in dit geval hetzelfde als hoveren met een muis.
Alleen op iOS 9 levert dit problemen op. Die problemen bestaan al heel lang en op iOS11 is dat kennelijk eindelijk opgelost. Hoe het met iOS 10 zit is onduidelijk, want daar wordt niet op getest.
Op iOS 9 opent het menu stomweg niet, behalve in Firefox en Opera Mini, als een knop wordt aangeraakt. Om dat op te lossen wordt een klein stukje JavaScript gebruikt:
body.addEventListener("touchstart", function () {return null;});
Omdat een iPad of iPhone zonder JavaScript weinig meer is dan een duur en groot uitgevallen horloge, zal JavaScript vrijwel nooit uitstaan. In steeds meer mobiele browsers kan JavaScript bovendien helemaal niet worden uitgezet.
(Helemaal zeker dat dit op iOS 11 is opgelost, is het trouwens niet. Omdat Apple nauwelijks informatie over dit soort dingen geeft, staat eigenlijk alleen maar vast dat het in dit voorbeeld werkt. Dat kan ook toeval zijn. Maar er zit hoe dan ook (eindelijk) kennelijk beweging in. Op Android werkt dit vanaf het begin vrijwel probleemloos.)
: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.
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 Tabindex en Tab-toets.)
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. In dit voorbeeld is er helemaal geen kadertje, om de eenvoudige reden dat de link of kop die focus heeft, wit kleurt. Een link die focus heeft wordt ook nog onderstreept. Dit is bij elkaar zo duidelijk, dat het lelijke kadertje kan worden weggelaten.
: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 hem hebt ingevoerd. Alsof je het in een editor ziet. In Firefox bijvoorbeeld kan dat door in het menu te kiezen voor Extra → Webontwikkelaar → 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 bloedig 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 te werken is.
Elke browser heeft inmiddels mogelijkheden om de gegenereerde code te bekijken. In Firefox bijvoorbeeld in het menu Extra → Webontwikkelaar → Hulpmiddelen in-/uitschakelen. 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. In het verleden veranderde Internet Explorer bijvoorbeeld een <p> juist in een <P>, nu is dat niet meer zo.
De code aanpassen aan je eigen ontwerp
Als je dit voorbeeld gaat aanpassen voor je eigen site, houd het dan in eerste instantie zo eenvoudig mogelijk. Ga vooral geen details invullen.
Gebruik geen FrontPage, Publisher of Word (alle drie van Microsoft). Deze programma's maken niet-standaard code die alleen goed te bekijken is in Internet Explorer. In alle andere browsers zie je grotendeels bagger, áls je al iets ziet.
Publisher en Word zijn niet bedoeld om websites mee te maken. FrontPage is zwaar verouderd en wordt al jaren niet meer onderhouden door Microsoft.
Ook OpenOffice en LibreOffice leveren een uiterst beroerd soort html af. Tekstverwerkers met al hun toeters en bellen zijn gewoon niet geschikt om websites mee te bouwen.
Je kunt beter een goed (gratis) programma gebruiken. Links naar dat soort programma's vind je op de pagina met links onder Gereedschap → wysiwyg-editor..
Maar het allerbeste is om gewoon zelf html, css, enzovoort te leren, omdat zelfs het allerbeste programma het nog steeds zwaar verliest van 'n op de juiste manier met de hand gemaakte pagina.
Als je in een desktopbrowser met behulp van zoomen het beeld vergroot, heeft dit hetzelfde effect, als wanneer de pagina in een kleiner browservenster wordt getoond. Je kunt hiermee dus kleinere apparaten zoals een tablet of een smartphone simuleren. Maar het blijft natuurlijk wel een simulatie: het is nooit hetzelfde als testen op een écht apparaat. Zo kun je bijvoorbeeld aanrakingen alleen echt testen op een echt touchscreen.
Inmiddels hebben veel browsers in de ontwikkelgereedschappen mogelijkheden voor het simuleren van weergave op een kleiner scherm ingebouwd. Ook dit blijft een simulatie, maar geeft vaak wel een beter beeld dan zoomen.
Ik maak zelf het liefst een site in Firefox. Als je 'n site maakt in Firefox, Opera, Safari, Google Chrome of Edge, is er 'n hele grote kans dat hij in alle browsers werkt. Ik geef de voorkeur aan Firefox, omdat het de enige grote browser is die niet bij een bedrijf hoort dat vooral op je centen of je data uit is.
Google Chrome wordt ook door veel mensen gebruikt, maar ik heb dus wat moeite met hoe Google je hele surfgedrag, je schoenmaat en de kleur van je onderbroek vastlegt. Daarom gebruik ik Google Chrome zelf alleen om in te testen.
Het allereerste dat je moet invoeren, is het doctype, vóór welke andere code dan ook. Een lay-out met een missend of onvolledig doctype ziet er totaal anders uit dan een lay-out met een geldig doctype. Wát er anders is, verschilt ook nog 'ns tussen de diverse browsers. Als je klaar bent en dan nog 'ns 'n doctype gaat invoeren, weet je vrijwel zeker dat je van voren af aan kunt beginnen met de lay-out.
Gebruik het volledige doctype, inclusief de eventuele url, anders werkt het niet goed.
Gebruik een 'strict' doctype of (beter!) het doctype voor html5. Deze zijn bedoeld voor nieuwe sites. Het transitional doctype is bedoeld voor al bestaande sites, niet voor nieuwe.
Het transitional doctype staat talloze tags toe, die in html5 zijn verboden. Deze tags worden al zo'n tien jaar afgeraden. Het transitional doctype is echt alleen bedoeld om de puinhoop van vroeger, toen niet volgens standaarden werd gewerkt, enigszins te herstellen.
Het strict doctype staat verouderde tags niet toe. Daardoor kan met 'n strict doctype, of het nu html of xhtml is, probleemloos worden overgestapt naar html5. Met een transitional doctype en het gebruik van afgekeurde tags kun je niet overstappen naar html5. Je moet dan eerst alle verouderde tags verwijderen, wat echt ontzettend veel werk kan zijn.
Het doctype voor html5 is uiterst simpel: <!doctype html>. Omdat het doctype voor html5 in alle browsers werkt, zelfs in de gelukkig vrijwel uitgestorven nachtmerrie Internet Explorer 6, is er geen enkele reden dit uiterst simpele doctype niet te gebruiken.
Als tweede voer je de charset in. Dit vertelt de browser, welke tekenset er gebruikt moet worden, zodat letters met accenten en dergelijke overal goed worden weergegeven. Het beste kun je utf-8 nemen. Als je later van charset verandert, loop je 'n grote kans dat je alle aparte tekens als letters met accenten weer opnieuw moet gaan invoeren. In html5 is het simpele <meta charset="utf-8"> voldoende.
Test vanaf het allereerste begin in zoveel mogelijk verschillende browsers in 'n aantal resoluties (schermgroottes). Onder het kopje Getest in kun je in deze uitleg vinden, waar dit voorbeeld in is getest. Ook van Internet Explorer kun je meerdere versies naast elkaar draaien. Op de pagina met links staan onder het kopjes Gereedschap → Weergave en dergelijke testen 'n aantal links die daarbij kunnen helpen. De compatibiliteitsweergave in Internet Explorer is niet geschikt om in te testen, omdat deze enigszins verschilt van de weergave in échte browsers.
(Overigens wordt op deze site alleen nog in Internet Explorer 11 getest. Oudere versies van Internet Explorer worden niet meer ondersteund en zijn daardoor per definitie uiterst onveilig. In Edge, de opvolger van Internet Explorer, wordt wel gewoon getest.)
Voor alle voorbeelden geldt: breng veranderingen stapsgewijs aan. Als je bijvoorbeeld foto's wilt laten weergeven, begin dan met het alleen veranderen van de namen van de foto's, zodat je eigen foto's worden weergegeven. Maakt niet uit als de maten niet kloppen en de teksten fout zijn. Als dat werkt, ga dan bijvoorbeeld de maten aanpassen. Dan de teksten. En controleer steeds, of alles nog goed werkt.
Als het om een lay-out of iets dergelijks gaat: zorg eerst dat header, kolommen, footer, menu, en dergelijke staan en bewegen, zoals je wilt. Ga daarna pas details binnen die blokken invullen. In eerste instantie gebruik je dus bijvoorbeeld 'n leeg blok op de plaats, waar uiteindelijk het menu komt te staan.
Als je begint met allerlei details, is er 'n heel grote kans dat die de werking van de blokken gaan verstoren. Bouw eerst het huis, en ga dan pas de kamers inrichten. Zorg eerst dat de blokken werken, zoals je wilt. Dan zul je het daarna gelijk merken, als 'n toegevoegd detail als tekst of 'n afbeelding iets gaat verstoren. Daarvoor moet je natuurlijk wel regelmatig controleren in verschillende browsers, of alles nog wel goed werkt.
Je kunt de blokken tijdens het aanpassen opvullen met bijvoorbeeld <br>1<br>2<br>3 enzovoort, tot ze de juiste hoogte hebben. Het is handig om aan het einde even iets toe te voegen als 'laatste', zodat je zeker weet dat er niet ongemerkt drie regels onderaan naar 't virtuele walhalla zijn verhuisd.
Om de breedte te vullen, kun je het best 'n kort woord als 'huis' duizend keer of zo herhalen. Ook hier is het handig om aan 't eind (en hier ook aan 't begin) 'n herkenningsteken te maken, zodat je zeker weet dat je de hele tekst ziet.
Zolang je in grotere dingen zoals 'n lay-out aan 't wijzigen bent, kan het helpen de verschillende delen een achtergrondkleur te geven. Je ziet dan goed, waar 'n deel precies staat. Een achtergrondkleur heeft - anders dan bijvoorbeeld een border - verder geen invloed op de lay-out, dus die is hier heel geschikt voor.
Als je eigenschappen verandert in de css, verander er dan maar één, hooguit twee tegelijk. Als je er zeventien tegelijk verandert, is de kans groot dat je niet meer weet, wat je hebt gedaan. En dat je 't dus niet meer terug kunt draaien.
margin, padding en border worden bij de hoogte en breedte van het element opgeteld. Hier worden vaak fouten mee gemaakt. Als je bijvoorbeeld in een lay-out 'n border toevoegt aan een van de 'hoofdvakken' (header, footer, kolommen), dan wordt deze er bij opgeteld. Bij 'n border van 2 px rondom de linkerkolom wordt deze dus plotseling 4 px breder (2 aan beide kanten), en 4 px hoger. Zoiets kan je hele lay-out verstoren, omdat iets net te breed of te hoog wordt. Je moet dan elders iets 4 px kleiner maken. Dat zal vaak zo zijn: als je één maat verandert, zul je vaak ook 'n andere moeten aanpassen.
Css geeft de mogelijkheid om met behulp van box-sizing de padding en border bínnen de breedte en hoogte van de inhoud te zetten, als je dat handiger vindt.
Met nieuwere css-eigenschappen als grid en flexbox, die speciaal zijn gemaakt om een lay-out mee te maken, spelen dit soort problemen veel minder. Maar hier moet je weer goed op de ondersteuning door browsers letten, want niet elke browser ondersteunt al alles van deze eigenschappen, en zeker niet foutloos.
In plaats van een absolute eenheid als px kun je ook een relatieve eenheid gebruiken, met name em en rem. Voordeel van em en rem is dat een lettergrootte, regelhoogte, en dergelijke in em en rem in alle browsers kan worden veranderd. Nadeel is dat het de lay-out sneller kan verstoren dan bijvoorbeeld px. Dit moet je gewoon van geval tot geval bekijken. Voor weergave in mobiele apparaten zijn relatieve eenheden als em en rem vrijwel altijd beter dan absolute eenheden als px.
(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. Bij de em kan de lettergrootte worden beïnvloed door de voorouders van het element, bij de rem niet.)
Zoomen kan trouwens altijd, ongeacht welke eenheid je gebruikt.
Valideren, valideren, valideren en dan voor 't slapen gaan nog 'ns valideren.
Valiwie???
Valideren is het controleren van je html en css op 'n hele serie fouten. Computers zijn daar vaak veel beter in dan mensen. Als je 300 keer <h2> hebt gebruikt en 299 keer </h2> vindt 'n computer die ene missende </h2> zonder enig probleem. Jij ook wel, maar daarna ben je misschien wel aan vakantie toe.
Valideren kan helpen om gekmakende fouten te vinden. Valide code garandeert ook dat de weergave in verschillende browsers (vrijwel) hetzelfde is. En valide code is over twintig jaar ook nog te bekijken.
Valideren moet trouwens ook niet worden overdreven. Het is een hulpmiddel om echte fouten te vinden, meer niet. Het gaat erom dat je site goed werkt, niet dat je het braafste kind van de klas bent. Als de code niet valideert, maar daar is een goede reden voor, is daar niets op tegen. Zeker met nieuwere html en css wil de validator nog wel eens achterlopen, terwijl dat al prima is te gebruiken.
Op deze site is alle css en html gevalideerd. Als de code niet helemaal valide is (wat regelmatig voorkomt), staat daar onder Bekende problemen (en oplossingen) de reden van.
Je kunt je css en html valideren als 't online staat, maar ook als het nog in je computer staat.
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:
Gebruik altijd een alt-beschrijving bij een afbeelding. De alt-tekst wordt gebruikt, als afbeeldingen niet kunnen worden getoond of gezien (dat geldt dus ook voor zoekmachines). Als je iets wilt laten zien, als je over de afbeelding hovert, gebruik daar dan het title-attribuut voor, niet de alt-beschrijving.
Als een afbeelding alleen maar voor de sier wordt gebruikt, zet je daarbij alt="", om aan te geven dat de afbeelding niet belangrijk is voor het begrijpen van de tekst of zo.
Als uit de tekst in een link niet duidelijk wordt, waar de link naartoe leidt, gebruik dan een title bij de link. Een tekst als 'pagina met externe links' is waarschijnlijk duidelijk genoeg, een tekst als alleen 'links' mogelijk niet. Een duidelijke zwart-witregel is niet te geven, omdat dit ook van tekst en dergelijke in de omgeving van de link afhangt.
Accesskeys (sneltoetsen) kun je beter niet gebruiken, deze geven te veel problemen, omdat ze vaak dubbelop zijn met sneltoetsen voor de browser of andere al gebruikte sneltoetsen. Bovendien is voor de gebruiker meestal niet duidelijk, welke toetsen het zijn.
Op zichzelf zijn accesskeys een heel goed idee. Maar helaas zijn ze ook in html5 volstrekt onvoldoende gedefinieerd. Er is nog steeds geen standaard voor de meest gebruikelijke accesskeys, zoals Zoek of Home.
Er is nog steeds niet vastgelegd, hoe accesskeys zichtbaar gemaakt kunnen worden. Voor de makers van browsers zou dit 'n relatief kleine moeite zijn, voor de makers van 'n site is het bergen extra werk.
Hierdoor zijn accesskeys (vrijwel) niet te gebruiken. Misschien kunnen ze nog enig nut hebben op sites, die gericht zijn op 'n specifieke groep gebruikers. Maar voor algemene sites is het advies: normaal genomen niet gebruiken.
Met behulp van de Tab-toets (of op 'n soortgelijke manier) kun je in de meeste browsers door links, invoervelden, en dergelijke lopen. Elke tab brengt je één link, invoerveld, en dergelijke verder, Shift+Tab één plaats terug. Met behulp van het attribuut tabindex kun je de volgorde aangeven, waarin de Tab-toets werkt. Zonder tabindex wordt de volgorde van de html aangehouden bij gebruik van de Tab-toets, maar soms is een andere volgorde logischer.
In principe is het beter, als tabindex niet nodig is, maar gewoon de volgorde van de html wordt aangehouden. Bij verkeerd gebruik kan tabindex heel verwarrend zijn. Het is niet bedoeld om van de pagina een hindernisbaan voor kangoeroes te maken, waarop van beneden via links over rechts naar boven wordt gesprongen.
Als, zoals hierboven beschreven, een gebruiker van de Tab-toets bij een link, invoerveld, en dergelijke is aangekomen, heeft dit element 'focus'. Dit wordt aangegeven door de link, invoerveld, en dergelijke extra te markeren met een kadertje. Dat kadertje mag je alleen weghalen, als op een andere manier wordt duidelijk gemaakt, welk element focus heeft. Een gebruiker van de Tab-toets kan anders niet zien, waar zij of hij zit, en welk element gaat reageren op bijvoorbeeld een Enter.
In het verleden werd vaak aangeraden de volgorde van de code aan te passen. Een menu bijvoorbeeld kon in de html onderaan worden gezet, terwijl het op het scherm met behulp van css bovenaan werd gezet. Inmiddels zijn schermlezers en dergelijke zo sterk verbeterd dat dit niet meer wordt aangeraden. De volgorde in de html kan tegenwoordig beter hetzelfde zijn als die op het scherm, omdat het anders juist verwarrend kan werken.
Een zogenaamde skip-link is vaak nog wel zinvol. Dat is een link die je buiten het scherm parkeert met behulp van css, zodat hij normaal genomen niet te zien is. Zo'n link is wel gewoon zichtbaar in speciale programma's zoals tekstbrowsers en schermlezers, want die kijken gewoon naar wat er in de broncode staat. (Alleen in de schermlezer TalkBack op Android werkt zo'n buiten het scherm geplaatste link niet. TalkBack leest zo'n link wel voor, maar de link kan niet worden gevolgd, als deze buiten het scherm staat. Meer hierover is te vinden bij Probleem: in TalkBack op Android moet een link en dergelijke binnen het venster staan.)
Een skip-link staat bovenaan de pagina, nog boven menu, header, en dergelijke, en linkt naar de eigenlijke inhoud van de pagina. Hierdoor kunnen mensen met één toetsaanslag naar de eigenlijke inhoud van de pagina gaan.
Een skip-link is vooral nuttig voor gebruikers van de Tab-toets. Zodra de normaal genomen onzichtbare link door het indrukken van de Tab-toets focus krijgt, kun je hem op het scherm plaatsen, waardoor hij zichtbaar wordt. Bij een volgende tab wordt hij dan weer buiten het scherm geplaatst en is dus niet meer zichtbaar, zodat de lay-out niet wordt verstoord.
Op pagina's en in voorbeelden waar dat nuttig is, wordt op deze site een skip-link gebruikt. (Althans: nog niet in alle voorbeelden die daarvoor in aanmerking komen, zit een skip-link. Maar geleidelijk aan worden dat er steeds meer.)
Van oorsprong is html een taal om wetenschappelijke documenten weer te geven, pas later is hij gebruikt voor lay-out. Maar daar is hij dus eigenlijk nooit voor bedoeld geweest. Het gebruiken van html voor lay-out leidt tot enorme problemen voor gehandicapten en tot een lage plaats in zoekmachines.
De html hoort alleen inhoud te bevatten, lay-out doe je met behulp van css. Die css moet in een externe stylesheet staan of, als hij alleen voor één bepaalde pagina van toepassing is, in de <head> van die pagina.
Breng een logische structuur aan in je document. Gebruik een <h1> voor de belangrijkste kop, een <h2> voor een subkop, enzovoort. Schermlezers en dergelijke kunnen van kop naar kop springen. En een zoekmachine gaat ervan uit dat <h1> belangrijke tekst bevat.
Dit geldt voor al dit soort structuurbepalende tags.
Als een <h1> te grote letters geeft, maak daar dan met behulp van je css 'n kleinere letter van, maar blijf die <h1> gewoon gebruiken. Op dezelfde manier kun je al dit soort dingen oplossen.
<table> is fantastisch, maar alleen als die wordt gebruikt om een echte tabel weer te geven, niet als hij voor opmaak wordt misbruikt. In het verleden is dat op grote schaal gebeurd bij gebrek aan andere mogelijkheden. Een tabel is, als je niet heel erg goed oplet, volstrekt ontoegankelijk voor gehandicapten en zoekmachines. Het lezen van een tabel is ongeveer te vergelijken met het lezen van een krant van links naar rechts: niet per kolom, maar per regel. Dat gaat dus alleen maar goed bij een echte tabel, zoals een spreadsheet. In alle andere gevallen garandeert 'n tabel volstrekte ontoegankelijkheid voor schermlezers en dergelijke en als extra bonus vaak 'n lagere plaats in een zoekmachine.
Frames horen bij een volstrekt verouderde techniek, die heel veel nadelen met zich meebrengt. <iframe>'s hebben voor een deel dezelfde nadelen. Eén van die nadelen is dat de verschillende frames voor zoekmachines, schermlezers, en dergelijke als los zand aan elkaar hangen, omdat ze los van elkaar worden weergegeven. Ze staan wel naast elkaar op het scherm, maar er zit intern geen verband tussen.
Als je 'n stuk code vaker wilt gebruiken, zoals 'n menu dat op elke pagina hetzelfde is, voeg dat dan in met PHP. Dan wordt de pagina niet pas in de browser, maar al op de server samengesteld. Hierdoor zien zoekmachines, schermlezers, en dergelijke één pagina, net zoals wanneer je maar één pagina met html zou hebben geschreven.
(Je kunt ook invoegen met behulp van SSI. Maar tegenwoordig kun je beter PHP dan SSI gebruiken, omdat SSI min of meer aan het uitsterven is en PHP veel meer mogelijkheden heeft. Op deze site wordt in enkele voorbeelden nog SSI gebruikt, maar zodra die worden bijgewerkt, gaat dat vervangen worden door PHP.)
Geef de taal van het document aan, en bij woorden en dergelijke die afwijken van die taal de afwijkende taal met behulp van lang="...". Op deze site gebeurt dat maar af en toe, omdat de tekst (en vooral de code) een mengsel is van Engels, Nederlands en eigengemaakte namen. Dat soort teksten is gewoon niet goed in te delen in een taal. Maar bij enigszins 'normale' teksten hoor je een taalwisseling aan te geven.
Gebruik de tag <abbr> bij afkortingen. Doe dat de eerste keer op een pagina samen met de title-eigenschap: <abbr title="ten opzichte van">t.o.v.</abbr>. Daarna kun je op dezelfde pagina volstaan met <abbr>t.o.v.</abbr>. Doe je dit niet, dan is er 'n grote kans dat 'n schermlezer 't.o.v.' uit gaat spreken als 'tof', en 'n zoekmachine kan er ook geen chocola van maken.
Geef een verandering niet alleen door kleur aan. Een grote groep mensen heeft moeite met het onderscheiden van kleuren en/of het herkennen van kleuren. Verander bijvoorbeeld een ronde rode knop niet in een ronde groene knop, maar in een vierkante groene knop. Door ook de vorm te veranderen, is het herkennen van de verandering niet alleen van een kleur afhankelijk.
Zorg voor voldoende contrast tussen achtergrond- en voorgrondkleur, tussen background-color en color. Soms zie je heel lichtgrijze tekst op een donkergrijze achtergrond, en dan ook nog in een mini-formaat. Dat is dus voor heel veel mensen stomweg volledig onleesbaar. Een uitstekende online contrastchecker is bijvoorbeeld te vinden op snook.ca.
De spider van 'n zoekmachine, schermlezers, en dergelijke kunnen geen plaatjes 'lezen'. Het is soms verbazingwekkend om te zien hoe veel, of eigenlijk: hoe weinig tekst er overblijft op een pagina, als de plaatjes worden weggehaald. Hetzelfde geldt voor die fantastisch mooie flash-pagina's, als daarbij geen voorzieningen voor dit soort programma's zijn aangebracht. (Omdat flash wordt uitgefaseerd en op steeds minder machines is te bekijken, zijn flash-pagina's hoe dan ook geen goed idee meer.)
Op Linux kun je met Lynx kijken, hoe je pagina eruitziet zonder plaatjes en dergelijke, als echt alleen de tekst overblijft. Een installatie-programma voor Lynx op Windows is te vinden op invisible-island.net/lynx.
Ook kun je in Windows het gratis programma WebbIE installeren. WebbIE laat de pagina zien, zoals een tekstbrowser en dergelijke hem zien. WebbIE is te downloaden vanaf www.webbie.org.uk. (LET OP: kies voor 'Install WebbIE 4 Web Browser Now'. Dat is - op het moment van schrijven - de een na bovenste knop. Als je voor de bovenste download kiest, krijg je 'n hele berg hulpprogramma's erbij, waar je voor het testen niets aan hebt.)
Ten slotte kun je je pagina nog online op toegankelijkheid laten controleren op 'n behoorlijk aantal sites, zoals:
lowvision.support: laat zien hoe een kleurenblinde de site ziet. Engelstalig.
wave.webaim.org: deze laat grafisch zien, hoe de toegankelijkheid is. Engelstalig. Deze tester is ook als extensie in Firefox en Google Chrome te installeren.
Op de pagina met links kun je onder Toegankelijkheid links naar meer tests en dergelijke vinden.
Getest in
Laatst gecontroleerd op 30 november 2018.
Onder dit kopje staat alleen maar, hoe en waarin is getest. Alle eventuele problemen, ook die met betrekking tot zoomen, lettergroottes, toegankelijkheid, uitstaan 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!
Opmerking over Opera:
De uitvoeringen van Opera jongen als de konijnen. Voor Android bijvoorbeeld zijn er op dit moment al vijf verschillende uitvoeringen. Er wordt alleen in de hieronder vermelde uitvoering getest, want dit is niet meer bij te benen. Op Windows 10 Mobile wordt Opera Mini niet getest, omdat die bij installeren gelijk advertenties begon te spuiten, die niet waren te sluiten.
Dit voorbeeld is getest op de volgende systemen:
Desktopcomputers
Windows 7 (1280 x 1024 px, resolution 96 dpi):
Firefox, UC Browser, Google Chrome, Opera en Internet Explorer 11, in grotere en kleinere browservensters.
OS X 10.11.6 ('El Capitan') (1680 x 1050 px, resolution: 96: dpi, device-pixel-ratio: 1):
Firefox, Safari, Opera en Google Chrome, in grotere en kleinere browservensters.
Linux (Kubuntu 14.04 LTS, 'Trusty Tahr') (1280 x 1024 px, resolution: 96 dpi):
Firefox, Opera en Google Chrome, in grotere en kleinere browservensters.
Laptops
Windows 8.1 (1366 x 768 px, resolution: 96 dpi):
Bureaublad-versie: Firefox, UC Browser, Google Chrome, Opera en Internet Explorer 11, in grotere en kleinere browservensters.
Startscherm-versie: Internet Explorer 11.
Windows 10 (1600 x 900 px, resolution: 96 dpi):
Firefox, UC Browser, Google Chrome, Opera, Internet Explorer 11 en Edge, in grotere en kleinere browservensters.
Tablets
iPad met iOS 9.3.5 (1024 x768 px, device-pixel-ratio: 1):
Safari, Chrome for iOS, UC Browser, Firefox (alle portret en landschap).
Opera Mini (Opera Turbo) portret en landschap.
iPad met iOS 11.4.1 (2048 x 1536 px, device-pixel-ratio: 2 ('retina'):
Safari, Chrome for iOS, Firefox, Microsoft Edge (alle portret en landschap).
Opera Mini (Opera Turbo) portret en landschap.
(Omdat tijdens het testen iOS 12 uitkwam, is dit voorbeeld getest in iOS 11.4.1 én iOS 12. De opmerkingen in de tekst over iOS 11.4.1 gelden ook voor iOS 12.)
Android 4.4.2 ('Kitkat') (1280 x 800 px, resolution: 96 dpi):
Android browser, UC Browser, Firefox en Chrome (alle portret en landschap).
Opera Mini (besparingen uitgeschakeld) portret en landschap.
Android 4.4.2 ('Kitkat') (2560 x 1600 px, resolution: 192 dpi):
Android browser, UC Browser, Firefox en Chrome (alle portret en landschap).
Opera Mini (besparingen uitgeschakeld) portret en landschap.
Android 6.0 ('Marshmallow') (1920 x 1200 px, resolution: 144 dpi): Dolphin, Samsung Internet, UC Browser, Firefox en Chrome (alle portret en landschap).
Opera Mini (besparingen uitgeschakeld) portret en landschap.
Android 7.0 ('Nougat') (1920 x 1200 px, resolution: 144 dpi): Dolphin, Samsung Internet, UC Browser, Firefox, Microsoft Edge en Chrome (alle portret en landschap).
Opera Mini (besparingen uitgeschakeld) portret en landschap.
Smartphones
Windows 10 Mobile (1280 x 720 px, resolution: 192 dpi): Edge en UC browser (portret en landschap).
Android 4.1.2 ('Jelly Bean') (800 x 480 px, resolution: 144 dpi):
Chrome, Android browser, UC Browser, Firefox en Opera for Android (alle portret en landschap).
Android 7.0 ('Nougat') (1280 x 720 px, resolution: 192 dpi): Dolphin, Samsung Internet, UC Browser, Firefox, Microsoft Edge en Chrome (alle portret en landschap).
Opera Mini (besparingen uitgeschakeld) portret en landschap.
(Omdat tijdens het testen Android 8.1 ('Oreo') uitkwam, is dit voorbeeld getest in Android 7.0 én Android 8.1.)
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, Android en Windows 10 Mobile, waar een touchscreen is gebruikt. Op Windows 8.1 en 10 is getest met touchscreen, touchpad, toetsenbord, muis, en - waar dat zinvol was - op een combinatie daarvan.
Als JavaScript is gebruikt, is op de desktop ook getest zonder JavaScript. Op iOS, Android en Windows 10 Mobile is niet getest zonder JavaScript, omdat je JavaScript in een toenemend aantal mobiele browsers niet uit kunt zetten. Bovendien is een mobiel apparaat zonder JavaScript niet veel meer dan een duur en groot uitgevallen horloge. Ook in Edge is niet getest zonder JavaScript, omdat Microsoft het onmogelijk heeft gemaakt dit uit te zetten. Ten slotte 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, ChromeVox 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 Windows7. (LET OP: kies voor 'Install WebbIE 4 Web Browser Now'. Dat is - op het moment van schrijven - de een na bovenste knop. Als je voor de bovenste download kiest, krijg je 'n hele berg hulpprogramma's erbij, waar je voor het testen niets aan hebt.)
NVDA is een schermlezer, zoals die door blinden wordt gebruikt. Er is getest op Windows 7 en Windows 10 in combinatie met Firefox.
TalkBack is een in Android ingebouwde schermlezer. Er is getest in combinatie met Chrome op Android 4.4,2, 6.0 en 7.0.
VoiceOver is een in iOS en OS X ingebouwde schermlezer. Er is getest in combinatie met Safari op iOS (9.3.5 en 11.4.1) en OS X 10.11.6.
ChromeVox is een schermlezer in de vorm van een extensie bij Google Chrome. Er is getest op een systeem met Kubuntu Linux 14.04.
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).
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.
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 algemene problemen in alle of in specifieke browsers.
Als in een onderdeel één of meer problemen worden besproken, staat in een breed rood kadertje een korte samenvatting daarvan. Bij een onderwerp over toegankelijkheid zijn er soms geen problemen, maar alleen aanpassingen. In dat geval staat in een smal groen kadertje 'Geen problemen'.
Probleem: op iOS versie 9 (of 10) en eerder opent het menu niet.
Op iOS versie 11 opent werkt het menu prima zonder JavaScript. Maar op iOS versie 9 en eerder opent het menu niet zonder JavaScript. Mogelijk geldt dit ook voor versie 10, maar daar wordt niet op getest.
Met behulp van een klein stukje JavaScript wordt dit opgelost. Meer hierover is te vinden bij body.addEventListener("touchstart", function () {return null;});. Een groot probleem is dit niet, want zonder JavaScript is een iPad of iPhone weinig meer dan een groot en duur uitgevallen horloge. In steeds meer mobiele browsers kan JavaScript bovendien helemaal niet worden uitgeschakeld.
Zonder css
Geen problemen.
Alles werkt zonder css, alleen ziet het er natuurlijk niet uit. Omdat er niets wordt verborgen, staan alle links en koppen in een gigantische rij onder elkaar. Wel netjes opgedeeld in lijsten, maar echt geweldig oogt het toch niet. Maar als iemand om een of andere reden css heeft uitstaan, zal die dit wel gewend zijn.
Probleem: een uitklapmenu is volstrekt ontoegankelijk voor gebruikers van de Tab-toets.
Als mensen de Tab-toets gebruiken om links af te gaan, worden de koppen en links in het menu netjes bezocht. Maar de links in een submenu zijn pas zichtbaar, als dat submenu zelf zichtbaar is. En dat is niet het geval bij een uitklapmenu. Pas bij aanraken of -klikken van een kop, opent een submenu. Gebruikers van de Tab-toets zien dus niet, welke kop of link focus heeft.
Onderaan het venster van de browser verschijnt meestal wel de bestemming van de link, maar dat is vaak een volstrekt onduidelijke naam. Als een pagina bijvoorbeeld 'a300.html' heet, heb je daar niet echt veel aan.
Als na het openen van de pagina de Tab-toets wordt ingedrukt, krijgt de gebruiker daarom de mogelijkheid het hele menu in één keer te openen, of het hele menu in één keer te passeren en naar de eigenlijke inhoud van de pagina te gaan.
Als het menu wordt geopend, zijn alle links gewoon zichtbaar en weet de gebruiker, welke link focus heeft en dus wordt gevolgd, als op Enter wordt gedrukt.
In dit menu zijn 160 links en koppen aanwezig (dat is veel te veel voor een uitklapmenu, maar het gaat hier om een voorbeeld, waarin allerlei mogelijkheden zichtbaar moeten zijn.) Als de gebruiker van de Tab-toets de laatste link moet hebben, moet 160 keer op de Tab-toets worden gedrukt. (Hmmm, m'n wiskundeknobbel is gesmolten in de hittegolf en ik ga het niet uitproberen, dus het kan ook 159 keer zijn.)
Daar word je niet vrolijk van. Daarom kunnen met behulp van de pijltjestoetsen delen van het menu worden overgeslagen. Hoe die toetsen precies werken, staat naast het menu, wanneer dat wordt geopend. Met Escape kan het menu worden gesloten en wordt naar de eigenlijke inhoud van de pagina gegaan. Voor deze toetsen is echter wel JavaScript nodig.
Maar ook zonder JavaScript kan het menu, op een iets andere wijze, worden geopend voor gebruikers van de Tab-toets. De pijltjestoetsen werken nu niet: als de laatste link moet worden bezocht, moet je nu echt 160 keer op de Tab-toets drukken. Dit is natuurlijk niet echt ideaal, maar zonder JavaScript wordt gewoon het standaardgedrag van de browser gevolgd.
Probleem: als het menu met Escape is gesloten, is het nauwelijks meer te openen.
Om het menu voor gebruik met de Tab-toets te openen moet je, bij openen van de pagina, op de Tab-toets drukken en daarvoor kiezen. Met Escape sluit je het menu en ga je naar de eigenlijke inhoud van de pagina (dat is hier de Latijnse flauwekultekst).
Om het menu weer te openen, moet je op een of andere manier met de Tab-toets terug naar het begin van de pagina. Dat kan met Shift + Tab, dan worden links van onder naar boven doorlopen. Maar dan moet je dus minstens 160 keer op de Shift + Tab drukken, voordat de keuze om het menu te openen weer verschijnt.
Je kunt ook op de Tab-toets drukken, totdat deze bij de laatste link, tekstveld, knop, enzovoort op de pagina is aangekomen. Dan begint de Tab-toets weer bovenaan de pagina (nadat eerst nog eventueel allerlei knoppen en dergelijke in de browser zijn bezocht).
De derde mogelijkheid is het herladen van de pagina. Geforceerd herladen, want in sommige browsers gaat de focus niet terug naar het begin van de pagina.
Geen van deze drie manieren is echt geweldig. Daarom kan het menu altijd worden geopend met de toetscombinatie Alt + Control + Shift + Home, ongeacht waar je bent op de pagina. Dit is een wat woeste toetscombinatie, maar deze combinatie is waarschijnlijk nog nergens in gebruik. Ook hiervoor is wel wat JavaScript nodig.
Tekstbrowsers
Geen problemen.
Lynx toont álles, aankruisvakjes, teksten voor de schermlezer, echt alles. Maar gebruikers van Lynx zullen dit gewend zijn.
WebbIE toont ook alles, behalve de delen die met de WAI-ARIA-codearia-hidden="true" voor schermlezers zijn verborgen.
Probleem: het tot vier niveaus diep nesten van lijsten kan verwarrend zijn.
Schermlezers lezen dit menu voor. Dat betekent dat gebruikers van een schermlezer moeten onthouden, op welk niveau van het menu ze zitten. Dat is lastig.
Boven elk submenu staat een <h2>, <h3> of <h4>. Omdat schermlezers het nummer van de kopregel aangeven, geeft dat ook gelijk het niveau van het submenu weer. Maar het blijft lastig. De enige manier om dit te voorkomen: het aantal niveaus beperken.
Probleem: een uitklapmenu werkt slecht of zelfs helemaal niet in schermlezers.
Elke schermlezer leest netjes alle links uit het menu voor. Maar niet alle gebruikers van schermlezers zijn volledig blind. Als links wel worden voorgelezen, maar niet zichtbaar zijn, kan dat uiterst verwarrend zijn.
Als de pagina geopend wordt met een schermlezer, wordt gelijk een stukje tekst met aanwijzingen voor gebruikers van schermlezers voorgelezen. Als vervolgens een aankruisvakje wordt aangevinkt en als dat nogmaals wordt bevestigd, wordt het menu volledig geopend. (Die bevestiging is om te voorkomen dat bij een toevallige aanraking van het aankruisvakje het menu wordt geopend. Het tweede aankruisvakje wordt pas actief, als het eerste is aangevinkt. Dit maakt een ongewilde opening van het menu voor schermlezers (vrijwel) onmogelijk.)
Deze tekst is de eerste tekst op de pagina. De kop ervan staat in een <h2>. Bovendien is het de eerste tekst binnen <header> (omdat <header> een kind van <body> is, werkt het hier als oriëntatiepunt). Dus of een gebruiker van een schermlezer nu gewoon de tekst vanaf het begin laat voorlezen, kopregels volgt, of oriëntatiepunten volgt, in alle gevallen is dit de eerste tekst.
Als wordt gekozen om het menu te openen, wordt de eigenlijk inhoud van de pagina (hier is dat de Latijnse flauwekultekst) naast het menu gezet, zodat het menu niet over de inhoud van de pagina komt te staan. In smalle browservensters wordt de tekst onder het menu gezet.
Elk submenu staat in een eigen <ul>. Boven elk submenu staat een <h2>, <h3> of <h4>, afhankelijk van de diepte van het submenu. Hierdoor kunnen de gebruikelijke sneltoetsen van een schermlezer worden gebruikt om delen van het menu over te slaan.
Er zijn geen speciale toetscombinaties of zoiets. Daardoor werken de normale toetscombinaties, waar gebruikers van schermlezers aan gewend zijn, op de gebruikelijke manier. Omdat elke schermlezer snel naar het begin van de pagina kan gaan, is ook geen toetscombinatie aangebracht om het menu te openen of te sluiten. Als de gebruiker naar het begin van de pagina gaat, kunnen de aankruisvakjes weer worden gebruikt om het menu te openen of te sluiten.
Voor de hier beschreven oplossing is wel JavaScript nodig. Als JavaScript uitstaat, wordt aan de tekst voor de schermlezer een waarschuwing toegevoegd. Het menu kan dan echter niet worden geopend.
Probleem: in TalkBack op Android moet een link en dergelijke binnen het venster staan.
In TalkBack op Android moet een link, aankruisvakje, enzovoort een stukje van minstens 1 x 1 px binnen het venster van de browser hebben staan, anders werken link, aankruisvakje, en dergelijke niet. Bovendien mag die pixel niet zijn verborgen onder een ander element.
De link, aankruisvakje, en dergelijke worden wel voorgelezen door TalkBack, en er wordt ook gezegd dat deze aangeklikt, aangevinkt, en dergelijke kan worden, maar in werkelijkheid kan dat dus niet. Daarom is ook gekozen voor de ietwat onhandige oplossing om een aankruisvakje binnen het browservenster te zetten, waarmee de gebruiker van een schermlezer het menu voor schermlezers kan openen. Waarna dat nog eens bevestigd moet worden, om per ongeluk openen door andere gebruikers te voorkomen.
Hier wordt al jaren over geklaagd. Het was zelfs kort gerepareerd, maar toen bleek dat scrollen soms niet meer mogelijk was, dus is die reparatie er in allerijl weer uitgehaald. Het is een heel vervelend probleem, want gebruikers van een schermlezer bedienen een link nou juist met het toetsenbord of door het scherm op een willekeurige plek aan te raken.
Wat er kennelijk gebeurt: de zogenaamde toegankelijkheids-focus wordt wel op link, aankruisvakje, en dergelijke gezet. Hierdoor worden link, aankruisvakje, en dergelijke wel correct aangekondigd. Maar de gewone focus wordt er niet op gezet. En die heb je wel nodig om link, aankruisvakje, en dergelijke te laten reageren op een aanraking.
Het lijkt erop dat in Android 8.1.0, dat net tijdens het testen van dit voorbeeld uitkwam, dit probleem eindelijk is opgelost. Maar omdat dit al eens eerder het geval was, en toen is teruggedraaid, kun je hier beter nog niet op vertrouwen. Bovendien worden er tot op de dag van vandaag schandalig genoeg zelfs nog smartphones met Android 4.2 verkocht. Dus het zal hoe dan ook nog vele jaren duren voor oudere versies van Android zijn verdwenen.
Daarom zullen voorlopig oplossingen zoals in dit voorbeeld nog nodig zijn.
Probleem: sommige schermlezers kunnen niet in één keer naar oriëntatiepunt <main> gaan (de eigenlijke inhoud van de pagina).
Met een schermlezer kun je snel naar bijvoorbeeld kopregels en lijsten springen. Ook kun je snel naar bepaalde oriëntatiepunten gaan, zoals <nav> en <main>. Niet al deze mogelijkheden werken echter in elke schermlezer. (In elke geteste schermlezer werken wel genoeg van dit soort sneltoetsen, om snel door het menu te kunnen navigeren.)
Als een schermlezer het oriëntatiepunt <main> niet herkent, moet de gebruiker toch nog een fors aantal lijsten of kopregels passeren, voordat bij de eigenlijke inhoud van de pagina wordt aangekomen. Daarom wordt bovenaan het menu voor schermlezers een zogenaamde skip-link getoond. Hiermee kan het hele menu in één keer worden gepasseerd.
Probleem: in VoiceOver op iOS werkt de skip-link boven het menu niet.
De skip-link die gelijk hierboven wordt beschreven, werkt niet in VoiceOver op iOS. De link wordt netjes voorgelezen en zo, en het anker verschijn in beeld. Maar vervolgens gaat VoiceOver gewoon door met voorlezen onder de skip-link.
Dit is een bekend probleem, wat alleen door Apple kan worden opgelost. Er wordt al jaren op allerlei plaatsen over geklaagd. Omdat een bij de AIVD werkende oester een wonder van openheid is vergeleken met Apple, is onbekend of dit ooit gerepareerd gaat worden, of dit misschien zo hoort te werken, of het Apple eigenlijk wel iets kan schelen, of wat dan ook.
Zoomen en lettergroottes
Probleem: in Firefox en Android browser op Android smartphones kan inzoomen alleen in landschapsstand.
Op een smartphone worden de submenu's geopend door een dubbel pijltje aan te raken. Als je in portretstand inzoomt (vergroot), vallen deze pijltjes al snel buiten het scherm. Omdat het menu niet horizontaal gescrold kan worden, kunnen de submenu's hierdoor niet worden geopend. Op tablets speelt dit probleem niet, want in browservensters minimaal 760 px breed wordt het menu op een andere manier geopend.
In portretstand is het browservenster breder, dus daarin kan wel worden gezoomd zonder dat de pijltjes wegvallen.
Android browser wordt sinds Android versie 4.3 niet meer geleverd, dus dit onding is snel aan het uitsterven. Voor Firefox is dit niet op te lossen (behalve door alleen in landschapsstand te zoomen).
Probleem: behalve in Firefox scrolt op Android smartphones bij inzoomen de menubalk mee.
De menubalk hoort bovenaan het browservenster te blijven staan, ook als de pagina wordt gescrold. Bij inzoomen (vergroten) scrolt de menubalk echter met de pagina mee. Alleen in Firefox blijft de balk netjes staan.
Omdat alles gewoon blijft werken, is dit niet een echt groot probleem.
Probleem: in Android browser, Dolphin en Samsung Internet op Android smartphones kan de lettergrootte alleen worden vergroot in landschapsstand.
Op een smartphone worden de submenu's geopend door een dubbel pijltje aan te raken. Als je in portretstand de lettergrootte verhoogd, vallen deze pijltjes al snel buiten het scherm. Omdat het menu bij een grotere letter niet horizontaal gescrold kan worden, kunnen de submenu's hierdoor niet worden geopend. Op tablets speelt dit probleem niet, want in browservensters minimaal 760 px breed wordt het menu op een andere manier geopend.
Bij inzoomen (vergroten) speelt dit probleem niet.
Android browser wordt sinds Android versie 4.3 niet meer geleverd en verdwijnt dus snel. In de andere browsers kan dit niet worden opgelost (behalve door alleen in landschapsstand de letters te vergroten).
Probleem: in Android browser en Dolphin op Android verschijnt bij een grotere lettergrootte een kier tussen menubalk en menu.
Dit is alleen een cosmetisch probleem, want alles werkt gewoon.
Probleem: in sommige (versies van) browsers op Android tablets leveren inzoomen en/of een grotere letter problemen op.
In de oudst geteste versie van Android op een tablet (4.4.2) kan de menubalk alleen in Chrome worden gescrold, als de menubalk te breed wordt voor het browservenster door inzoomen (vergroten) of een grotere letter. In alle andere browsers zijn buiten het venster vallende knoppen onbereikbaar, omdat de menubalk niet kan worden gescrold.
Dit speelt vooral in portretstand, omdat het browservenster dan smaller is.
In de laatste geteste versie (Android 7) heeft alleen Firefox nog problemen. In alle andere browsers scrolt de menubalk mee met de pagina, als wordt ingezoomd of als de letters worden vergroot. Als knoppen buiten het venster komen te vallen, kunnen die weer binnen het venster worden gescrold.
Het is niet zo zinvol om hier een precieze lijst met welk probleem in welke versie van welke browser speelt (of speelde), want kennelijk is er geen peil op te trekken. Bovendien sleutelen fabrikanten ook zelf aan Android, waardoor dit bij een andere fabrikant mogelijk weer net iets anders zou zijn.
In het algemeen geldt kennelijk: hoe recenter de versie van Android, hoe minder problemen er zijn met buiten het browservenster zittende onbereikbare knoppen.
Probleem: als op tablets met iOS 9 (of 10) en eerder knoppen buiten het browservenster vallen, kunnen die niet meer worden bediend.
Dit probleem speelt alleen in browservensters minimaal 760 px breed, want in smallere vensters werkt het menu anders.
Als door inzoomen (vergroten) de menubalk niet meer binnen het browservenster past, kunnen knoppen die buiten het venster vallen niet meer worden bediend. In portretstand gebeurt dit heel snel, maar in landschapsstand kun je nog behoorlijk ver inzoomen.
Omdat de menubalk niet gescrold kan worden, zijn de buiten het browservenster vallende knoppen onbereikbaar.
In iOS 11 kan de menubalk wel worden gescrold, als deze te breed is. Omdat op iOS 10 niet wordt getest, is onduidelijk of dit probleem ook speelt in die versie.
Alleen in UC browser kan de menubalk wel worden gescrold, maar dat komt omdat de balk in die browser altijd met de pagina meescrolt.
Probleem: op iOS kan in browservensters smaller dan 760 px bij inzoomen het menu niet worden gescrold.
Dit probleem heeft te maken met -webkit-overflow-scrolling, dat in browservensters smaller dan 760 px wordt gebruikt bij nav. In vensters met een breedte van minstens 760 px wordt -webkit-overflow-scrolling niet gebruikt en levert het dus ook geen problemen op.
Zodra je in iOS in browservensters smaller dan 760 px inzoomt (vergroot), kan het menu niet meer worden gescrold. Onderdelen van het menu die niet op het scherm staan op het moment dat je inzoomt, komen gewoon niet meer tevoorschijn.
Bij nav staat de regel -webkit-overflow-scrolling: touch;. Zodra deze regel wordt verwijderd, speelt dit probleem niet meer. Het scrollen heeft dan echter de opmerkelijke soepelheid van een tapdansende olifant met eksterogen en acute jicht. Normaal genomen blijft de tekst nog even doorscrollen, maar zonder deze regel stopt het scrollen onmiddellijk als het scherm wordt losgelaten. Wat niet echt lekker werkt.
Hier is geen oplossing voor: of zoomen kan niet, of scrollen loopt niet echt lekker.
Als je deze eigenschap gebruikt, moet je trouwens altijd heel grondig testen. Internet staat vol met verhalen over verdwijnende tekst en andere rampen bij gebruik van deze eigenschap.
Er zijn al jaren allerlei problemen met -webkit-overflow-scrolling. En er worden ook al jaren oplossingen gegeven, die helaas geen van alle werkten in dit geval. Omdat Apple zich weer 'ns volledig in stilzwijgen hult, is de oorzaak van deze bug onbekend. En of 't ooit wordt gerepareerd, tja, er zijn al jaren problemen mee, dus... Omdat dit Apple is, is het trouwens mogelijk geen bug, maar 'n feature.
Omdat Apple op iOS alle browsers verplicht gebruik te maken van de superieure weergave-machine van Safari, speelt dit probleem in alle browsers op iOS.
Probleem: op iOS 11 en Windows 10 Mobile scrolt de menubalk mee als wordt ingezoomd.
Bij inzoomen (vergroten) blijft de menubalk niet bovenaan het browservenster staan, maar scrolt mee met de pagina.
Een groot probleem is dit niet, want alles werkt verder gewoon.
Android browser op Android 4.12
Probleem: de lijntjes aan de bovenkant van de submenu's en aan de rechterkant van de submenu's op het derde en vierde niveau vallen weg.
Dit speelt alleen op een smartphone, want in browservensters minimaal 760 px breed wordt het menu op een andere manier weergegeven.
Waarschijnlijk wordt dit veroorzaakt, omdat deze browser calc() niet ondersteunt. Omdat het hier alleen om een klein cosmetisch probleem gaat en deze browser in rap tempo aan het verdwijnen is, is hier verder niet naar gekeken.
Opera Mini op tablets met Android 4.4.2
Probleem: de pagina wordt weergegeven als in een smal browservenster en het menu opent niet.
Bij de laatste update voor deze tablets is Opera Mini een volkomen verkeerde, veel te kleine schermgrootte door gaan geven, waardoor de media query's niet meer goed werken. Hierdoor wordt in deze tablets van 1024 x 768 px de pagina getoond, die bedoeld is voor browservensters smaller dan 760 px. Daarom speelt dit probleem ook niet op smartphones met Android 4.x
Hierdoor opent het menu niet meer.
Dit probleem is plotseling ontstaan bij de laatste update van Opera Mini. Op Android 6.0 en 7.0 zijn er geen problemen.
Hier is weinig aan te doen, omdat het overduidelijk om een bug in Opera Mini gaat. Hopelijk is dit bij een volgende update opgelost, want heel veel sites werken nu niet goed.
Opera Mini op Android
Probleem: in sommige oudere versies scrolt de menubalk mee met de pagina.
De menubalk hoort bovenaan het browservenster te blijven staan, ook als de pagina wordt gescrold. In sommige oudere versies van deze browser scrolt de menubalk mee met de pagina. Welke versies dat precies zijn is wat vaag, omdat fabrikanten zelf veranderingen kunnen aanbrengen in Android.
Omdat alles verder gewoon werkt, is dit geen groot probleem.
UC browser op iOS9
Probleem: in UC browser op iOS 9 scrolt de menubalk mee met de pagina.
De menubalk hoort bovenaan het browservenster te blijven staan, ook als de pagina wordt gescrold. In deze browser scrolt de menubalk mee.
UC browser is nog niet beschikbaar voor iOS 11, dus hoe het in die versie gaat werken, is onduidelijk. Omdat op iOS 10 niet wordt getest, is het ook daarin onduidelijk.
Een groot probleem is dit niet, want alles werkt verder gewoon.
Alle browsers
Probleem: bij zoomen verdwijnen of verdubbelen lijntjes soms.
Als wordt ingezoomd (vergroot) of uitgezoomd (verkleind), verdwijnen soms lijntjes uit het menu. Soms worden de lijntjes juist dikker.
Dit heeft te maken met het afronden van maten. Browsers worden hier steeds beter in, maar zijn nog niet helemaal feilloos. Soms verdwijnt een lijntje als je inzoomt, om dan bij nog wat meer inzoomen weer terug te komen.
Aan dit probleem is niets te doen, maar het is ook eigenlijk nauwelijks echt 'n probleem. (En als je hier toch diep ongelukkig van wordt: vroeger was dit veel en veel erger.)
Wijzigingen
Alleen grotere wijzigingen worden hier vermeld, geen dingen als een link die is geüpdatet.
:
Nieuw opgenomen.
21 juli 2008:
Bij de nieuwe zoom-functie in Firefox 3 pasten bij inzoomen de knoppen niet meer in het menu, waardoor de zesde knop op 'n nieuwe regel kwam te staan. Menu iets breder gemaakt en nu blijft 't goed.
In Internet Explorer 7 liep de lay-out volledig in 't honderd bij in- en uitzoomen. Niet gezien omdat het in eerste instantie gewoon goed werkte, pas bij herhaald openen gebeurde dat. Opgelost door aan #menu ul li a voor Internet Explorer 7 {width: 1px;} toe te voegen. Ik neem aan dat het met hasLayout te maken heeft, maar waarom het dan zonder zoomen wel goed werkte, en waarom het niet altijd 'n rotzooi werd, is mij 'n raadsel.
(hasLayout is 'n ongelooflijk onhandig idee van Microsoft en is gelukkig in Internet Explorer 8 verlaten. Als je wilt weten wat het is moet je maar op internet zoeken, het voert te ver om het hier uit te leggen. Voor zover het al te begrijpen is... Ik geef hier 'n breedte van 1 px aan 'n element dat overduidelijk ruim boven de 100 px breed is. En dat repareert dan 'n foutieve weergave. Dat is de logica van hasLayout in 'n notendop: niet te begrijpen.)
Bij Bekende problemen (en oplossingen) het verhaal over de kaderlijntjes bij Opera herschreven en daar Internet Explorer 7 aan toegevoegd.
Bij Bekende problemen (en oplossingen) het verhaal over de benodigde achtergrondplaatjes bij Opera aangepast. Dit is niet meer nodig in Opera op Windows.
Diverse andere kleine aanpassingen bij Bekende problemen (en oplossingen).
8 februari 2009:
Geen wijziging, maar Safari heeft 'n probleem minder. Bij het hoveren sloot het submenu steeds gelijk als je met de cursor boven 'n title kwam. Deze bug is kennelijk hersteld, want dit gebeurt niet meer.
1 april 2009:
Tekst aangepast aan de nieuw verschenen Internet Explorer 8. De code is hetzelfde gebleven.
18 november 2009:
Bij Opmerkingen stukje over toegankelijkheid in Opera herschreven. Opera blijkt 'n aantal fantastische mogelijkheden te hebben, die helaas nauwelijks te vinden zijn.
31 oktober 2010:
color: black; toegevoegd aan body vanwege toegankelijkheid.
Google Chrome toont, bij gebruik van de Tab-toets, inmiddels de bestemming van de links in het menu.
Tal van kleinere aanpassingen in de tekst, vooral op het gebied van toegankelijkheid.
11 oktober 2011:
Bij Opmerkingen tekst over volgorde van de code en volgorde op het scherm aangepast aan nieuwere inzichten.
De inmiddels verschenen Internet Explorer 9 verwerkt in de tekst.
Bij Bekende problemen (en oplossingen) Opera verwijderd. Inmiddels blijft ook bij hoveren over het kadertje het menu geopend in deze browser.
24 oktober 2018
Alle aanpassingen (en dat waren er heel veel) voor versies van Internet Explorer voor versie 11 verwijderd. (Als het menu verder niet zou zijn aangepast voor mobiel en dergelijke, zou de html bijna zijn gehalveerd en de css zou nog maar ongeveer een derde zijn.)
Doctype veranderd van xhtml naar html5.
Selectors zijn ingekort, waar dat kon.
Alle titles verwijderd uit de links. Schermlezers bleken de titles én de tekst uit de links voor te lezen.
In het menu zijn alleen échte links nog <a>'s. Nu Internet Explorer 6 niet meer wordt ondersteund, kan dat.
Op veel plaatsen de eenheid em vervangen door rem, en nieuwere eigenschappen zoals transform gebruikt. Nu Beëlzebub is teruggedreven naar z'n duistere krochten, ik bedoel: nu Internet Explorer 6 is verdwenen, kan dat.
In de vorige versie heette elk item in het menu 'Knop'. Nu heten koppen 'Kop' en links 'Link'. Dat is iets duidelijker. Uiteraard zou in een 'echt' menu zinvollere tekst worden gebruikt.
Het menu staat niet meer onder, maar boven de eigenlijke inhoud van de pagina.
Lijntjes rondom links en koppen in het menu aangebracht.
Hele kleine vertraging aangebracht bij het sluiten van submenu's. Als de muis nu even van het menu afschiet, is er net genoeg tijd om weer terug naar het menu te gaan.
Menu werkt nu ook op alle touchscreens. (In eerdere versies werkte het op sommige systemen wel, op andere niet.)
In smallere browservensters zoals op smartphones wordt het menu nu aangepast weergegeven.
Alle koppen en links in de submenu's hoger gemaakt, zodat ze beter zijn te bedienen met vingers.
Gebruikers van de Tab-toets kunnen het menu nu openen en met bepaalde toetsen delen overslaan.
Gebruikers van schermlezers kunnen het menu nu openen.
Boven elk submenu een <h> aangebracht, waardoor schermlezers snel van <h> naar <h> kunnen springen.
Aantal nieuwe hoofdstukken, voornamelijk over toegankelijkheid en mobiele apparaten.
Tig kleinere wijzigingen in de tekst.
30 november 2018
Hele serie kleine onduidelijkheden in uitleg verbeterd.
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 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.
menu-035.pdf: deze uitleg (aangepast aan de inhoud van de download).
menu-035-inhoud-download-en-licenties.txt: een kopie van de tekst onder dit kopje (Inhoud van de download en licenties).
035-css-dl:
menu-035-dl.css: stylesheet voor menu-035-dl.html.
menu-035-hulp-dl.css: stylesheet voor de twee hulppagina's achter de links in menu en tekst.
035-files-dl:
menu-035-menu-dl.html: hulppagina achter de links in het menu.
menu-035-tekst-dl.html: hulppagina achter de links in de tekst.
035-js-dl:
menu-035-dl.js: javascript voor het menu.
menu-035-dl-met-commentaar.js: javascript voor het menu met kort commentaar.
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.
<!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.
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">
De toevoeging 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
<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 (ä 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 hij anders door sommige browsers niet wordt gelezen.
In html5 hoeft deze regel niet langer te zijn, dan wat hier staat.
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 hij 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 hij of zij 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 het menu in smallere vensters anders weergegeven dan in bredere vensters.
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.
Een iPad in portretstand bijvoorbeeld is 768 px breed. 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. Voor een iPad in portretstand dus 768 px.
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.
Dit is een koppeling naar een externe stylesheet (stijlbestand), waarin de css staat. In html5 is de toevoeging type="text/css" niet meer nodig, omdat dit standaard al zo staat ingesteld. Je moet uiteraard de naam van en het pad naar de stylesheet aanpassen aan de naam en plaats, waar je eigen stylesheet staat.
Voordeel van een externe stylesheet is onder andere dat deze geldig is voor alle pagina's, waaraan deze is gelinkt. 'n Verandering in de lay-out hoef je dan maar in één enkele stylesheet aan te brengen, in plaats van in elke pagina apart. Op een grotere site kan dit ontzettend veel werk schelen. Bovendien hoeft de browser zo'n externe stylesheet maar één keer te downloaden, ongeacht hoeveel pagina's er gebruik van maken. Zou je de css in elke pagina opnieuw aanbrengen, dan worden de te downloaden bestanden veel groter.
In dit voorbeeld heeft een extern stylesheet eigenlijk geen nut, omdat er maar één pagina is die dit stylesheet gebruikt. In dit geval kun je de css beter in de <head> van de html-pagina zelf zetten. Voor de omvang maakt het hier niets uit, want de css wordt hoe dan ook altijd precies één keer gedownload, en nooit vaker. Voor het onderhoud maakt het ook geen verschil, want ook hier hoef je de css maar op één plaats te wijzigen. Maar het scheelt wel een extra aanroep naar de server, omdat geen apart stylesheet hoeft te worden gedownload.
Dat opnemen in de <head> gaat heel simpel: je kopieert gewoon het hele stylesheet en zet die bovenin de <head>, tussen <style> en </style>:
<style>body {color: black;}(...) rest van de css (...)div {color: red;}</style>
Maar zodra een stylesheet op meerdere pagina's wordt gebruikt, wat meestal het geval zal zijn, is een extern stylesheet beter.
(De reden dat er toch externe stylesheets zijn, terwijl hierboven omstandig wordt beweerd dat dat in dit voorbeeld eigenlijk geen nut heeft: overzichtelijkheid. Nu kun je html en css los van elkaar bekijken.)
De hulppagina's achter de knoppen hebben een gezamenlijke stylesheet: menu-035-hulp-dl.css. Deze wordt hier verder niet besproken, omdat hij uiterst simpel is. Op de site is deze met de stylesheet voor het voorbeeld zelf (en nog andere pagina's) gecombineerd tot één gezamenlijke stylesheet.
<body class="xgeen-jsx">
Bij het maken van de pagina moet aan aan body de class 'xgeen-jsx' worden toegevoegd. Als JavaScript aanstaat, wordt deze class door het script verwijderd. Deze class wordt in de css in enkele selectors gebruikt om extra informatie te tonen, als JavaScript uitstaat.
Helemaal los van deze class kunnen gewoon andere classes of een id worden gebruikt. Het verwijderen van 'xgeen-jsx' wordt volledig door het script geregeld en heeft geen invloed op andere classes of een id.
'xgeen-jsx' is een tamelijk idiote naam. Dit verkleint de kans dat deze classnaam al in gebruik is. Als je deze naam wilt veranderen, moet je die ook in het script en in de css veranderen.
Als je wilt kijken of deze class inderdaad door het script wordt verwijderd, moet je niet in de gewone broncode kijken, maar in de Gegenereerde code.
geopend met Alt + Control + Shift + Home.)</p></a>
Dit is de eerste link op de pagina. Als iemand de Tab-toets gebruikt om links, tekstvelden, en dergelijke langs te gaan, is dit dus de eerste link die focus krijgt. (In browservensters minimaal 760 px breed, in smallere vensters wordt deze <a> met alles erin verborgen.)
Zodra de link focus krijgt, verschijnt bijstaand venstertje met uitleg op het scherm. Als de bezoeker vervolgens op Enter drukt, wordt gewoon de link gevolgd en wordt het hele menu gepasseerd en naar de eigenlijke inhoud van de pagina bij main#content gegaan.
Als de gebruiker de Tab-toets indrukt, wordt met behulp van JavaScript het hele menu met alle submenu's in één keer geopend, waardoor dit bruikbaar wordt voor gebruikers van de Tab-toets.
Als een andere toets dan de Tab-toets of Enter wordt ingedrukt, zorgt het script dat deze toets wordt genegeerd: de gebruiker móét kiezen tussen Enter of de Tab-toets.
(Ook een klik en/of een aanraking ergens op het scherm sluit soms het venstertje met tekst, afhankelijk van browser en besturingssysteem. Dat is geen echt probleem, want deze constructie is juist gericht op gebruikers van het toetsenbord, die geen muis gebruiken. Bovendien kan het menu altijd opnieuw worden geopend met Alt + Control + Shift + Home.)
De <p>'s met de eigenlijke tekst staan binnen de <a>. In html5 mag dat. Hierdoor kan een Enter, waarbij de link gevolgd moet worden, heel makkelijk worden afgehandeld. De tekst binnen de <p>'s, de opmaak van de tekst, het aantal <p>'s, en dergelijke kunnen probleemloos worden aangepast.
Omdat deze <a> met tekst geen nut heeft voor schermlezers, wordt deze voor schermlezers verborgen met de WAI-ARIA-codearia-hidden="true".
<label id="label-open-menu" for="checkbox-open-menu" aria-hidden="true">Je hebt JavaScript uitstaan. Druk op de
(...) tot en met (...)
je weer voor het menu uitkomt.)</label>
Het openen van het menu voor gebruikers van de Tab-toets wordt geregeld met behulp van JavaScript. Het is echter relatief makkelijk het menu ook zonder JavaScript te laten openen.
Zodra het script vaststelt dat JavaScript aanstaat, wordt class 'xgeen-jsx' bij <body> verwijderd. Hierdoor kun je in de css selectors gebruiken die beginnen met .xgeen-jsx, en die alleen werken als JavaScript uitstaat.
Dit geeft de mogelijkheid bovenstaande <input> en bijbehorende <label> alleen te tonen, als JavaScript uitstaat. Door de <input> aan te vinken, kan het menu alsnog in z'n geheel worden geopend voor gebruikers van de Tab-toets.
(Speciale door JavaScript aangestuurde toetsen om snel door het menu te kunnen navigeren, werken dan uiteraard niet.)
De tekst binnen de <label>, de opmaak van de tekst, en dergelijke kunnen probleemloos worden aangepast.
Omdat deze <input> en <label> geen nut hebben voor schermlezers, worden ze voor schermlezers verborgen met de WAI-ARIA-codearia-hidden="true".
Deze drie elementen zorgen voor de menubalk bovenin browservensters smaller dan 760 px. De <div> zorgt voor de grijze balk, de tekst zit in de bij de <input> horende <label>. De <input> zelf is wegens acute gevorderde en besmettelijke lelijkheid verborgen: het aan- en uitvinken gebeurt via de bij de <input> horende <label>.
Omdat deze <input>, <div> en <label> geen nut hebben voor schermlezers, worden ze voor schermlezers verborgen met de WAI-ARIA-codearia-hidden="true".
Ook voor gebruikers van de Tab-toets heeft deze constructie geen nut. Daarom wordt met behulp van tabindex="-1" gezorgd dat de <input> geen focus kan krijgen.
<div id="voor-schermlezers">
<h2>Voor schermlezers</h2><p>Je lijkt een schermlezer te gebruiken. Deze pagina bevat een uitklapmenu met vier niveaus, dat slecht toegankelijk is voor schermlezers. Als je het toegankelijk wilt maken, vink dan de twee aankruisvakjes na deze tekst aan. Het menu wordt dan volledig geopend.</p><p>Zonder JavaScript wordt het menu niet geopend en zal het niet of lastig werken in een schermlezer.</p></div>
Deze tekst staat links buiten het scherm. Voor schermlezers maakt dat echter niets uit: hij wordt gewoon voorgelezen. Omdat dit de eerste elementen op de pagina zijn die niet worden verborgen voor schermlezers, wordt deze tekst als eerste voorgelezen.
Schermlezers kennen tal van sneltoetsen, waarmee snel door een pagina kan worden gegaan. Veel gebruikers gaan van <h> naar <h> op een pagina, omdat een <h> - als het goed is - een pagina min of meer in 'n soort hoofdstukjes opdeelt. Omdat de tekst begint met een <h2>, horen ook deze gebruikers als eerste deze tekst.
Ook gaan veel gebruikers van oriëntatiepunt naar oriëntatiepunt. Een <header> die een direct kind is van <body>, geldt als een oriëntatiepunt. Omdat de tekst voor schermlezers de eerste niet voor schermlezers verborgen tekst binnen <header> is, horen ook deze gebruikers als eerste deze tekst.
Als JavaScript aanstaat, wordt bij <body> de class 'xgeen-jsx' verwijderd. Hierdoor kan 'xgeen-jsx' worden gebruikt om de tweede <p> uit bovenstaande tekst te verbergen, als JavaScript aanstaat.
<a id="skippy" href="#content">Skip menu</a>
Dit is een zogenaamde skip-link. Zo'n link staat (vrijwel) bovenaan de pagina en geeft de mogelijk in één keer het hele menu en dergelijke te passeren en naar de eigenlijke inhoud van de pagina te gaan (dat is hier de Latijnse flauwekultekst).
De link staat normaal genomen links buiten het scherm geparkeerd, zodat de lay-out van de pagina niet wordt verstoord. Normaal genomen wordt de link pas getoond, als de link focus krijgt door het indrukken van de Tab-toets. Na nogmaals indrukken van de Tab-toets, of door het indrukken van Enter, verdwijnt de link dan weer.
In dit voorbeeld echter wordt het passeren van het menu voor gebruikers van de Tab-toets afgehandeld met behulp van JavaScript en heeft deze skip-link voor de Tab-toets geen nut.
Maar ook schermlezers kunnen gebruik maken van deze link. In principe kunnen schermlezers een menu heel makkelijk overslaan, maar dit werkt bepaald niet feilloos in alle schermlezers. Daarom komt deze link bovenaan het menu te staan, als het menu door gebruikers van een schermlezer volledig wordt geopend.
Op de afbeelding staat het bovenste deel van het menu, zoals dit voor schermlezers kan worden geopend. De skip-link staat rechtsboven het menu. In de html staat deze skip-link boven de rest van het menu. (Omdat voor de weergave van het volledige menu vrijwel dezelfde lay-out wordt gebruikt als voor gebruikers van de Tab-toets, staat de link niet recht boven, maar bovenaan rechts naast het menu.)
Omdat schermlezers heel simpel naar het begin van de pagina terug kunnen gaan en deze link bovenaan staat, kunnen nu ook schermlezers met enkele toetsaanslagen het hele menu in één keer passeren. Wat wel prettig is, omdat anders altijd 160 links en koppen zouden worden voorgelezen, voor de eigenlijke inhoud van de pagina wordt bereikt.
Boven elk submenu zit een <input> met bijbehorende <label>. Deze worden in browservensters smaller dan 760 px gebruikt om submenu's te openen. In bredere vensters worden ze verborgen.
De id van de <input> heeft hetzelfde volgnummer als de kop, waar de <input> onder staat: onder 'Kop 5 - 1 - 2' staat een <input id="checkbox-5-1-2">. Dit voorkomt hopelijk acute hersenverwildering door verdwaling in het oerwoud van <input>'s en <label>'s.
Omdat deze <input> en <label> geen nut hebben voor schermlezers, worden ze voor schermlezers verborgen met de WAI-ARIA-codearia-hidden="true".
Ook voor gebruikers van de Tab-toets heeft deze constructie geen nut. Daarom wordt met behulp van tabindex="-1" gezorgd dat de <input> geen focus kan krijgen.
In elke <label> staat ». Dat ziet er, na een draaiing van negentig graden, uit als een dubbel pijltje naar beneden. In werkelijkheid zijn het gewoon Franse aanhalingstekens.
In deze <li>'s zitten de kopjes boven elk submenu. Het is niet strikt noodzakelijk de tekst binnen een <h2> (in lagere menu's <h3> of <h4>) te zetten, maar dat maakt navigatie voor schermlezers een stuk makkelijker.
De WAI-ARIA-codearia-haspopup="true" is nodig voor Internet Explorer 11 en Edge op Windows en Windows 10 Mobile, omdat het submenu onder deze <li> anders niet opent. Ook UC browser op Windows 10 Mobile heeft dit nodig.
Gebruikers van de Tab-toets kunnen het menu in één keer volledig openen en er dan met de Tab-toets doorheen lopen. Een <li> kan echter geen focus krijgen. Door het attribuut tabindex="0" kan de <li> wel focus krijgen. Ook het JavaScript dat de speciale toetsen voor gebruikers van de Tab-toets regelt, heeft dit attribuut nodig.
<ul aria-haspopup="false">
Als je, zoals gelijk hierboven beschreven, de WAI-ARIA-codearia-haspopup="true" toevoegt aan de <li> boven een submenu, moet je om een of andere duistere reden in de hierboven genoemde browsers een link twee keer aanraken, voordat deze wordt gevolgd. Door aan elke <ul> met submenu weer aria-haspopup="false" toe te voegen, wordt dit voorkomen.
Dit is de <li> met 'Kop 1 - 1', het eerste item in het submenu onder 'Kop 1'. Ook onder deze <li> zit weer een submenu. Deze <li> is vrijwel hetzelfde als die bij <li aria-haspopup="true" tabindex="0"><h2>Kop 1</h2>, alleen zit in deze <li> een <span>, waarin een dubbel pijltje zit. Hiermee wordt aangegeven dat er een submenu onder deze <li> zit.
<main id="content" lang="la" tabindex="-1">
De <main> met de eigenlijke inhoud van de pagina. 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 deze <p> 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.)
De id is nodig, omdat hiernaartoe wordt gelinkt, als gebruikers van de Tab-toets het hele menu in één keer willen passeren.
Als het menu voor de Tab-toets is geopend, kan het met behulp van JavaScript worden gesloten, waarbij de focus aan <main> wordt gegeven. Om dit te kunnen doen, moet bij <main> het attribuut tabindex="-1" aanwezig zijn. Zonder tabindex kan het script geen focus aan <main> geven.
<h1>Horizontaal responsief uitklapmenu, tot vier niveaus diep</h1>
Normaal genomen horen <h>'s in de juiste volgorde in de html te staan. Van de belangrijkste (<h1>) tot de minst belangrijke (<h6>). Deze volgorde is belangrijk voor gebruikers van schermlezers, die van kopregel naar kopregel kunnen springen.
Maar hier staat een menu met een aantal <h>'s voor de belangrijkste <h1>. In dat geval mag je van die volgorde afwijken, want anders zou je de eerste kopregel van het menu tot belangrijkste kopregel van de pagina verklaren.
In de tekst zitten twee links. Die zitten daar alleen maar om zichtbaar te maken dat je inderdaad naar de tekst gaat, als je het menu voor gebruikers van de Tab-toets sluit (of helemaal niet opent).
Omdat de taal van de tekst in de links Nederlands is, wordt dat met lang="nl" aangegeven. Waarom dat belangrijk is, staat gelijk hierboven bij <main>.
<script src="035-js-dl/menu-035-dl.js"></script>
Met deze tag wordt het JavaScript aan de pagina gekoppeld, zodat het gebruikt kan worden. Deze tag wordt helemaal onderaan de pagina gezet, gelijk boven </body>. Het script zoekt namelijk elementen in de pagina op, en als die nog niet zijn gemaakt door de browser, gaat het mis. 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.
Een andere mogelijkheid is om het wel bovenaan in de <head> te zetten. Veel sitebouwers zetten scripts en dergelijke graag bij elkaar, voor de overzichtelijkheid. In dat geval kun je hetzelfde bereiken met:
Door het toevoegen van het sleutelwoord async weet de browser dat dit script niet nodig is voor de opbouw van de pagina. Het script wordt nu gedownload, terwijl de html gewoon verder wordt verwerkt. Zodra het script is gedownload, wordt het verwerkt, waarbij het verwerken van de html alsnog wordt onderbroken. Maar deze onderbreking is zo kort dat je dat nauwelijks merkt.
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.)
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, garandeer ik je hele grote problemen, omdat het volstrekt onoverzichtelijk is. Beter is het om de css netjes in te laten springen:
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, 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-035-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 aan 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; padding: 0;
Slim om te doen vanwege verschillen tussen browsers.
(Mogelijk is padding niet meer nodig, maar in het verleden verschilde de standaard-padding tussen browsers. Het is simpeler om dit gewoon te blijven gebruiken dan om een uitgebreide test uit te gaan voeren om te kijken, of dit nog wel nodig is.)
#voor-tab
Het element met id="voor-tab". De <a> waarin tekst voor gebruikers van de Tab-toets staat en die gebruikers laat kiezen tussen het openen van het menu, waardoor dit toegankelijk wordt voor de Tab-toets, of het in één keer passeren van het hele menu.
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: 400px;
Breedte.
text-decoration: none;
De tekst in dit venstertje staat weliswaar in <p>'s, maar die <p>'s staan weer binnen een <a>. Tekst in een <a> wordt standaard onderstreept, dat wordt hier verwijderd.
border: red solid 3px;
Dikke rode rand, zodat de melding beter opvalt.
padding: 5px;
Wat afstand tussen rand rondom en tekst binnen de <a>.
position: fixed;
Vastzetten ten opzichte van het venster van de browser. Ook als de bezoeker de pagina al heeft gescrold, voordat voor het eerst op de Tab-toets wordt gedrukt, verschijnt de tekst toch bovenaan het venster.
Een <a> is van zichzelf een inline-element. Hierdoor kunnen eigenschappen als breedte niet worden gebruikt. Door de <a> fixed te positioneren, verandert deze in een soort blok-element, waardoor dit soort eigenschappen toch is te gebruiken.
top: 20px;
20 px vanaf de bovenkant neerzetten.
left: -20000px;
Links buiten het scherm neerzetten. Pas als de <a> focus heeft gekregen, wordt de <a> bij #voor-tab:focus binnen het scherm gezet en zichtbaar.
z-index: 10;
Normaal genomen komen latere elementen in de html boven eerdere elementen te staan. Daardoor zou, in dit geval, de <a> met de tekst gedeeltelijk worden afgedekt door de knoppen van het menu. Een hogere z-index zorgt dat de <a> altijd zichtbaar is.
Een z-index werkt alleen in bepaalde omstandigheden. Een van die omstandigheden is een fixed positie, die iets hierboven aan de <a> is gegeven.
#voor-tab p
Alle <p>'s binnen het element met id="voor-tab". Binnen deze <p>'s staat de uitleg voor gebruikers van de Tab-toets.
text-indent: -20px; margin: 0 0 0 20px;
De standaardmarge aan boven- en onderkant van de <p> wordt weggehaald. Alleen aan de linkerkant wordt een marge van 20 px gegeven, waardoor de hele <p>, en dus alle tekst daarbinnen, 20 px vanaf de linkerkant komt te staan.
Met text-indent: -20px; wordt de eerste regel tekst 20 px naar links verplaatst, waardoor deze weer op de normale plaats komt te staan.
Het resultaat van deze hele exercitie: alle tekst staat 20 px naar rechts, behalve de eerste regel. Hierdoor springt alle tekst in, behalve de eerste regel. Daardoor vallen nieuwe onderwerpjes beter op.
Je kunt tekst ook op andere manieren laten inspringen, maar met text-indent springt alleen de eerste regel in. (Of eigenlijk: springt de eerste regel uit, want hier is een negatieve waarde gebruikt.) Als de regels langer of korter worden, bijvoorbeeld door een andere lettergrootte, wordt dit automatisch door de browser aangepast: alleen de eerste regel wordt naar links verplaatst.
#voor-tab span
Alle <span>'s binnen het element met id="voor-tab". Binnen deze <span>'s staan woorden die moeten opvallen.
font-variant: small-caps;
Alles in kleinkapitalen (grote en kleine hoofdletters).
Als de gebruikte lettersoort kleinkapitalen heeft, worden die gebruikt. Veel lettersoorten hebben geen kleinkapitalen. Kleinkapitalen worden speciaal ontworpen, het is iets anders dan 'alle letters in kleine hoofdletters veranderen'. Sommige letters kunnen er zelfs heel anders uitzien.
Als de lettersoort geen kleinkapitalen heeft, maakt de browser die. Dat wil zeggen dat de browser gewoon overal grotere en kleinere hoofdletters van maakt. Dat kan mooi zijn, maar vaak is het foeilelijk. Het is heel iets anders dan een speciaal ontworpen font. Elke rechtgeaarde typograaf gruwt hiervan.
#checkbox-open-menu
Het element met id="checkbox-open-menu". Dit aankruisvakje (<input type="checkbox">) wordt gebruikt om het menu in z'n geheel te openen voor gebruikers van de Tab-toets of schermlezers. Het wordt aan- en uitgevinkt met behulp van JavaScript. (Als JavaScript uitstaat, is er nog 'n mogelijkheid voor gebruikers van de Tab-toets om het menu toch te openen door dit aankruisvakje rechtstreeks aan te vinken.)
position: absolute;
Om het aankruisvakje op de juiste plaats neer te kunnen zetten. Er wordt gepositioneerd ten opzichte van de eerste voorouder die zelf een andere positie dan statisch heeft. Als die er niet is, zoals hier het geval is, wordt gepositioneerd ten opzichte van het venster van de browser.
left: -20000px;
Ver links buiten het scherm plaatsen.
Het aankruisvakje wordt hier aan- en uitgevinkt met behulp van JavaScript of - als JavaScript uitstaat - via de gelijk hieronder staande label#label-open-menu, waarin tekst met uitleg staat. Daarom is het aankruisvakje zelf hier overbodig en kan het onzichtbaar buiten het scherm worden geplaatst.
Helemaal verbergen met display: none; kan hier niet. Als JavaScript uitstaat, kunnen gebruikers van de Tab-toets het menu nog steeds openen door zelf het aankruisvakje aan te vinken. Maar daarvoor moet het aankruisvakje wel focus kunnen krijgen. Dat kan wel als het aankruisvakje links buiten het scherm staat, maar niet als het helemaal wordt verborgen met display: none;.
#label-open-menu
Het element met id="label-open-menu". De <label> die bij de gelijk hierboven staande input#checkbox-open-menu hoort.
In deze <label> zit de tekst die verschijnt als JavaScript uitstaat en de bezoeker de Tab-toets gebruikt om links, tekstvelden, en dergelijke af te lopen.
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.
Kleine afstand tussen tekst in en rand om de <label>.
position: fixed;
Voordat de bezoeker op de Tab-toets drukt, kan de pagina al zijn gescrold. Daarom wordt de positie van deze <label> met uitleg vastgezet aan de bovenkant van het browservenster.
top: 20px; left: 100px;
Afstand tot boven- en linkerkant van het browservenster.
z-index: 10;
Normaal genomen komen latere elementen in de html boven eerdere elementen te staan. Daardoor zou, in dit geval, de <label> met de tekst gedeeltelijk worden afgedekt door de knoppen van het menu. Een hogere z-index zorgt dat de <label> altijd zichtbaar is.
Een z-index werkt alleen in bepaalde omstandigheden. Een van die omstandigheden is een fixed positie, die iets hierboven aan de <label> is gegeven.
#checkbox-voor-smal
Het element met id="checkbox-voor-smal". Dit aankruisvakje (<input type="checkbox">) wordt gebruikt om in browservensters smaller dan 760 px het menu te openen.
display: none;
Het aankruisvakje wordt aan- en uitgevinkt via de iets hieronder staande #label-voor-smal. Daarom kan het aankruisvakje zelf worden verborgen.
#menubalk-voor-smal
Het element met id="menubalk-voor-smal". Deze <div> zorgt voor de grijze balk bovenin browservensters smaller dan 760 px, die altijd bovenaan het venster blijft staan.
background:#e4e4e4;
Grijze 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%;
Een breedte in procenten is normaal genomen ten opzichte van de ouder van het element. Dat is hier <header>, een blok-element. Een blok-element wordt normaal genomen automatisch als z'n ouder. Die ouder is hier <body>, ook een blok-element. Ook <body> wordt daarom normaal genomen weer even breed als zo'n ouder <html>. Omdat <html> het buitenste element is, wordt dit normaal genomen even breed als het venster van de browser.
Uiteindelijk wordt deze <div>, en daarmee de menubalk, dus even breed als het browservenster.
height: 2rem;
Hoogte.
Als het menu in browservensters smaller dan 760 px wordt geopend, komt dat onder de <div> met de menubalk te staan. Het is netter, als het menu precies op de menubalk aansluit. Daarom wordt de relatieve eenheid rem gebruikt, omdat bij een absolute eenheid zoals px de hoogte niet mee verandert met de lettergrootte.
De rem werkt ongeveer hetzelfde als de bekendere eenheid em. Ook de rem is op de lettergrootte gebaseerd, maar altijd op de lettergrootte van <html>. Daardoor is de rem altijd overal even groot, ongeacht de lettergrootte van het element zelf. En ongeacht eventuele afwijkende lettergroottes in voorouders van het element. Als de bezoeker de lettergrootte verandert, wordt in feite de lettergrootte van <html> veranderd. Hierdoor verandert ook de grootte van de rem, maar die verandering is overal op de pagina precies hetzelfde.
Omdat de meeste maten in het menu als eenheid de rem gebruiken, blijft de lay-out van het menu ook intact, als de bezoeker de lettergrootte verandert.
Als twee of meer elementen ten opzichte van elkaar neergezet moeten worden, een bepaalde grootte ten opzichte van elkaar moeten hebben, en dergelijke, is de eenheid rem vrijwel altijd beter dan de eenheid em. (Even los van nieuwere mogelijkheden als flexbox en grid, maar die worden hier niet gebruikt.)
Door de <div> hier 2 rem hoog te maken en het eronder staande menu bij nav met top: 2rem; 2 rem vanaf de bovenkant neer te zetten, sluiten menubalk en menu altijd op elkaar aan, ook als de bezoeker de lettergrootte verandert.
border-bottom: black solid 1px;
Zwart randje aan de onderkant.
position: fixed;
De menubalk moet altijd bovenaan het venster van de browser blijven staan, ook bij scrollen, zodat het menu altijd snel geopend kan worden. Daarom wordt de <div> vastgezet ten opzichte van het venster.
top: 0;
Aan de bovenkant van het browservenster zetten.
z-index: 10;
Normaal genomen komen latere elementen in de html boven eerdere elementen te staan. Daardoor zou, in dit geval, bij scrollen de pagina de menubalk verbergen.
Een hogere z-index zorgt dat de <label> altijd zichtbaar is.
Een z-index werkt alleen in bepaalde omstandigheden. Een van die omstandigheden is een fixed positie, die iets hierboven aan de <div> is gegeven.
#label-voor-smal
Het element met id="label-voor-smal". Via deze <label> wordt input#checkbox-voor-smal bediend. Met behulp van dit aankruisvakje wordt het menu in browservensters smaller dan 760 px geopend en gesloten.
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.
line-height: 2rem;
Regelhoogte. Omdat er geen gewone hoogte is opgegeven, is dat gelijk ook de hoogte van de <label> zelf. Tekst wordt automatisch halverwege de regelhoogte neergezet, dus de tekst staat ook gelijk verticaal gecentreerd.
Als eenheid wordt de relatieve eenheid rem 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.
De rem is ongeveer hetzelfde als de bekendere eenheid em, maar is gebaseerd op de lettergrootte van <html>. Hierdoor is de rem, anders dan de em, overal op de pagina even groot. Ook als de bezoeker de lettergrootte heeft veranderd.
Omdat de meeste maten in het menu als eenheid de rem gebruiken, blijft de lay-out van het menu ook intact, als de bezoeker de lettergrootte verandert.
padding-left: 10px;
Wat ruimte links van de tekst.
#label-voor-smal::after
Met behulp van ::after wordt bij het element met id="label-voor-smal" een pseudo-element gemaakt, waarin tekst kan worden weergegeven. In het menu staat al de tekst 'Menu ' (met een spatie achter 'Menu'). Door hier met behulp van een pseudo-element tekst achter te zetten, kan deze tekst later makkelijk worden veranderd.
content: "open";
De tekst die in het pseudo-element komt te staan. De totale zichtbare tekst wordt 'Menu open'.
Het element met id="voor-schermlezers". In deze <div> zitten aanwijzingen voor gebruikers van een schermlezer, hoe ze het menu toegankelijk kunnen maken voor een schermlezer.
position: absolute;
Om de <div> op de juiste plaats neer te zetten. Er wordt gepositioneerd ten opzichte van de eerste voorouder die zelf een andere positie dan statisch heeft. Als die er niet is, zoals hier het geval is, wordt gepositioneerd ten opzichte van het venster van de browser.
left: -20000px;
Ver links buiten het scherm zetten, zodat de tekst niet te zien is. Voor schermlezers maakt dat niets uit, die lezen de tekst gewoon voor.
#voor-schermlezers p + p
#voor-schermlezers: het element met id="voor-schermlezers".
p: alle <p>'s binnen #voor-schermlezers.
+: het element achter de + moet in de html direct volgen op het element voor de +. In dit geval gaat het om een <p> die gelijk op een <p> volgt. Beide elementen moeten ook nog dezelfde ouder hebben. Dat is hier het geval, ze hebben beide als ouder #voor-schermlezers.
p: het element dat gelijk op het element voor de + moet volgen.
De hele selector in normale mensentaal: binnen div#voor-schermlezers elke <p> die op een andere <p> volgt. Omdat er maar twee <p>'s in div#voor-schermlezers zitten, gaat het hier alleen om de tweede <p>.
display: none;
Verbergen. Deze tweede <p> bevat tekst die alleen getoond moet worden, als JavaScript uitstaat. In dat geval wordt deze <p> in de regel gelijk hieronder zichtbaar gemaakt.
.xgeen-jsx #voor-schermlezers p + p
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.
Deze selector is precies dezelfde als die gelijk hierboven, alleen staat er hier nog .xgeen-jsx voor. Het geheel moet dus binnen een element met class="xgeen-jsx" vallen.
Bij het maken van de pagina moet <body> class="xgeen-jsx" krijgen. Een van de eerste dingen die het JavaScript doet, is het verwijderen van deze class. Als JavaScript niet aanstaat, wordt de class niet verwijderd. Oftewel: deze selector werkt alleen, als JavaScript uitstaat. Hierdoor kun je hier tekst in stoppen die alleen wordt getoond, als JavaScript uitstaat.
('xgeen-jsx' is een rare naam. Dit maakt de kans kleiner dat deze class al in gebruik is. Als je deze naam in de html wilt wijzigen, moet je dat ook in het script doen.)
Om het verwijderen van 'xgeen-jsx' te zien moet je niet de gewone broncode, maar de Gegenereerde code bekijken.
display: block;
Gelijk hierboven bij #voor-schermlezers p + p is deze <p> met display: none; verborgen. Hier wordt hij zichtbaar gemaakt.
De elementen met id="checkbox-voor-schermlezers-1" en id="checkbox-voor-schermlezers-2". Met behulp van deze twee aankruisvakjes (<input type="checkbox">) kan het menu volledig worden geopend, zodat het bruikbaar wordt voor gebruikers van schermlezers.
Het zou het makkelijkste zijn, als deze twee aankruisvakjes gewoon buiten het scherm konden worden geparkeerd. Helaas werken ze dan niet in TalkBack op Android. Voor TalkBack op Android moet minimaal een plekje van 1 x 1 px van een aankruisvakje binnen het venster staan, niet verborgen onder een ander element. (Meer hierover is te vinden bij Probleem: in TalkBack op Android moet een link en dergelijke binnen het venster staan.)
Daarom worden ze noodgedwongen op het scherm gezet, maar zo klein mogelijk gemaakt. Dit maakt de kans dat ze per ongeluk worden aangeraakt of -geklikt door niet-gebruikers van een schermlezer zo klein mogelijk. Bovendien wordt het tweede aankruisvakje pas op het scherm gezet, als het eerste is aangevinkt.
Hiermee wordt het standaard-uiterlijk van een aankruisvakje uitgeschakeld. Of juister: er blijft helemaal niets van over. Het aankruisvakje is er nog wel, maar je ziet er niets meer van.
Helaas doet zich nu in TalkBack weer het hierboven omschreven probleem voor: je kunt het aankruisvakje niet meer aan- of uitvinken, daarvoor is nog steeds een oppervlakte van minimaal 1 x 1 px nodig, wat gelijk hieronder wordt geregeld.
width: 1px; height: 1px;
De kleinst mogelijke oppervlakte aan het aankruisvakje geven. Zonder oppervlakte kun je het niet uit- en aanvinken in TalkBack, zoals hierboven beschreven.
position: absolute;
Om de aankruisvakjes op de juiste plaats neer te kunnen zetten. Er wordt gepositioneerd ten opzichte van de eerste voorouder die zelf een andere positie dan statisch heeft. Als die er niet is, zoals hier het geval is, wordt gepositioneerd ten opzichte van het venster van de browser.
top: 50px; right: 0;
50 px vanaf de bovenkant en helemaal rechts neerzetten. Boven rechts is de kans dat ze per ongeluk worden aangeraakt of -geklikt het kleinst. Het tweede aankruisvakje wordt gelijk hieronder iets lager neergezet, anders zouden beide aankruisvakjes over elkaar heen staan.
#checkbox-voor-schermlezers-2
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.
Het element met id="checkbox-voor-schermlezers-2". Dit is het tweede aankruisvakje voor gebruikers van een schermlezer, waarmee ze de keuze om het menu te openen kunnen bevestigen.
display: none;
Verbergen. Dit tweede aankruisvakje wordt pas op het scherm gezet, als het eerste aankruisvakje is aangevinkt. Dit maakt de kans op het per ongeluk openen van het menu voor schermlezers heel erg klein, want je moet dan twee aankruisvakjes in de juiste volgorde aanraken of -klikken.
120 px vanaf de bovenkant van het browservenster neerzetten.
#label-voor-schermlezers-1
Het element met id="label-voor-schermlezers-1". De <label> die bij het eerste aankruisvakje voor schermlezers input#checkbox-voor-schermlezers-1 hoort.
position: absolute;
Om de <label> op een bepaalde plaats neer te kunnen zetten. Er wordt gepositioneerd ten opzichte van de eerste voorouder die zelf een andere positie dan statisch heeft. Als die er niet is, zoals hier het geval is, wordt gepositioneerd ten opzichte van het venster van de browser.
left: -20000px;
In deze <label> staat een korte tekst: 'Maak toegankelijk voor schermlezer'. Deze tekst wordt onzichtbaar links buiten het scherm geparkeerd. Een schermlezer leest deze tekst gewoon voor, ook al zie je hem niet.
#label-voor-schermlezers-2
Het element met id="label-voorschermlezers-2". De <label> die bij het tweede aankruisvakje voor schermlezers input#checkbox-voor-schermlezers-2 hoort.
display: none;
Verbergen. Deze <label> wordt pas getoond, als het eerste aankruisvakje voor schermlezers is aangevinkt. (Eigenlijk is 'getoond' niet het juiste woord, want ook deze <label> staat links buiten het scherm.)
Om de <label> op een bepaalde plaats neer te kunnen zetten. Er wordt gepositioneerd ten opzichte van de eerste voorouder die zelf een andere positie dan statisch heeft. Als die er niet is, zoals hier het geval is, wordt gepositioneerd ten opzichte van het venster van de browser.
left: -20000px;
In deze <label> staat een korte tekst: 'Bevestig je keuze'. Deze tekst wordt onzichtbaar links buiten het scherm geparkeerd. Een schermlezer leest deze tekst gewoon voor, ook al zie je hem niet.
#skippy
Het element met id="skippy". Dit is een zogenaamde skip-link. Het geeft aan gebruikers van een schermlezer de mogelijkheid in één keer het menu te passeren.
Omdat het menu voor schermlezers vrijwel hetzelfde is als dat voor gebruikers van de Tab-toets, staat de link niet recht boven, maar rechtsboven naast het menu. Maar omdat in de <html> deze link voor het menu komt, is het wel degelijk de eerste link.
background: white;
Witte achtergrond.
display: none;
De skip-link wordt normaal genomen verborgen. Het tonen wordt geregeld door het JavaScript, dat aan <body> de class 'xschermlezerx' toevoegt.
box-sizing: border-box;
Normaal genomen worden border en padding bij de breedte opgeteld. Hier is dat onhandig, omdat de skip-link netjes moet aansluiten bij het menu naast en de tekst onder de skip-link. Met deze regel komt de border binnen de breedte te vallen.
(Dit geldt hier alleen voor de breedte, want hieronder wordt alleen een breedte, geen hoogte, opgegeven. De hoogte van de skip-link wordt iets lager met behulp van line-height opgegeven. Daarom wordt in de hoogte de border nog wel bij deze regelhoogte opgeteld, want box-sizing werkt niet voor line-height, maar alleen voor width en height.)
width: 15rem;
Breedte.
De skip-link komt boven een <div> met een korte uitleg en het menu te staan. Het is netter, als de skip-link, de <div> en het menu dezelfde breedte hebben. Daarom wordt voor de breedte de relatieve eenheid rem gebruikt.
De rem werkt ongeveer hetzelfde als de bekendere eenheid em. Ook de rem is op de lettergrootte gebaseerd, maar altijd op de lettergrootte van <html>. Daardoor is de rem altijd overal even groot, ongeacht de lettergrootte van het element zelf. En ongeacht eventuele afwijkende lettergroottes in voorouders van het element. Als de bezoeker de lettergrootte verandert, wordt in feite de lettergrootte van <html> veranderd. Hierdoor verandert ook de grootte van de rem, maar die verandering is overal op de pagina precies hetzelfde.
Omdat de meeste maten in het menu als eenheid de rem gebruiken, blijft de lay-out van het menu ook intact, als de bezoeker de lettergrootte verandert.
Door de <a> hier 15 rem breed te maken, en de eronder staande <div> bij #uitleg-schermlezer en het menu bij nav dezelfde breedte te geven, zijn de skip-link, de <div> en het menu altijd even breed, ook als de bezoeker de lettergrootte verandert.
line-height: 3rem;
Regelhoogte. Omdat geen echte hoogte wordt opgegeven, is dit ook de hoogte van de <a> zelf. Tekst wordt automatisch in het midden van de regelhoogte gezet, dus de tekst staat verticaal gecentreerd binnen de <a>.
De <a> wordt 3 rem hoog gemaakt, en de <div> met de korte uitleg wordt iets lager bij #uitleg-schermlezer op 3,4 rem vanaf de bovenkant neergezet. Hierdoor sluiten <a> en <div> altijd goed op elkaar aan, ook als de bezoeker de lettergrootte verandert.
Je zou verwachten dat de <div> op 3 rem vanaf de bovenkant komt te staan, maar 3,4 rem komt iets beter uit vanwege de border die aan de onderkant van de <a> staat. Als je de <div> op 3 rem vanaf de bovenkant zet, moet je weer allerlei correcties gaan aanbrengen.
Als eenheid wordt de relatieve eenheid rem 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.
De rem is ongeveer hetzelfde als de bekendere eenheid em, maar is gebaseerd op de lettergrootte van <html>. Hierdoor is de rem, anders dan de em, overal op de pagina even groot. Ook als de bezoeker de lettergrootte heeft veranderd.
Omdat de meeste maten in het menu als eenheid de rem gebruiken, blijft de lay-out van het menu ook intact, als de bezoeker de lettergrootte verandert.
text-align: center;
Tekst horizontaal centreren.
border: red solid 3px;
Brede rode rand, zodat de skip-link goed opvalt.
position: absolute;
Om de skip-link op de goede plaats neer te zetten. Er wordt gepositioneerd ten opzichte van de eerste voorouder die zelf een andere positie dan statisch heeft. Als die er niet is, zoals hier het geval is, wordt gepositioneerd ten opzichte van het venster van de browser.
Een <a> is van zichzelf een inline-element. Daardoor zijn eigenschappen als breedte niet te gebruiken. Door de <a> absoluut te positioneren, verandert deze in een soort blok-element, waardoor dit soort eigenschappen wel is te gebruiken.
top: 0;
Bovenaan neerzetten.
nav
Alle <nav>'s. Er is hier maar een <nav>: de <nav> waar het hele menu in zit.
width: 15.9rem;
Breedte.
Als eenheid wordt de relatieve eenheid rem gebruikt, omdat bij een absolute eenheid zoals px de breedte niet mee verandert met de lettergrootte.
De rem werkt ongeveer hetzelfde als de bekendere eenheid em. Ook de rem is op de lettergrootte gebaseerd, maar altijd op de lettergrootte van <html>. Daardoor is de rem altijd overal even groot, ongeacht de lettergrootte van het element zelf. En ongeacht eventuele afwijkende lettergroottes in voorouders van het element. Als de bezoeker de lettergrootte verandert, wordt in feite de lettergrootte van <html> veranderd. Hierdoor verandert ook de grootte van de rem, maar die verandering is overal op de pagina precies hetzelfde.
Omdat de meeste maten in het menu als eenheid de rem gebruiken, blijft de lay-out van het menu ook intact, als de bezoeker de lettergrootte verandert.
Als in browservensters smaller dan 760 px het menu voor schermlezers wordt geopend, komt de <nav> met het menu onder een skip-link en een <div> met een korte uitleg te staan. De skip-link heeft eerder bij #skippy al een breedte van 15 rem gekregen, de <div> krijgt later bij #uitleg-schermlezer ook die breedte. De buitenste knoppen van het menu krijgen bij nav > ul > li ook deze breedte. Hierdoor zijn skip-link, <div> en menu voor schermlezers altijd even breed, ook als de bezoeker de lettergrootte verandert.
De <nav> met het menu komt onder een skip-link en een <div> met een korte uitleg te staan. De skip-link heeft eerder bij #skippy al een breedte van 15 rem gekregen, de <div> krijgt later bij #uitleg-schermlezer ook die breedte, en de buitenste knoppen van het menu krijgen die breedte bij nav > ul > li. Hierdoor zijn skip-link, <div> en menu altijd even breed, ook als de bezoeker de ettergrootte verandert.
Hier wordt de breedte iets groter gemaakt dan die breedte van 15 rem voor de drie genoemde elementen. Als in browservensters smaller dan 760 px submenu's worden geopend, past het menu in de hoogte al snel niet meer binnen het browservenster. Afhankelijk van browser en besturingssysteem kan dan aan de rechterkant van de <nav> een scrollbalk verschijnen. Deze scrollbalk wordt afgetrokken van de breedte van de <nav>.
De onderdelen binnen de <nav> zijn echter altijd 15 rem breed. Als daar nog een scrollbalk bijkomt, past dat niet meer in een <nav> die ook 15 rem breed is. Daardoor verschijnt ook een overbodige horizontale scrollbalk aan de onderkant van de <nav>. Een breedte van 15,9 rem levert 0,9 rem extra op voor de scrollbalk. Dat is genoeg om de horizontale scrollbalk altijd te verbergen.
Dat de <nav> iets breder is, heeft verder geen gevolgen, omdat de <nav> zelf geen achtergrondkleur of zo heeft.
overflow: auto;
De <nav> is fixed gepositioneerd ten opzichte van het browservenster. Hierboven is de bovenkant vastgezet op 2 rem van de bovenkant van het venster. Als het menu wordt geopend, wordt bij #checkbox-voor-smal:checked ~ nav de onderkant van de <nav> vastgezet aan de onderkant van het venster.
Als het menu hoger is dan het venster van de browser, kan hierdoor het menu dat aan de onderkant buiten het venster zit, niet worden bereikt. Alles is er wel, maar je kunt er niet bij.
Als de inhoud van een element er niet in past, wordt dit normaal genomen toch getoond, omdat overflow standaard op visible staat. Mogelijk wordt de lay-out dan verstoord, maar er verdwijnt in ieder geval geen tekst of zo.
Hier werkt dat niet, want je kunt tekst onder het browservenster alleen weergeven, als je een hogere monitor koopt. Dus tenzij je aandelen in een fabriek van hoge monitors hebt, moet dit op een of andere manier worden opgelost.
Met overflow: auto; kan er worden gescrold binnen de <nav>, als de inhoud er niet in past.
(In theorie kun je dit ook oplossen door overflow-x: hidden; en overflow-y: auto; (of overflow: hidden auto;) te gebruiken, met een breedte van 15 rem. In de breedte wordt de overflow dan altijd verborgen, in de hoogte kan worden gescrold, als dat nodig is (en verschijnt afhankelijk van besturingssysteem en browser mogelijk een scrollbalk).
Althans: dat is de theorie. Als je in het verleden overflow-x en overflow-y gebruikte, leverde dat een waar bacchanaal aan bugs en afwijkende implementaties op. Een korte test leerde dat dit nog steeds niet betrouwbaar werkt. Vandaar gewoon overflow: auto;, waarbij een horizontale scrollbalk voorkomen wordt door de iets hierboven opgegeven iets grotere breedte van 15,9 rem.
Als in browservensters smaller dan 760 px submenu's worden geopend, wordt het menu al snel te hoog voor het venster. Je kunt het menu dan horizontaal scrollen om toch overal bij te kunnen.
Door de <nav> fixed te positioneren, wordt deze vastgezet ten opzichte van het venster van de browser. Hierdoor blijft de <nav> op z'n plaats staan, als het menu wordt gescrold. Je scrolt niet de <nav> zelf, maar de inhoud daarvan. Hierdoor scrolt de rest van de pagina niet mee met het menu.
position: fixed;
Als in browservensters smaller dan 760 px submenu's worden geopend, wordt het menu al snel te hoog voor het venster. Je kunt het menu dan horizontaal scrollen om toch overal bij te kunnen.
Door de <nav> fixed te positioneren, wordt deze vastgezet ten opzichte van het venster van de browser. Hierdoor blijft de <nav> op z'n plaats staan, als het menu wordt gescrold. Je scrolt niet de <nav> zelf, maar de inhoud daarvan. Hierdoor scrolt de rest van de pagina niet mee met het menu.
top: 2rem;
Op 2 rem vanaf de bovenkant van het browservenster neerzetten.
In browservensters smaller dan 760 px staat bovenin het browservenster een menubalk, die bij #menubalk-voor-smal een hoogte van 2 rem heeft gekregen. De <nav> (en het menu in de <nav>) en de menubalk sluiten nu netjes op elkaar aan. Ook als de bezoeker de lettergrootte heeft veranderd.
Als eenheid wordt de relatieve eenheid rem gebruikt, omdat bij een absolute eenheid zoals px de afstand vanaf de bovenkant niet mee verandert met de lettergrootte.
De rem werkt ongeveer hetzelfde als de bekendere eenheid em. Ook de rem is op de lettergrootte gebaseerd, maar altijd op de lettergrootte van <html>. Daardoor is de rem altijd overal even groot, ongeacht de lettergrootte van het element zelf. En ongeacht eventuele afwijkende lettergroottes in voorouders van het element. Als de bezoeker de lettergrootte verandert, wordt in feite de lettergrootte van <html> veranderd. Hierdoor verandert ook de grootte van de rem, maar die verandering is overal op de pagina precies hetzelfde.
Omdat de meeste maten in het menu als eenheid de rem gebruiken, blijft de lay-out van het menu ook intact, als de bezoeker de lettergrootte verandert.
Als twee of meer elementen ten opzichte van elkaar neergezet moeten worden, een bepaalde grootte ten opzichte van elkaar moeten hebben, en dergelijke, is de eenheid rem vrijwel altijd beter dan de eenheid em. (Even los van nieuwere mogelijkheden als flexbox en grid, maar die worden hier niet gebruikt.)
#uitleg-tab, #uitleg-schermlezer
De elementen met id="uitleg-tab" en id="uitleg-schermlezer". Twee <div>'s met respectievelijk een korte uitleg voor gebruikers van de Tab-toets en gebruikers van een schermlezer.
Veel eigenschappen zijn voor beide <div>'s hetzelfde. Een aantal eigenschappen wordt later bij #uitleg-schermlezer voor de uitleg voor de schermlezer aangepast.
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.
Met behulp van JavaScript kunnen gebruikers van de Tab-toets door middel van speciale toetsen snel door het menu navigeren. Het is handig als die uitleg altijd zichtbaar is, ook als het menu te hoog is voor het browservenster en er moet worden gescrold. Daarom wordt die uitleg bovenaan het venster vastgezet.
#uitleg-tab h2, #uitleg-schermlezer h2
Alle <h2>'s binnen de elementen met id="uitleg-tab" en id="uitleg-schermlezer". Boven elke uitleg staat één <h2>.
font-weight: bold;
Standaard wordt een <h2> vet weergegeven, maar dat wordt verderop bij nav h2, nav h3, nav h4 veranderd in normaal. Deze twee kopregels moeten echter wel vet worden weergegeven, anders vallen ze niet op.
Als de gebruikte lettersoort een vette variant heeft, wordt die gebruikt. De meeste lettersoorten hebben wel een vette variant, soms zelfs meerdere. Een vette variant wordt speciaal ontworpen, het is iets anders dan 'alle lijntjes wat dikker maken'. Sommige letters kunnen er zelfs heel anders uitzien.
Als de lettersoort geen vette variant heeft, maakt de browser de letter vet. Dat wil zeggen dat de browser gewoon alle lijntjes wat dikker maakt. Dat kan mooi zijn, maar vaak is het foeilelijk. Het is heel iets anders dan een speciaal ontworpen font. Elke rechtgeaarde typograaf gruwt hiervan.
#uitleg-tab p, #uitleg-schermlezer p
Alle <p>'s binnen de elementen met id="uitleg-tab" en id="uitleg-schermlezer". De <p>'s waar de uitleg voor gebruikers van Tab-toets en schermlezers in staat.
margin: 0;
Standaard heeft een <p> een marge aan boven- en onderkant. Die wordt hier verwijderd.
margin-left: 20px; text-indent: -20px;
Aan de linkerkant wordt een marge van 20 px gegeven, waardoor de hele <p>, en dus alle tekst daarbinnen, 20 px vanaf de linkerkant komt te staan.
Met text-indent: -20px; wordt de eerste regel tekst 20 px naar links verplaatst, waardoor deze weer op de normale plaats komt te staan.
Het resultaat van deze hele exercitie: alle tekst staat 20 px naar rechts, behalve de eerste regel. Hierdoor springt alle tekst in, behalve de eerste regel. Daardoor vallen nieuwe onderwerpjes beter op.
Je kunt tekst ook op andere manieren laten inspringen, maar met text-indent springt alleen de eerste regel in. (Of eigenlijk: spring de eerste regel uit, want hier is een negatieve waarde gebruikt.) Als de regels langer of korter worden, bijvoorbeeld door een andere lettergrootte, wordt dit automatisch door de browser aangepast: alleen de eerste regel wordt naar links geplaatst.
#uitleg-schermlezer
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.
Het element met id="uitleg-schermlezer". De <div> waarin uitleg voor schermlezers staat.
box-sizing: border-box;
Normaal genomen worden border en padding bij de breedte opgeteld. Hier is dat onhandig, omdat de <div> even breed als de erboven staande skip-link en het eronder zittende menu moet worden. Met deze regel komen border en padding binnen de breedte te vallen.
width: 15rem;
Breedte. Omdat de skip-link boven de <div> bij #skippy en het menu zelf bij nav > ul > li dezelfde breedte krijgen, zijn die alle twee altijd even breed als deze uitleg, ook als de bezoeker de lettergrootte verandert.
Als eenheid wordt de relatieve eenheid rem gebruikt, omdat bij een absolute eenheid zoals px de breedte niet mee verandert met de lettergrootte.
De rem werkt ongeveer hetzelfde als de bekendere eenheid em. Ook de rem is op de lettergrootte gebaseerd, maar altijd op de lettergrootte van <html>. Daardoor is de rem altijd overal even groot, ongeacht de lettergrootte van het element zelf. En ongeacht eventuele afwijkende lettergroottes in voorouders van het element. Als de bezoeker de lettergrootte verandert, wordt in feite de lettergrootte van <html> veranderd. Hierdoor verandert ook de grootte van de rem, maar die verandering is overal op de pagina precies hetzelfde.
Omdat de meeste maten in het menu als eenheid de rem gebruiken, blijft de lay-out van het menu ook intact, als de bezoeker de lettergrootte verandert.
Om de <div> op de juiste plaats neer te kunnen zetten. Er wordt gepositioneerd ten opzichte van de eerste voorouder die een andere positie dan statisch heeft. Als die er niet is, zoals hier het geval is, wordt gepositioneerd ten opzichte van het venster van de browser.
(De ouder van deze <div>, de <nav> waarin het hele menu staat, heeft bij nav een fixed positie gekregen. Maar op het moment dat deze <div> wordt getoond, wordt dat bij .xschermlezerx nav veranderd in een statische positie. Anders zou worden gepositioneerd ten opzichte van de <nav>.)
top: 3.4rem;
3,4 rem vanaf de bovenkant neerzetten.
Boven de uitleg staat de skip-link, die bij #skippy een hoogte van 3 rem heeft gekregen. Aan de onderkant van de skip-link staat een breder rode border. Omdat deze uitleg in de html na de skip-link komt, komt de uitleg over de skip-link te staan. Hierdoor valt de brede border aan de onderkant van de skip-link weg.
Door de <div> met de uitleg iets lager dan 3 rem te zetten, wordt dat voorkomen.
Als eenheid wordt de relatieve eenheid rem gebruikt, omdat bij een absolute eenheid zoals px de afstand vanaf de bovenkant niet mee verandert met de lettergrootte.
De rem werkt ongeveer hetzelfde als de bekendere eenheid em. Ook de rem is op de lettergrootte gebaseerd, maar altijd op de lettergrootte van <html>. Daardoor is de rem altijd overal even groot, ongeacht de lettergrootte van het element zelf. En ongeacht eventuele afwijkende lettergroottes in voorouders van het element. Als de bezoeker de lettergrootte verandert, wordt in feite de lettergrootte van <html> veranderd. Hierdoor verandert ook de grootte van de rem, maar die verandering is overal op de pagina precies hetzelfde.
Omdat de meeste maten in het menu als eenheid de rem gebruiken, blijft de lay-out van het menu ook intact, als de bezoeker de lettergrootte verandert.
Als twee of meer elementen ten opzichte van elkaar neergezet moeten worden, een bepaalde grootte ten opzichte van elkaar moeten hebben, en dergelijke, is de eenheid rem vrijwel altijd beter dan de eenheid em. (Even los van nieuwere mogelijkheden als flexbox en grid, maar die worden hier niet gebruikt.)
nav ul
Alle <ul>'s binnen een <nav>. Er is in het voorbeeld maar één <nav> aanwezig, waarin een hele reeks <ul>'s zit. Elk submenu zit in een eigen <ul>. Een aantal eigenschappen is voor alle <ul>'s hetzelfde, die worden hier in één keer opgegeven.
nav aan het begin van deze selector is eigenlijk overbodig, en dat geldt voor veel meer selectors die met nav beginnen. Maar omdat de elementen binnen de <nav> vaak voorkomen, is voor de veiligheid aan het begin van de selector nav gezet. Nu worden bijvoorbeeld <a>'s elders op de pagina niet per ongeluk ook geselecteerd.
height: 0;
Hoogte. Een hoogte van 0 betekent: helemaal geen hoogte.
Hoewel je in browservensters smaller dan 760 px het menu niet ziet, is dit feitelijk steeds aanwezig. Als je deze regel weghaalt, kun je dat zien. Op de afbeelding is alleen deze regel weggehaald, waardoor de <ul> zichtbaar wordt. Dat levert een weliswaar uitermate rustiek grijs uiterlijk op, maar mogelijk willen mensen toch liever een menu, waarin ze iets kunnen kiezen.
Je ziet vrijwel geen links en dergelijke op de afbeelding, omdat het grootste deel verderop bij nav ul ul met opacity: 0; onzichtbaar is gemaakt. Die links zijn er echter wel degelijk. Als je zo'n onzichtbare link aanraakt, wordt de link gewoon gevolgd. En zelfs liefhebbers van een rustiek menu raken mogelijk zelf ietwat minder rustiek, als ze uit 125 onzichtbare links de juiste moeten kiezen.
Kortom: het is handig het menu te verbergen, tot het wordt geopend.
Alle menu's zitten in een eigen <ul>. Door die <ul>'s een hoogte van 0 te geven, wordt de inhoud ervan verborgen.
overflow: hidden;
Gelijk hierboven hebben alle <ul>'s van het menu een hoogte van 0 px gekregen. Maar standaard wordt de inhoud van een element toch getoond, ook al past het er niet in. Mogelijk wordt de lay-out dan verstoord, maar er verdwijnt in ieder geval geen tekst of zo.
Hier is dat niet de bedoeling. Met overflow: hidden; wordt alles, dat niet in de <ul>'s past, verborgen. En omdat de <ul>'s 0 px hoog zijn, past er helemaal niets in. Waardoor dus álles wordt verborgen.
list-style-type: none;
De gebruikelijke balletjes en dergelijke bij een <ul> zijn hier niet welkom.
margin: 0; padding: 0;
Verschillende browsers hebben verschillende standaardinstellingen voor marge en padding. Nu zijn ze allemaal hetzelfde.
nav > 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.
nav ul {height: 0; overflow: hidden; list-style-type: none; margin: 0; padding: 0;}
Alle <ul>'s binnen een <nav>. Het teken > geeft aan, dat het alleen <ul>'s mogen zijn die een direct kind zijn van een <nav>. Onderstaande <ul> is een direct kind van nav#uitklapmenu:
<nav id="uitklapmenu"><ul></ul></nav>
De middelste <ul> hieronder is geen direct kind van nav#uitklapmenu, omdat er een <ul> en een <li> tussen nav#uitklapmenu en de binnenste <ul> zitten:
In dit voorbeeld is er maar één <ul> die een direct kind van nav#uitklapmenu is. Binnen deze <ul> zit het hele menu. Alle andere <ul>'s zijn weer nakomelingen van deze <ul> en dus geen direct kind van nav#uitklapmenu. Deze selector geldt dus alleen voor de buitenste <ul> van het menu.
position: absolute;
Om de <ul>, en daarmee het hele menu, op de juiste plaats neer te kunnen zetten.
Er wordt gepositioneerd ten opzichte van de eerste voorouder van het element die geen statische positie heeft. Dat is hier de <nav>, die bij nav fixed is gepositioneerd op 2 rem vanaf de bovenkant van het browservenster, gelijk onder de menubalk.
top: 1px;
1 px vanaf de bovenkant neerzetten. Op deze hoogte staat de <ul> precies onder de menubalk.
nav li
Alle <li>'s binnen een <nav>. Er is in dit voorbeeld maar één <nav> aanwezig, waarbinnen het hele menu zit. Binnen dat menu zit elke kopregel (<h>) en elke link (<a>) binnen een eigen <li>.
background: #e4e4e4;
Grijze 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.
box-sizing: border-box;
Normaal genomen worden border en padding bij de breedte opgeteld. Hier komt dat slecht uit, omdat de <li>'s precies op de juiste plaats moeten komen te staan, zodat je geen kieren of overlappingen in het menu krijgt.
Met deze regel worden border en padding niet bij de breedte opgeteld, maar vallen ze binnen de breedte.
(Dit geldt hier alleen voor de breedte, want hieronder wordt alleen een breedte, geen hoogte, opgegeven. De hoogte van de <li>'s wordt iets lager met behulp van line-height opgegeven. Daarom wordt in de hoogte de border nog wel bij deze regelhoogte opgeteld, want box-sizing werkt niet voor line-height, maar alleen voor width en height. Maar het probleem van de juiste plaatsing speelt alleen in de breedte, dus dit maakt niets uit.)
line-height: 2.5rem;
Regelhoogte. Omdat geen gewone hoogte wordt opgegeven, is dit ook de hoogte van de <li>. Tekst wordt automatisch halverwege de regelhoogte gezet, zodat de tekst verticaal is gecentreerd.
Als eenheid wordt de relatieve eenheid rem 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.
De rem is ongeveer hetzelfde als de bekendere eenheid em, maar is gebaseerd op de lettergrootte van <html>. Hierdoor is de rem , anders dan de em, overal op de pagina even groot. Ook als de bezoeker de lettergrootte heeft veranderd.
Omdat de meeste maten in het menu als eenheid de rem gebruiken, blijft de lay-out van het menu ook intact, als de bezoeker de lettergrootte verandert.
text-indent: 0.3rem;
Tekst klein beetje laten inspringen.
Als eenheid wordt de relatieve eenheid rem gebruikt, omdat bij een absolute eenheid zoals px de inspringing niet mee verandert met de lettergrootte.
De rem werkt ongeveer hetzelfde als de bekendere eenheid em. Ook de rem is op de lettergrootte gebaseerd, maar altijd op de lettergrootte van <html>. Daardoor is de rem altijd overal even groot, ongeacht de lettergrootte van het element zelf. En ongeacht eventuele afwijkende lettergroottes in voorouders van het element. Als de bezoeker de lettergrootte verandert, wordt in feite de lettergrootte van <html> veranderd. Hierdoor verandert ook de grootte van de rem, maar die verandering is overal op de pagina precies hetzelfde.
Omdat de meeste maten in het menu als eenheid de rem gebruiken, blijft de lay-out van het menu ook intact, als de bezoeker de lettergrootte verandert.
border: black solid 1px;
Zwart randje rondom de <li>'s.
border-top: none;
De <li>'s zitten onder elkaar. Als ze aan boven- én onderkant een border hebben, komen die tegen elkaar aan te staan, waardoor je daar een border van 2 px dik krijgt. Daarom wordt de border aan de bovenkant hier weer weggehaald.
position: relative;
Om nakomelingen van de <li> te kunnen positioneren ten opzichte van de <li>, moet de <li> zelf een andere positie dan statisch hebben. Een relatieve positie heeft verder geen invloed op de <li>'s zelf, omdat niets voor top en dergelijke wordt opgegeven.
-ms-user-select: none;
In Internet Explorer en Edge op touchscreens wordt regelmatig de tekst uit de link geselecteerd, in plaats van dat de link wordt gevolgd. Hiermee wordt het selecteren van tekst voorkomen.
Omdat dit probleem alleen in Internet Explorer en Edge speelt, is in dit geval alleen de eigenschap met het voorvoegsel -ms- voldoende. Waarom dit alleen in Internet Explorer en Edge werkt, staat beschreven bij De voorvoegsels -moz-, -ms- en -webkit-.
nav > ul > li
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.
nav li {background: #e4e4e4; color: black; box-sizing: border-box; line-height: 2.5rem; text-indent: 0.3rem; border: black solid 1px; border-top: none; position: relative; -ms-user-select: none;}
Alle <li>'s binnen een <ul>, die weer binnen een <nav> staat. Het teken > geeft aan, dat het alleen <li>'s mogen zijn die een direct kind zijn van een <ul>, die zelf weer een direct kind van een <nav> is.. Onderstaande <li> voldoet aan die voorwaarde: de <li> is een direct kind van een <ul>, die weer een direct kind van nav#uitklapmenu is:
<nav id="uitklapmenu"><ul><li></li></ul></nav>
De binnenste <li> hieronder voldoet niet aan deze eis, omdat de binnenste <ul> geen direct kind van nav#uitklapmenu is:
De hele selector in normale mensentaal: doe iets met de <li>'s die een direct kind van de <ul> zijn, die weer een direct van de <nav> is.
In dit voorbeeld is er maar één <ul> die een direct kind van nav#uitklapmenu is. Alleen de <li>'s uit die ene <ul> vallen onder deze selector. Het zijn de zes <li>'s met de bovenste knoppen: 'Kop 1' tot en met 'Kop 6'. In browservensters minimaal 760 px breed zijn deze <li>'s altijd zichtbaar. In smallere vensters zijn het de knoppen, die bij het openen van het menu worden getoond.
width: 15rem;
Breedte.
Als eenheid wordt de relatieve eenheid rem gebruikt, omdat bij een absolute eenheid zoals px de breedte niet mee verandert met de lettergrootte.
De rem werkt ongeveer hetzelfde als de bekendere eenheid em. Ook de rem is op de lettergrootte gebaseerd, maar altijd op de lettergrootte van <html>. Daardoor is de rem altijd overal even groot, ongeacht de lettergrootte van het element zelf. En ongeacht eventuele afwijkende lettergroottes in voorouders van het element. Als de bezoeker de lettergrootte verandert, wordt in feite de lettergrootte van <html> veranderd. Hierdoor verandert ook de grootte van de rem, maar die verandering is overal op de pagina precies hetzelfde.
Omdat de meeste maten in het menu als eenheid de rem gebruiken, blijft de lay-out van het menu ook intact, als de bezoeker de lettergrootte verandert.
border-left: none;
Bij deze bovenste rij knoppen moet ook de border links worden verwijderd.
In browservensters smaller dan 760 px komen deze knoppen tegen de linkerkant van het venster te staan. Op de afbeelding is te zien, dat de knoppen hierdoor een lelijke brede border links krijgen.
In bredere browservensters staan deze zes knoppen naast elkaar, waardoor er tussen de knoppen een dubbele border komt te staan.
In beide gevallen moet de border links worden verwijderd.
nav h2, nav h3, nav h4
Voor een deel van 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.
Alle <h2>'s, <h3>'s en <h4>'s in een <nav>. Boven elk submenu staat een kop die, afhankelijk van het niveau van het submenu, in een <h2>, <h3> of <h4> staat.
display: inline;
Een <h> is een blok-element, waardoor het op een nieuwe regel komt te staan. Dat is hier niet de bedoeling, daarom wordt het in een inline-element veranderd.
font-size: 1rem;
Lettergrootte.
Als eenheid wordt de relatieve eenheid rem 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.
De rem is ongeveer hetzelfde als de bekendere eenheid em, maar is gebaseerd op de lettergrootte van <html>. Hierdoor is de rem, anders dan de em, overal op de pagina even groot. Ook als de bezoeker de lettergrootte heeft veranderd.
Omdat de meeste maten in het menu als eenheid de rem gebruiken, blijft de lay-out van het menu ook intact, als de bezoeker de lettergrootte verandert.
font-weight: normal;
Een <h> is standaard vet. Hier wordt er normale tekst van gemaakt.
line-height: 0;
Bij nav li is een regelhoogte van 2,5 rem aan de <li>'s gegeven. Die regelhoogte is belangrijk, want die bepaalt mede de juiste positie van de <li>'s in het menu (zonder kieren of overlappingen). Omdat een <li> een blok-element is, is die regelhoogte gelijk ook de minimum-regelhoogte voor alle inline-elementen in het blok.
De <h>'s zijn iets hierboven met display: inline; in inline-elementen veranderd. Deze minimum-regelhoogte geldt daarom ook voor deze <h>'s. Omdat tekst automatisch halverwege de regelhoogte komt te staan, is de tekst in de <h>'s gelijk verticaal gecentreerd.
Tot zover een mooi verhaal.
De regelhoogte van de <li>'s is een minimum-regelhoogte. Maar de regelhoogte van een inline-element in die <li> kan wel groter worden dan de regelhoogte van de <li>. Dit blijkt in enkele browsers te gebeuren, waardoor de <li>'s met kopregels niet meer op de goede plaats staan. Door de regelhoogte bij de <h>'s gewoon weg te halen, wordt dit voorkomen.
nav a
Alle <a>'s in een <nav>. Elke link in het menu staat in een <a>.
color: black;
Een link heeft standaard een afwijkende kleur. Dat is hier niet mooi.
display: block;
Een <a> is van zichzelf een inline-element. Hierdoor moet je precies de tekst in de link aanraken of -klikken om de link te volgen. Door de <a>'s te veranderen in een blok-element, worden ze even groot als de knoppen (de <li>'s), waar ze in zitten, en daarmee ook het raakvlak van de <a>.
text-decoration: none;
Standaard wordt een link onderstreept. Dat is hier niet mooi.
nav ul 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.
nav ul {height: 0; overflow: hidden; list-style-type: none; margin: 0; padding: 0;}
<ul>'s die binnen een andere <ul> zitten, die weer binnen een <nav> zit. Dit zijn de <ul>'s, waarin de submenu's van het tweede, derde en vierde niveau zitten. (De <ul>'s voor het derde en vierde niveau worden later op diverse punten aangepast.)
opacity: 0;
Onzichtbaar maken.
Als in een browservenster smaller dan 760 px een submenu wordt geopend, knalt dat nogal abrupt open. Om de opening tot een iets minder schokkende gebeurtenis te maken, is het submenu bij opening onzichtbaar en wordt met behulp van transition iets hieronder gedurende 'n seconde geleidelijk aan zichtbaar. Hierdoor verloopt de opening wat vriendelijker.
box-shadow: black -5px -4px 10px;
De schaduw maakt in browservensters smaller dan 760 px duidelijker, bij welke kop een bepaald submenu hoort.
Rondom elke link en kop in het menu staat een lijntje, maar aan dat lijntje is niet duidelijk te zien, bij welke kop een bepaald submenu hoort. Door boven en links van een submenu een schaduw aan te brengen, zoals op de linkerafbeelding, is gelijk duidelijk, bij welke kop een submenu hoort.
Met behulp van box-shadow kan schaduw worden gegeven aan een element, zoals een <ul>.
Als eerste wordt de kleur opgegeven: black (zwart). Omdat dit dezelfde kleur is als de voorgrondkleur, is dit eigenlijk niet nodig. Als je geen kleur opgeeft, wordt automatisch de voorgrondkleur gebruikt. Oudere versies van Android browser en Opera Mini op Android negeren de box-shadow echter, als je geen kleur opgeeft.
Als je verder niets opgeeft, is de schaduw precies even groot als het element, waar de schaduw bij hoort. Dat schiet niet echt op, want dat betekent dat de schaduw precies onder het element staat en dus onzichtbaar is. De natte droom van de Gevorderde Geheim Agent, maar niet van een sitebouwer.
Daarom wordt de schaduw met behulp van de twee waarden -5px en -4px aangepast.
De eerste waarde is altijd voor de horizontale verplaatsing van de box-shadow. Hier wordt de schaduw 5 px naar links gezet. (Een positieve waarde zou de schaduw naar rechts verplaatsen).
De tweede waarde is altijd voor de verticale verplaatsing. Hier wordt de schaduw 4 px naar boven gezet. (Een positieve waarde zou de schaduw naar beneden verplaatsen.)
De derde waarde is de breedte van de vervaging. Als je hier niets opgeeft, is de schaduw 'n gewone lijn. Door de schaduw over 'n breedte van 10 px te laten vervagen, wordt het pas echt 'n soort schaduw.
Je kunt nog meer dingen opgeven bij een box-shadow, maar hier worden alleen de kleur, de horizontale en verticale verplaatsing en de vervaging gebruikt.
width: 14rem;
Breedte. De buitenste <ul> is bij nav > ul > li 15 rem breed gemaakt, dus deze <ul>'s passen daar ruim binnen.
Als eenheid wordt de relatieve eenheid rem gebruikt, omdat bij een absolute eenheid zoals px de breedte niet mee verandert met de lettergrootte.
De rem werkt ongeveer hetzelfde als de bekendere eenheid em. Ook de rem is op de lettergrootte gebaseerd, maar altijd op de lettergrootte van <html>. Daardoor is de rem altijd overal even groot, ongeacht de lettergrootte van het element zelf. En ongeacht eventuele afwijkende lettergroottes in voorouders van het element. Als de bezoeker de lettergrootte verandert, wordt in feite de lettergrootte van <html> veranderd. Hierdoor verandert ook de grootte van de rem, maar die verandering is overal op de pagina precies hetzelfde.
Omdat de meeste maten in het menu als eenheid de rem gebruiken, blijft de lay-out van het menu ook intact, als de bezoeker de lettergrootte verandert.
margin: 1px 0 0 1rem;
De ene px marge aan de bovenkant is alleen van belang voor browservensters minstens 760 px breed. Zonder deze marge komen de submenu's van het tweede niveau net over de knoppen in de menubalk te staan, waardoor de border aan de onderkant van die knoppen wegvalt.
De marge links zorgt ervoor dat de submenu's 1 rem naar rechts komen te staan, waardoor duidelijk is bij welke kop ze horen. De marge van 1 rem wordt bij de iets hierboven opgegeven breedte van 14 rem opgeteld, waardoor het totaal 15 rem breed is. Precies even breed als de buitenste <ul>, waarvan de daarin zittende <li>'s bij nav > ul > li ook een breedte van 15 rem hebben gekregen. De submenu's vullen daardoor precies de ruimte in de buitenste <ul> op, maar met een lege ruimte aan de linkerkant.
(In browservensters minimaal 760 px breed ziet het er heel anders uit en wordt deze marge weer weggehaald.)
Als eenheid wordt de relatieve eenheid rem gebruikt, omdat bij een absolute eenheid zoals px de breedte van de marge niet mee verandert met de lettergrootte.
De rem werkt ongeveer hetzelfde als de bekendere eenheid em. Ook de rem is op de lettergrootte gebaseerd, maar altijd op de lettergrootte van <html>. Daardoor is de rem altijd overal even groot, ongeacht de lettergrootte van het element zelf. En ongeacht eventuele afwijkende lettergroottes in voorouders van het element. Als de bezoeker de lettergrootte verandert, wordt in feite de lettergrootte van <html> veranderd. Hierdoor verandert ook de grootte van de rem, maar die verandering is overal op de pagina precies hetzelfde.
Omdat de meeste maten in het menu als eenheid de rem gebruiken, blijft de lay-out van het menu ook intact, als de bezoeker de lettergrootte verandert.
transition: opacity 1s;
Iets hierboven zijn met opacity: 0; alle geneste <ul>'s onzichtbaar gemaakt. Hoewel een onzichtbaar menu gewoon volledig werkt, is het misschien wat onhandig als je niet kunt zien, welke link je aanraakt of -klikt. Daarom wordt een submenu, als dit wordt getoond, later op diverse plaatsen in de css met opacity: 1; zichtbaar gemaakt.
Als je bij :hover, :focus, :checked, en dergelijke een andere waarde bij opacity opgeeft, zorgt transition ervoor dat opacity niet in één keer naar die nieuwe waarde wordt veranderd bij hoveren en dergelijke, maar geleidelijk. Als de zichtbaarheid van 0 (onzichtbaar) naar 1 (zichtbaar) wordt veranderd, vindt die verandering geleidelijk aan plaats: geleidelijk van onzichtbaar naar zichtbaar. Hierdoor opent een submenu wat vriendelijker.
Bij transition staat maar één waarde: 1s. Als er maar één waarde wordt opgegeven, bepaalt die over hoeveel tijd de verandering wordt uitgesmeerd. Dat is hier één seconde. Een eventuele tweede waarde, die hier ontbreekt, geeft een eventueel uitstel aan het begin van de verandering aan.
Het is ook mogelijk om het verloop van de verandering aan te geven met behulp van sleutelwoorden of getallen. Hiermee kun je bijvoorbeeld aangeven dat de verandering aan het eind snel of juist langzaam moet gaan. Bij een verandering die maar 1 seconde duurt, is het verschil nauwelijks te zien, dus dat wordt hier niet gebruikt.
transition 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 niet opgeeft, voor welke eigenschappen transition geldt, geldt het voor álle eigenschappen die bij :hover, :focus, :checked, en dergelijke worden veranderd. Als daar 'n eigenschap bij zit die op dit moment niet door transition worden vertraagd, verandert die gewoon in één keer. 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. Daarom moet je absoluut zeker weten dat ook in de toekomst geen problemen kunnen ontstaan. Bij twijfel: geef aan welke eigenschap(pen) transition mag aansturen.
In dit voorbeeld wordt :checked nogal uitbundig gebruikt om hele reeksen eigenschappen te veranderen. In mindere mate worden ook nog :focus en :hover gebruikt. Er is dus een kans dat transition onbedoeld voor meer eigenschappen dan alleen opacity blijkt te werken. Ook kan de code in de toekomst worden gewijzigd, waardoor dat alsnog zou kunnen gebeuren.
Mogelijk is het hier overbodig, maar om alle risico's uit te sluiten wordt opacity hier genoemd. Zodra één of meer eigenschappen met name worden genoemd bij transition, werkt transition alleen nog voor die eigenschappen.
Voor browservensters minimaal 760 px breed staat verderop op een aantal plaatsen css, die bij hoveren over een kop in het menu het bij die kop horende submenu zichtbaar maakt met opacity: 1; (op een touchscreen heeft een aanraking hetzelfde effect). Door daar voor transition een vertraging van 0 seconde op te geven, opent het submenu wel in één keer. Een vertraging zou hier juist storend werken.
Veel mensen vinden gevoelsmatig dat je die waarden juist andersom zou moeten opgeven, maar het is nou eenmaal niet anders: de waarde die bij hoveren werkt, geef je hier op, en omgekeerd.
nav ul ul 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.
nav ul {height: 0; overflow: hidden; list-style-type: none; margin: 0; padding: 0;}
nav ul ul {opacity: 0; box-shadow: black -5px -4px 10px; width: 14rem; margin: 1px 0 0 1rem; transition: opacity 1s;}
<ul>'s die binnen een andere <ul> zitten, die weer binnen een <ul> zit, die weer binnen een <nav> zit. Dit zijn de <ul>'s, waarin de submenu's van het derde en vierde niveau zitten. (De <ul>'s voor het vierde niveau worden later op diverse punten aangepast.)
width: 12.95rem; width: calc(13rem - 1px);
Hier wordt twee keer een breedte opgegeven.
De eerste breedte is voor oudere versies van Android browser, want die ondersteunen het in de tweede breedte gebruikte calc() niet. Omdat Android browser de tweede breedte niet begrijpt, wordt die gewoon genegeerd en wordt de eerste breedte gebruikt.
Andere browsers kennen calc() wel, en omdat de tweede breedte in de css na de eerste breedte staat, 'wint' de tweede breedte.
Bij nav > ul > li zijn de buitenste <li>'s 15 rem breed gemaakt. Bij nav ul ul zijn alle daarin zittende geneste <ul>'s 14 rem breed gemaakt. Hier worden de daar weer in zittende dubbel geneste <ul>'s iets minder dan 13 rem breed gemaakt, zodat ze in de geneste <ul>'s van 14 rem passen.
Omdat alle geneste <ul>'s bij nav ul ul aan de linkerkant een marge van 1 rem hebben gekregen, krijgen ook deze dubbel geneste <ul>'s weer een lege ruimte van 1 rem aan de linkerkant.
Op de afbeelding is de breedte even 12,9 rem gemaakt, omdat de afwijking in Android browser dan duidelijker is te zien. Het middelste submenu is iets te smal, omdat 12,95 rem een benadering is. Daardoor ontstaat er aan de rechterkant een klein kiertje.
Met behulp van calc() kunnen berekeningen worden gemaakt. Daarbij worden alle eenheden automatisch door de browser omgezet naar pixels, zodat je met verschillende eenheden kunt rekenen. De exact juiste breedte is 13 rem min 1 px.
Als eenheid wordt de relatieve eenheid rem gebruikt, omdat bij een absolute eenheid zoals px de breedte niet mee verandert met de lettergrootte.
De rem werkt ongeveer hetzelfde als de bekendere eenheid em. Ook de rem is op de lettergrootte gebaseerd, maar altijd op de lettergrootte van <html>. Daardoor is de rem altijd overal even groot, ongeacht de lettergrootte van het element zelf. En ongeacht eventuele afwijkende lettergroottes in voorouders van het element. Als de bezoeker de lettergrootte verandert, wordt in feite de lettergrootte van <html> veranderd. Hierdoor verandert ook de grootte van de rem, maar die verandering is overal op de pagina precies hetzelfde.
Omdat de meeste maten in het menu als eenheid de rem gebruiken, blijft de lay-out van het menu ook intact, als de bezoeker de lettergrootte verandert.
nav ul ul ul 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.
nav ul {height: 0; overflow: hidden; list-style-type: none; margin: 0; padding: 0;}
nav ul ul {opacity: 0; box-shadow: black -5px -4px 10px; width: 14rem; margin: 1px 0 0 1rem; transition: opacity 1s;}
nav ul ul ul {width: 12.95rem; width: calc(13rem - 1px);}
<ul>'s die binnen een andere <ul> zitten, die weer binnen een <ul> zit, die ook weer binnen een <ul> zit, die weer binnen een <nav> zit. Dit zijn de <ul>'s, waarin de submenu's van het vierde niveau zitten.
width: 11.9rem; width: calc(12rem - 2px);
Het verhaal over de breedte is precies hetzelfde als hierboven bij nav ul ul ul, alleen worden deze driedubbel geneste <ul>'s van het vierde niveau weer iets smaller, zodat ze in de dubbel geneste <ul>'s van het derde niveau passen.
nav ul 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.
nav li {background: #e4e4e4; color: black; box-sizing: border-box; line-height: 2.5rem; text-indent: 0.3rem; border: black solid 1px; border-top: none; position: relative; -ms-user-select: none;}
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 selector :nth-of-type() worden alleen elementen van een bepaald soort geteld. Omdat het vaak voorkomt dat je het laatste element van 'n bepaald type wilt hebben, heeft dat een aparte selector: :last-of-type.
nav ul ul: alle <ul>'s die in een <ul> zitten, die weer in een <nav> zit. Dit zijn alle <ul>'s waarbinnen de submenu's van het tweede, derde en vierde niveau zitten.
:last-of-type: het element met een bepaald volgnummer. In dit geval wordt geen volgnummer gebruikt, maar een speciaal voor het laatste element bedoelde selector: :last-of-type. Voor alle eerdere elementen gebruik je 'n soortgelijke selector, maar dan met een volgnummer: :nth-of-type(). Tussen de haakjes komt het volgnummer. :nth-of-type(1). Het voordeel van :last-of-type: dit past zich automatisch aan, als ooit een <li> wordt toegevoegd of verwijderd. Deze selector geldt altijd alleen voor de laatste <li>.
Omdat voor :last-of-type (of voor eerdere elementen :nth-of-type()) nav ul ul staat, geldt deze selector alleen voor <li>'s die binnen een minimaal dubbel geneste <ul> binnen een < nav> zitten. Ook bijvoorbeeld <li>'s binnen nav ul ul ul vallen onder deze selector, want ook die zitten in een dubbel geneste <ul> binnen een <nav>.
De hele selector in normale mensentaal: alle <li>'s die in een minstens dubbel geneste <ul> binnen een <nav> zitten, maar alleen de laatste van die <li>'s.
Dit zijn de laatste <li>'s van de submenu's van het tweede, derde en vierde niveau.
border-bottom: none;
Border aan de onderkant verwijderen. Deze <li>'s komen tegen de bovenkant van een andere <li> te staan. Die andere <li> heeft al een border aan de bovenkant. Hierdoor zou je hier een dubbele border van 2 px krijgen. Dit voorkomt dat.
.pijltje, .pijltje-links
De elementen met class="pijltje" en class="pijltje-links". Dit zijn de pijltjes die in browservensters minimaal 760 px breed aangeven dat er een submenu is.
display: none;
Verbergen. In browservensters smaller dan 760 px worden andere pijltjes gebruikt.
nav input
Alle <input>'s binnen een <nav>. Alle <input>'s binnen het menu. Door het aan- of uitvinken van een <input type="checkbox"> wordt in browservensters smaller dan 760 px het bijbehorende submenu getoond of verborgen.
opacity: 0;
Aankruisvakjes (<input type="checkbox">) gebruiken om submenu's te openen komt niet in aanmerking voor een schoonheidsprijs. Daarom worden ze aan- en uitgevinkt met behulp van bij de <input> horende <label>'s, waarin een soort pijltje staat. De aankruisvakjes zelf worden verborgen.
Op de afbeelding zijn de aankruisvakjes zichtbaar gemaakt. Onder elk aankruisvakje zit een pijltje verstopt.
Ook hebben op de afbeelding de <input>'s even een rode outline gekregen. Hier gelijk onder worden ze breder en hoger gemaakt, de rode outline op de afbeelding maakt de grootte zichtbaar. Door ze wat groter te maken, zijn ze makkelijker te bedienen op een touchscreen.
<input>'s zijn heel moeilijk op te maken, omdat elke browser het op een andere manier doet. Als je bijvoorbeeld een grotere hoogte aan de <input> geeft, verhoogt Firefox het zichtbare vakje niet, maar alleen de <input> zelf. Google Chrome daarentegen verhoogt het hele vakje. Deze verschillen zijn hier echter niet van belang, omdat het hele aankruisvakje hier onzichtbaar wordt gemaakt.
Het verbergen zou het makkelijkste gaan door ze buiten het scherm te zetten. Als de <input> niet boven de bijbehorende <label> staat, blijk je echter op Windows 10 Mobile de <label> twee keer aan te moeten raken om een submenu te openen of te sluiten. Als je ze verbergt met display: none; of visibility: hidden; werkt de <input> helemaal uit meer. Daarom worden de <input>'s verborgen met opacity: 0;.
width: 4.5rem; height: 2rem;
Hoogte en breedte verhogen, zodat de <input> makkelijker is te raken op een touchscreen.
Als eenheid wordt de relatieve eenheid rem gebruikt, omdat bij een absolute eenheid zoals px de breedte en hoogte niet mee veranderen met de lettergrootte.
De rem werkt ongeveer hetzelfde als de bekendere eenheid em. Ook de rem is op de lettergrootte gebaseerd, maar altijd op de lettergrootte van <html>. Daardoor is de rem altijd overal even groot, ongeacht de lettergrootte van het element zelf. En ongeacht eventuele afwijkende lettergroottes in voorouders van het element. Als de bezoeker de lettergrootte verandert, wordt in feite de lettergrootte van <html> veranderd. Hierdoor verandert ook de grootte van de rem, maar die verandering is overal op de pagina precies hetzelfde.
Omdat de meeste maten in het menu als eenheid de rem gebruiken, blijft de lay-out van het menu ook intact, als de bezoeker de lettergrootte verandert.
position: absolute;
Om de <input> op de juiste plaats neer te kunnen zetten. Er wordt gepositioneerd ten opzichte van de eerste voorouder die een andere positie dan statisch heeft. Dat is hier de <li>, waar de <input> in zit. Deze heeft bij nav li een relatieve positie gekregen.
right: 0;
Rechts neerzetten.
z-index: 10;
Normaal genomen worden elementen die later in de html staan boven eerdere elementen gezet. In dit geval zou dat onder andere betekenen dat de <label> boven de <input> komt te staan. Waardoor het hierboven bij opacity:0; beschreven probleem op Windows 10 Mobile weer zou optreden. Door de z-index te verhogen, komt de <input> toch boven de <label> te staan.
Een z-index werkt alleen in bepaalde omstandigheden. Eén van die omstandigheden is een absolute positie. Die hebben de <input>'s hierboven gekregen, dus dat is geregeld.
nav label
Alle <label>'s binnen een <nav>. Alle <label>'s binnen het menu. Met behulp van aankruisvakjes (<input type="checkbox">) worden in browservensters smaller dan 760 px submenu's geopend en gesloten. Die aankruisvakjes worden bediend door een bijbehorende <label>. Dit zijn die <label>'s, voor elk aankruisvakje één.
De aankruisvakjes zelf worden hierboven bij nav input verborgen, omdat ze niet mooi zijn. In deze <label>'s staan gewone Franse aanhalingstekens ». Deze zijn prima om te bouwen tot pijltjes die aangeven, of het submenu is gesloten of geopend.
Op de afbeelding staan drie dubbele pijltjes (feitelijk dus mishandelde Franse aanhalingstekens, maar dan hadden ze in Frankrijk maar fatsoenlijke aanhalingstekens moeten gebruiken). De bovenste twee pijltjes staan boven een gesloten submenu, het onderste pijltje staat boven een geopend submenu.
Als het submenu wordt geopend, draait het pijltje 180 graden. Dat draaien duurt 'n halve seconde. Als het pijltje niet op precies de juiste plaats staat, ziet dat draaien er uit als een volwassene die uit een draaimolen komt: het pijltje wiebelt alle kanten uit tijdens het draaien.
Bovenstaande css zorgt ervoor dat het pijltje in alle browsers netjes op de plaats zelf draait. Wat precies wat doet bij welke browser, is niet echt duidelijk. Bovenstaande serie eigenschappen is grotendeels door uitproberen gevonden. Waarbij het pijltje ook nog horizontaal gecentreerd moest komen te staan.
Bovendien was het pijltje al negentig graden gedraaid, want het Franse aanhalingsteken is ». En als je met behulp van transform gaat draaien, heeft dat invloed op de hoogte en breedte. Bij een draaiing van negentig graden wordt de breedte de hoogte, en omgekeerd. En (uiteraard) werkt niet alle bovenstaande css in alle browsers exact hetzelfde, omdat browsers bij <label>'s gedeeltelijk eigen ideeën hebben over de plaatsing van tekst in een <label>.
Kortom: een leuke puzzel.
Op de afbeelding hiernaast is van bovenstaande css alleen height: 4.4rem; veranderd in height: 4.2rem;. De pijltjes staan prompt niet meer netjes uitgelijnd.
Bovenstaande eigenschappen werken, daar gaat het om.
Voor een aantal maten wordt de relatieve eenheid rem gebruikt, omdat bij een absolute eenheid zoals px de grootte van de eenheid niet mee verandert met de lettergrootte.
De rem werkt ongeveer hetzelfde als de bekendere eenheid em. Ook de rem is op de lettergrootte gebaseerd, maar altijd op de lettergrootte van <html>. Daardoor is de rem altijd overal even groot, ongeacht de lettergrootte van het element zelf. En ongeacht eventuele afwijkende lettergroottes in voorouders van het element. Als de bezoeker de lettergrootte verandert, wordt in feite de lettergrootte van <html> veranderd. Hierdoor verandert ook de grootte van de rem, maar die verandering is overal op de pagina precies hetzelfde.
Omdat de meeste maten in het menu als eenheid de rem gebruiken, blijft de lay-out van het menu ook intact, als de bezoeker de lettergrootte verandert.
Enkele maten moesten echter juist weer in px worden opgegeven, omdat ze niet mee mochten veranderen met de lettergrootte.
Een <label> is van zichzelf een inline-element. Daardoor zijn eigenschappen als breedte niet te gebruiken. Door de <label> absoluut te positioneren, verandert deze in een soort blok-element, waardoor dit soort eigenschappen wel is te gebruiken.
Er wordt gepositioneerd ten opzichte van de eerste voorouder die een andere positie dan statisch heeft. Dat is hier de <li> waar de <label> in zit. Deze heeft bij nav li een relatieve positie gekregen.
In elke <label> wordt met » een Frans aanhalingsteken » gestopt. Dit wordt vergroot, waarna het als pijltje bruikbaar is. De hele <label> wordt negentig graden met de klok mee gedraaid, waarna het pijltje naar beneden wijst.
Als het bij het pijltje horende submenu wordt geopend, wordt bij nav input:checked + label de <label> negentig graden tegen de klok in gedraaid, waarna het pijltje naar boven wijst.
transition: transform 0.5s;
Hierboven is met transform: rotate(90deg); de <label> negentig graden met de klok mee gedraaid, waardoor het pijltje in de <label> omlaag wijst. Als het bij de <label> horende submenu wordt geopend, wordt de <label> bij nav input:checked + label met transform: rotate(-90deg); negentig graden tegen de klok in gedraaid, waardoor het pijltje in de <label> nu omhoog wijst.
Als je bij :hover, :focus, :checked, en dergelijke een andere waarde bij rotate() opgeeft, zorgt transition ervoor dat rotate() niet in één keer naar die nieuwe waarde wordt veranderd bij hoveren en dergelijke, maar geleidelijk. Als de draaiing van 90deg (negentig graden met de klok mee) naar -90deg (negentig graden tegen de klok in) wordt veranderd, vindt die verandering geleidelijk aan plaats: geleidelijk draaien van omlaag naar omhoog wijzen.
Omdat ook het submenu zelf niet in één keer zichtbaar wordt, ziet dit er beter uit. Bovendien valt de draaiing zo beter op. Hierdoor is ook duidelijker, waar het pijltje zit dat je moet aanraken of -klikken om het submenu weer te sluiten.
Bij transition staat maar één waarde: 0.5s. Als er maar één waarde wordt opgegeven, bepaalt die over hoeveel tijd de verandering wordt uitgesmeerd. Dat is 'n halve seconde. Een eventuele tweede waarde, die hier ontbreekt, geeft een eventueel uitstel aan het begin van de verandering aan.
Het is ook mogelijk om het verloop van de verandering aan te geven met behulp van sleutelwoorden of getallen. Hiermee kun je bijvoorbeeld aangeven dat de verandering aan het eind snel of juist langzaam moet gaan. Bij een verandering die maar 'n halve seconde duurt, is het verschil nauwelijks te zien, dus dat wordt hier niet gebruikt.
transition 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 niet opgeeft, voor welke eigenschappen transition geldt, geldt het voor álle eigenschappen die bij :hover, :focus, :checked, en dergelijke worden veranderd. Als daar 'n eigenschap bij zit die op dit moment niet door transition kan worden vertraagd, verandert die gewoon in één keer. 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. Daarom moet je absoluut zeker weten dat ook in de toekomst geen problemen kunnen ontstaan. Bij twijfel: geef aan welke eigenschap(pen) transition mag aansturen.
In dit voorbeeld wordt alleen transform: rotate(); veranderd bij de <label>'s. Maar door het vele gebruik van :checked (en in mindere mate :hover en :focus) is de code wat onoverzichtelijk. Bij een latere wijziging zou onbedoeld iets anders ook onder transition kunnen komen te vallen. Daarom wordt voor de zekerheid transition toch beperkt tot transform.
main
Alle <main>'s. Dat is er maar één: de belangrijkste inhoud van de pagina staat erin. (Hier is dat alleen Latijnse flauwekultekst.)
display: block;
Oudere browsers kennen <main> niet. Een onbekend element is standaard een inline-element, waardoor eigenschappen als breedte niet kunnen worden gebruikt. Nu weten alle browsers dat dit een blok-element is.
width: 760px;
Breedte.
max-width: 100%;
Hierboven is een breedte van 760 px opgegeven. In browservensters die smaller dan 760 px zijn, moet hierdoor horizontaal worden gescrold. Deze maximumbreedte voorkomt dat.
Een breedte in procenten is normaal genomen ten opzichte van de breedte van de ouder van het element. Dat is hier <body>. Omdat <body> een blok-element is, wordt dit normaal genomen automatisch even breed als z'n ouder <html>. Omdat <html> het buitenste element is, wordt dit normaal genomen even breed als het venster van de browser. Hierdoor wordt uiteindelijk <main> nooit breder dan het venster, waardoor er in vensters smaller dan 760 px niet horizontaal gescrold hoeft te worden.
margin: 0 auto;
Omdat voor onder en links geen waarde is opgegeven, krijgen die automatisch dezelfde waarde als boven en rechts. Hier staat dus eigenlijk 0 auto 0 auto in de volgorde boven - rechts - onder - links.
Boven en onder geen marge. Links en rechts auto, wat hier hetzelfde betekent als evenveel. Hierdoor staat <main> altijd horizontaal gecentreerd binnen z'n ouder <body>. Omdat <body> een blok-element is, wordt dit normaal genomen automatisch even breed als z'n ouder <html>. Omdat <html> het buitenste element is, wordt dit normaal genomen even breed als het venster van de browser. Hierdoor staat uiteindelijk <main> horizontaal gecentreerd binnen het venster.
(Omdat <main> iets hoger 760 px breed is gemaakt, zie je dit centreren uiteraard alleen in browservensters die breder dan 760 px zijn.)
padding-top: 2.5rem;
Alle inhoud van <main> 2,5 rem naar beneden zetten.
Aan de bovenkant van het browservenster staat een menubalk, die bij #menubalk-voor-smal een hoogte van 2 rem heeft gekregen. Door hier 2,5 rem te nemen, krijg je 'n kleine open ruimte tussen de menubalk en de inhoud van <main>.
In dit geval is margin-top niet bruikbaar. Op diverse plaatsen bestaat de mogelijkheid om (de rest van) het menu te passeren en in één keer naar <main> te springen. Bij zo'n 'anker' (een link binnen de pagina) wordt een marge genegeerd. Als je een marge aan de bovenkant van <main> zou gebruiken, komt de inhoud van <main> daardoor te hoog te staan: het begin van de tekst verdwijnt onder de menubalk.
Een padding telt echter wel gewoon mee voor een anker: het begin van de padding wordt bovenaan het browservenster gezet. Omdat de padding de rest van de inhoud van <main> omlaag schuift, verdwijnt er nu niets meer onder de menubalk.
Als eenheid wordt de relatieve eenheid rem gebruikt, omdat bij een absolute eenheid zoals px de hoogte van de padding niet mee verandert met de lettergrootte.
De rem werkt ongeveer hetzelfde als de bekendere eenheid em. Ook de rem is op de lettergrootte gebaseerd, maar altijd op de lettergrootte van <html>. Daardoor is de rem altijd overal even groot, ongeacht de lettergrootte van het element zelf. En ongeacht eventuele afwijkende lettergroottes in voorouders van het element. Als de bezoeker de lettergrootte verandert, wordt in feite de lettergrootte van <html> veranderd. Hierdoor verandert ook de grootte van de rem, maar die verandering is overal op de pagina precies hetzelfde.
Omdat de meeste maten in het menu als eenheid de rem gebruiken, blijft de lay-out van het menu ook intact, als de bezoeker de lettergrootte verandert.
h1
Alle <h1>'s. Dat is er maar eentje. De titel van de pagina staat erin.
font-size: 1.5em;
Een <h1> heeft van zichzelf wel een heel enthousiast 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.
font-weight: normal;
Standaard is een <h> vet. Dat wordt hier veranderd in normaal.
main p
Alle <p>'s binnen <main>.
padding: 0 5px;
Omdat voor onder en links niets is opgegeven, krijgen die automatisch dezelfde waarde als boven en rechts. Hier staat dus eigenlijk 0 5px 0 5px in de volgorde boven - rechts - onder - links.
Links en rechts wat ruimte tussen de buitenkant van <main> en de tekst erin.
main a:focus
Alle links binnen <main>, maar alleen als deze focus hebben.
In <main> zitten twee links, die alleen in dit voorbeeld zijn aangebracht om de werking voor gebruikers van de Tab-toets duidelijker zichtbaar te maken. De focus wordt vaak wat onduidelijk aangeven, waardoor dat soms wat slecht opvalt. Deze focus valt zeker op.
background: black;
Zwarte achtergrond.
color: white;
Witte voorgrondkleur. Dit is ook de kleur van de tekst.
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.
#checkbox-voor-smal:checked: het element met id="checkbox-voor-smal", maar alleen als dit is aangevinkt. Met behulp van deze <input type="checkbox"> wordt het menu in browservensters smaller dan 760 px geopend.
~: 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: #checkbox-voor-smal voor de ~ en #menubalk-voor-smal na de ~ hebben beide als ouder <header>.
(Dat is ook de enige reden dat #menubalk-voor-smal wordt gebruikt. #label-voor-smal, waar het eigenlijk om gaat, heeft niet dezelfde ouder als #checkbox-voor-smal.)
#menubalk-voor-smal: het element met id="menubalk-voor-smal". Dit is de <div> die in browservensters smaller dan 760 px voor de menubalk bovenin het venster zorgt.
#label-voor-smal: het element met id="label-voor-smal". De <label> die bij input#checkbox-voor-smal hoort. In deze <label> zit de tekst 'Menu ' die bovenin de menubalk staat.
::after: met behulp van ::after wordt bij het element met id="label-voor-smal" een pseudo-element gemaakt, waarin tekst kan worden weergegeven.
De hele selector in normale mensentaal: als het aankruisvakje om het menu te openen in browservensters smaller dan 760 px is aangevinkt, doe dan iets met het door ::after bij #label-voor-smal gemaakte pseudo-element, dat weer in #menubalk-voor-smal zit.
content: "dicht";
De tekst die in het pseudo-element komt te staan. De totale zichtbare tekst wordt 'Menu dicht'.
Bij #label-voor-smal::after is eerder 'open' opgegeven als tekst. Als #checkbox-voor-smal is aangevinkt, is ook het menu geopend. Daarom wordt hier 'open' veranderd in 'dicht'.
#checkbox-voor-smal:checked ~ nav
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.
nav {width: 15.9rem; overflow: auto; position: fixed; top: 2rem;}
#checkbox-voor-smal:checked: het element met id="checkbox-voor-smal", maar alleen als dit is aangevinkt. Met behulp van deze <input type="checkbox"> wordt het menu in browservensters smaller dan 760 px geopend.
~: 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: #checkbox-voor-smal voor de ~ en nav na de ~ hebben beide als ouder <header>.
nav: in dit voorbeeld is maar één <nav> aanwezig. In deze <nav> zit het hele uitklapmenu.
De hele selector in normale mensentaal: als het aankruisvakje om het menu te openen in browservensters smaller dan 760 px is aangevinkt, doe dan iets met de <nav>, waar het hele uitklapmenu in zit.
bottom: 0;
Bij nav is de <nav> fixed gepositioneerd ten opzichte van het venster van de browser. De afstand tot de bovenkant van het venster is daar al aangegeven: 2 rem.
Normaal genomen wordt een blok-element zoals <nav> automatisch precies hoog genoeg, om de inhoud ervan weer te kunnen geven. In dit geval is dat niet zo.
In de <nav> zitten drie directe kinderen: div#uitleg-tab, div#uitleg-schermlezer en de buitenste <ul>. Alle drie deze elementen zijn absoluut of fixed gepositioneerd, en dan tellen ze niet mee voor de hoogte van het blok-element, waar ze in zitten. Hierdoor is de hoogte van <nav> altijd 0, ook als het menu is geopend.
De <nav> zelf is fixed gepositioneerd. Als het menu dan door het openen van submenu's te hoog wordt voor het browservenster, kan het menu worden gescrold, zonder dat de pagina mee scrolt.
Maar door deze combinatie valt alles van het menu dat niet binnen de <nav> past weg. En aangezien de <nav> een hoogte van exact 0 px heeft, is dat het volledige menu.
Door de <nav> aan de onderkant van het browservenster vast te zetten, vult de <nav> de ruimte tussen de eerder opgegeven 2 rem van de bovenkant tot de onderkant van het venster. En omdat bij navoverflow op auto is gezet, kan het menu worden gescrold, als dit te hoog wordt voor het venster.
(Dit kan pas hier gebeuren, want bij nav heeft de <nav> ook een breedte van15,9 rem gekregen. Hoewel je de <nav> niet ziet, dekt die het scherm over die breedte af, waardoor links onbereikbaar worden, tekst niet gekopieerd kan worden, enzovoort. Maar omdat de <nav> geen hoogte heeft, gebeurt dit niet.)
#checkbox-voor-smal:checked ~ nav > ul, nav input:checked ~ 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.
nav ul {height: 0; overflow: hidden; list-style-type: none; margin: 0; padding: 0;}
Dit zijn twee selectors, gescheiden door een komma.
Eerst de eerste selector #checkbox-voor-smal:checked ~ nav > ul:
#checkbox-voor-smal:checked: als het element met id="checkbox-voor-smal" is aangevinkt. In browservensters smaller dan 760 px opent input#checkbox-voor-smal het menu, als deze wordt aangevinkt.
~: 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: #checkbox-voor-smal voor de ~ en nav na de ~ hebben beide als ouder <header>.
nav: in dit voorbeeld is maar één <nav> aanwezig. In deze <nav> zit het hele uitklapmenu.
> ul: alle <ul>'s binnen bovenstaande <nav>. Het teken > geeft aan, dat het alleen <ul>'s mogen zijn die een direct kind van die <nav> zijn. Onderstaande <ul> is een direct kind van de <nav>:
<nav><ul></ul></nav>
De middelste <ul> hieronder is geen direct kind van de <nav>, omdat er een <ul> en een <li> tussen de <nav> en de binnenste <ul> zitten:
<nav><ul><li><ul></ul></li></ul></nav>
In dit voorbeeld is er maar één <ul> die een direct kind van de <nav> is. Binnen deze <ul> zit het hele menu. Alle andere <ul>'s zijn weer nakomelingen van deze <ul> en dus geen direct kind van de <nav>. Deze selector geldt dus alleen voor de buitenste <ul> van het menu.
De hele eerste selector in normale mensentaal: doe iets met de <ul> die een direct kind is van de <nav>, maar alleen als input#checkbox-voor-smal is aangevinkt.
De tweede selector nav input:checked ~ ul:
nav: in dit voorbeeld is maar één <nav> aanwezig. In deze <nav> zit het hele uitklapmenu.
input:checked: als binnen bovenstaande <nav> een <input> is aangevinkt.
~: 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. Voor de ~ staat een <input>. Elke <li> heeft één <input> als direct kind. Elke <li> heeft ook slechts één <ul> als direct kind. Deze <input> en <ul> hebben dus beide een <li> als gemeenschappelijke ouder. En omdat er in elke <li> binnen de <nav> slechts één <input> en één <ul> zit, komt deze combinatie slechts één keer binnen elke <li> voor.
Hierdoor stuurt elke <input> slechts één <ul> aan: de <ul> waarin het bij de <input> horende submenu zit.
ul: de hierboven genoemde <ul>, waarin het submenu zit.
De hele tweede selector in normale mensentaal: doe iets met de <ul> die op een aangevinkte <input> binnen de <nav> volgt.
(Als een schermlezer wordt gebruikt, worden #checkbox-voor-smal de <input>'s binnen <nav> door middel van JavaScript aangevinkt, dus ook dan werkt deze selector.)
opacity: 1;
Bij nav ul ul zijn alle geneste <ul>'s met opacity: 0; onzichtbaar gemaakt. Omdat dat wat lastig leest, worden ze hier weer zichtbaar gemaakt. Omdat eerder ook met transition een vertraging is opgegeven, gebeurt dat wat vriendelijker, dan wanneer het in één keer met een grote floep gebeurt.
(De buitenste <ul> is niet verborgen, maar dat maakt niets uit. Daar heeft deze regel gewoon geen invloed op.)
height: auto;
Alle <ul>'s zijn eerder bij nav ul met height: 0; verborgen.
Als #checkbox-voor-smal wordt aangevinkt, moet het menu in browservensters smaller dan 760 px worden getoond. Daarom wordt hier de hoogte met auto zo hoog gemaakt, dat het menu er precies in past.
De eerste selector zorgt ervoor dat dat bij de buitenste <ul> gebeurt, zodat de zes <li>'s in de buitenste <ul> zichtbaar kunnen worden.
De tweede selector regelt dit voor de submenu's op het tweede, derde en vierde niveau. Als in het menu een <input> is aangevinkt, moet het bijbehorende submenu worden getoond. Dat submenu zit in de <ul> die volgt op die <input>. Ook die <ul> wordt nu zo hoog gemaakt, als nodig is om het erin zittende submenu te tonen.
nav input:checked + label
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.
nav: in dit voorbeeld is maar één <nav> aanwezig. In deze <nav> zit het hele uitklapmenu.
input:checked: als binnen bovenstaande <nav> een <input> is aangevinkt.
+: het element achter de + moet in de html direct volgen op het element voor de +. In dit geval gaat het om de <label> die gelijk op de <input> volgt. Beide elementen moeten ook nog dezelfde ouder hebben. Dat is hier het geval, ze hebben beide als ouder dezelfde <li>.
Omdat in elke <li> slechts één <input> en <label> zit, stuurt deze selector slechts één <label> aan.
label: de hierboven genoemde <label>.
De hele selector in normale mensentaal: doe iets met de <label> die volgt op een aangevinkte <input> in de <nav> met het menu.
In de <label>'s zit een Frans aanhalingsteken », dat dienst doet als pijltje en aangeeft of het submenu wordt getoond of niet. Bij nav label is de <label> met transform: rotate(90deg); negentig graden met de klok mee gedraaid, waardoor het pijltje omlaag wijst.
Als het submenu onder het pijltje is geopend, wordt de <label> (en daarmee het pijltje) negentig graden tegen de klok in gedraaid, waardoor het nu naar boven wijst.
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.
#checkbox-voor-schermlezers-1:checked: als het element met id="checkbox-voor-schermlezers-1" is aangevinkt. Dit is de eerste <input type="checkbox"> voor schermlezers, waarmee gekozen kan worden het menu voor schermlezers te openen.
~: 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: #checkbox-voor-schermlezers-1 voor de ~ en checkbox-voor-schermlezers-2 na de ~ hebben beide als ouder <header>.
#checkbox-voor-schermlezers-2: het tweede aankruisvakje voor schermlezers, waarmee de keuze om het menu voor schermlezers te openen kan worden bevestigd.
De hele selector in normale mensentaal: doe iets met het tweede aankruisvakje voor schermlezers, als het eerste aankruisvakje voor schermlezers is aangevinkt.
display: block;
Bij #checkbox-voor-schermlezers-2 dit aankruisvakje met display: none; verborgen. Hier wordt het weer zichtbaar gemaakt, als het eerste aankruisvakje voor schermlezers is aangevinkt.
Deze twee aankruisvakjes zijn voor gebruikers van schermlezers bedoeld: als ze beide worden aangevinkt, wordt het menu toegankelijk voor schermlezers. Voor TalkBack op Android moet minimaal een plekje van 1 x 1 px van een <input> binnen het venster staan, niet verborgen onder een ander element, anders werkt de <input> niet. (Meer hierover is te vinden bij Probleem: in TalkBack op Android moet een link en dergelijke binnen het venster staan.)
Als je maar één aankruisvakje zou gebruiken, zou iemand per ongeluk dat aankruisvakje kunnen aanraken, waarna plotsklaps een volledig menu verschijnt. Je zou van minder een hartverzakking krijgen.
Daarom wordt het tweede aankruisvakje pas op het scherm gezet, als het eerste is aangevinkt. (En pas als dat tweede ook is aangevinkt, opent het menu voor schermlezers.) Bij uitproberen lukte het nu niet het menu per ongeluk te openen.
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.
Deze <label>, en daarmee de erin zittende tekst 'Bevestig je keuze', is bij #label-voor-schermlezers-2 met display: none; verborgen. Als dat niet zou gebeuren, zou de tekst altijd worden voorgelezen, ook als helemaal niet is gekozen om het eerste aankruisvakje voor schermlezers aan te vinken.
Pas als dat eerste aankruisvakje is aangevinkt, wordt deze tekst door schermlezers voorgelezen. Om dat te doen, hoeft alleen maar display: none; te worden veranderd. En omdat een <label> een inline-element is, gebeurt dat met display: inline;.
.xschermlezerx #menubalk-voor-smal
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.
Het element met id="menubalk-voor-smal" dat binnen een element met class="xschermlezerx' zit.
Met behulp van JavaScript wordt bij <body> een class 'xschermlezerx' toegevoegd of verwijderd. (Dit heeft verder geen enkele invloed op eventuele andere classes of een id bij <body>). Deze class wordt toegevoegd, zodra de twee aankruisvakjes voor schermlezers input#checkbox-voor-schermlezers-1 en input#checkbox-voor-schermlezers-2 zijn aangevinkt. En weer verwijderd, als één van deze twee wordt uitgevinkt.
Omdat 'xschermlezerx' alleen bij <body> voorkomt, als is gekozen om het menu voor schermlezers te tonen, kunnen in de css nu selectors met .xschermlezerx worden gebruikt om het menu voor schermlezers te regelen: de selector .xschermlezerx #menubalk-voor-smal werkt alleen, als is gekozen om het menu toegankelijk te maken voor schermlezers.
De classnaam 'xschermlezerx' is wat vreemd. Dat maakt de kans kleiner dat deze naam al in gebruik is. Als je deze naam wilt veranderen, moet je die ook in het script veranderen.
(Om het verwijderen en toevoegen van 'xschermlezerx' te zien moet je niet de gewone broncode, maar de Gegenereerde code bekijken.)
display: none;
De menubalk, waarin onder andere de <input> zit om het menu in browservensters smaller dan 760 px te openen, heeft geen enkel nut voor schermlezers en wordt daarom verborgen.
.xschermlezerx #skippy
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.
Het element met id="skippy" binnen een element met class="xschermlezerx". Dit is een skip-link: een link waarmee in één keer het hele menu gepasseerd kan worden.
De class 'xschermlezerx' wordt door JavaScript aan <body> toegevoegd, als is gekozen om het menu voor schermlezers te openen. Er is meer over te lezen bij .xschermlezerx #menubalk-voor-smal gelijk hierboven.
display: block;
De eerder met display: none; verborgen skip-link zichtbaar maken.
.xschermlezerx #uitleg-schermlezer
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.
Het element met id="uitleg-schermlezer" binnen een element met class="xschermlezerx". Dit is de <div>, waarin een korte uitleg voor schermlezers staat.
De class 'xschermlezerx' wordt door JavaScript aan <body> toegevoegd, als is gekozen om het menu voor schermlezers te openen. Er is meer over te lezen bij .xschermlezerx #menubalk-voor-smal iets hoger.
display: block;
De eerder met display: none; verborgen <div> zichtbaar maken.
.xschermlezerx nav
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.
nav {width: 15.9rem; overflow: auto; position: fixed; top: 2rem;}
De <nav>'s binnen een element met class="xschermlezerx". Er is hier maar één <nav>, waarin het hele uitklapmenu zit.
De class 'xschermlezerx' wordt door JavaScript aan <body> toegevoegd, als is gekozen om het menu voor schermlezers te openen. Er is meer over te lezen bij .xschermlezerx #menubalk-voor-smal.
position: static;
In browservensters smaller dan 760 px wordt de <nav> fixed gepositioneerd. Als het menu te hoog is voor het venster, kan het menu hierdoor worden gescrold, terwijl de pagina zelf niet meescrolt. Bovendien komt de <nav> met het menu over de <main> met de eigenlijke inhoud van de pagina te staan, omdat fixed gepositioneerde elementen door andere elementen worden genegeerd.
Door de <nav> statisch te positioneren, is dat allemaal niet meer het geval. Hierdoor komt de <main> onder de <nav> met het menu te staan. Wat op het scherm staat, heeft nu dezelfde volgorde, als wat de schermlezer voorleest.
top en bottom hebben ook geen effect meer, want die werken niet bij een statische positie.
margin-top: 7.3rem;
Marge aan de bovenkant. In browservensters smaller dan 760 px komen boven het menu skip-link #skippy en de korte uitleg voor schermlezers div#uitleg-schermlezer te staan. Daar is nu ruimte voor.
Als eenheid wordt de relatieve eenheid rem gebruikt, omdat bij een absolute eenheid zoals px de hoogte van de marge niet mee verandert met de lettergrootte.
De rem werkt ongeveer hetzelfde als de bekendere eenheid em. Ook de rem is op de lettergrootte gebaseerd, maar altijd op de lettergrootte van <html>. Daardoor is de rem altijd overal even groot, ongeacht de lettergrootte van het element zelf. En ongeacht eventuele afwijkende lettergroottes in voorouders van het element. Als de bezoeker de lettergrootte verandert, wordt in feite de lettergrootte van <html> veranderd. Hierdoor verandert ook de grootte van de rem, maar die verandering is overal op de pagina precies hetzelfde.
Omdat de meeste maten in het menu als eenheid de rem gebruiken, blijft de lay-out van het menu ook intact, als de bezoeker de lettergrootte verandert.
.xschermlezerx nav > 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.
nav ul {height: 0; overflow: hidden; list-style-type: none; margin: 0; padding: 0;}
De <ul> die een direct kind is van een <nav>, die binnen een element met class="xschermlezerx' zit. Er is maar één <ul> een direct kind van de <nav>: de buitenste <ul>, waar het hele uitklapmenu in zit. Hoe de selector nav > ul precies werkt, is te vinden bij nav > ul.
De class 'xschermlezerx' wordt doorJavaScript aan <body> toegevoegd, als is gekozen om het menu voor schermlezers te openen. Er is meer over te lezen bij .xschermlezerx #menubalk-voor-smal.
position: static;
Deze <ul> heeft eerder een absolute positie gekregen. Daardoor wordt hij door andere elementen genegeerd en komt de <ul> - en daarmee het hele erin zittende menu - over de <main> met de eigenlijke inhoud van de pagina te staan.
Door de <ul> een statische positie te geven, wordt dit voorkomen.
css voor vensters maximaal 759 px breed
@media screen and (max-width: 759px)
De css die hier tot nu toe staat, geldt voor alle browservensters.
De css die binnen deze media query staat, geldt alleen voor browservensters die maximaal 759 px breed zijn.
In sommige versies van Android browser op Android zit een bug, waardoor het menu niet wordt geopend. Op iOS is er een probleem met het scrollen van het menu. Omdat deze problemen alleen in deze smallere browservensters spelen, is er geen enkele reden bredere vensters op te zadelen met de css in deze media query.
@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 bepaalde fysieke 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.
(max-width: 759px): het browservenster mag maximaal 759 px breed zijn. Is het venster breder, dan wordt de css die binnen deze media-regel staat genegeerd.
Gelijk na deze regel komt een { te staan, en aan het einde van de css die binnen deze regel valt een bijbehorende afsluitende }. Die zijn in de regel hierboven weggevallen, maar het geheel ziet er zo uit:
@media screen and (max-width: 759px) {body {color: silver;}(...) rest van de css voor deze @media-regel (...)footer {color: gold;}}
Voor de eerste css binnen deze media-regel staat dus een extra {, en aan het eind staat een extra }.
@-webkit-keyframes bugfix-0 {from {padding-left: 0;} to {padding-left: 0;}}
Bij nav ul wordt een animatie toegevoegd om een bug in Android browser op Android op te lossen. Die animatie wordt hier opgegeven.
Bij #checkbox-voor-smal:checked ~ nav > ul, nav input:checked ~ ul wordt in de selector gebruik gemaakt van het teken ~ (de 'general sibling'-selector) om in browservensters smaller dan 760 px het menu en eventueel submenu's te tonen en te verbergen.
Android browser toont en sluit menu en submenu's niet, omdat hij problemen heeft met de ~. Door het toevoegen van een animatie werkt het toch zoals bedoeld.
Als je nou denkt dat hier niets wordt uitgevoerd, omdat de padding links van 0 px in een padding links van 0 px wordt veranderd, dan heb je helemaal gelijk. Toch neutraliseert deze flauwekul de bug. De padding links is 0 px, omdat bij de <ul>'s helemaal geen padding is opgegeven. In dat geval heeft de padding de standaardwaarde van 0. Zou de padding links bij de <ul>'s wel een waarde hebben, dan zou je hier die waarde gebruiken (zoals gelijk hieronder bij @-webkit-keyframes bugfix-10 gebeurt).
Bij nav ul, waar de animatie daadwerkelijk wordt gebruikt, staat bij -webkit-animation het sleutelwoord infinite: de animatie wordt eindeloos herhaald. Verder staat daar nog de waarde 1s: 1 seconde. Dat is de tijdsduur van de animatie. Door hier een tamelijk lange waarde voor te nemen, wordt de animatie niet al te vaak afgespeeld. (Voor zover je hier van 'afspelen' kan spreken, want er gebeurt gewoon helemaal niets.)
Omdat er in feite helemaal niets gebeurt bij deze animatie, wordt de pagina ook niet opnieuw opgemaakt door de browser. Kennelijk werkt dit als 'n soort waakhond: áls er iets is veranderd, wordt het weergegeven. Anders gebeurt er niets.
Normaal genomen is het een bijzonder slecht idee css te gebruiken voor slechts één weergave-machine: webkit. Maar in dit geval is dit terecht, want de bug zit alleen in de op webkit gebaseerde Android browser. Het heeft dus geen zin om Internet Explorer of Firefox ook met deze ongein te belasten.
@-webkit-keyframes bugfix-10 {from {padding-left: 10px;} to {padding-left: 10px;}}
Het verhaal hiervoor is precies hetzelfde als gelijk hierboven bij de andere @-webkit-keyframes. Alleen is de waarde van de padding-left hier niet 0, maar 10 px. Deze animatie wordt later gebruikt bij #label-voor-smal en komt overeen met de eerder bij #label-voor-smal opgegeven linker-padding van 10 px.
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.)
Het element met id="label-voor-smal". Via deze <label> wordt input#checkbox-voor-smal bediend. Met behulp van dit aankruisvakje wordt het menu in browservensters smaller dan 760 px geopend en gesloten.
-webkit-animation: bugfix-10 infinite 1s;
In Android browser op Android zit een bug, waardoor de tekst 'Menu dicht' niet verandert in 'Menu open'. Met behulp van deze regel wordt de tekst toch veranderd. Meer hierover bij @-webkit-keyframes bugfix-10.
nav
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.)
nav {width: 15.9rem; overflow: auto; position: fixed; top: 2rem;}
Als in browservensters smaller dan 760 px het menu te hoog wordt voor het venster, kan het menu worden gescrold, los van de rest van de pagina. Het menu wordt binnen de <nav> gescrold. Zodra je echter op iOS binnen een element wilt scrollen, gebeurt dat echt ongelooflijk schokkerig. Zo schokkerig dat scrollen feitelijk onmogelijk is. Deze regel lost dat op, nu scrolt de inhoudsopgave vloeiend.
Deze eigenschap kan grote problemen opleveren en moet grondig worden getest. Meer hierover is te vinden bij Bekende problemen (en oplossingen) onder het kopje Zoomen op iOS in vensters minder dan 760 px breed.
nav 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.
nav ul {height: 0; overflow: hidden; list-style-type: none; margin: 0; padding: 0;}
In Android browser op Android zit een bug, waardoor menu en submenu's niet worden getoond en gesloten. Met behulp van deze regel wordt kan dat toch. Meer hierover bij @-webkit-keyframes bugfix-0.
css voor vensters minimaal 760 px breed
@media screen and (min-width: 760px)
De opbouw van deze regel staat beschreven bij @media screen and (max-width: 759px). Er is één verschil: het browservenster moet hier minimaal 760 px (min-width: 760px) breed zijn. In vensters met een breedte van minimaal 760 px is de weergave van het menu volledig anders dan in smallere vensters, dat wordt binnen deze query geregeld.
Als je nou 'n mobieltje hebt met een resolutie van - ik roep maar wat - 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 dpi ('dots per inch'). Als dat mobieltje een resolutie van 192 dpi 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 dpi van het mobieltje is twee keer zo veel als de 96 dpi 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 dpi 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, nav h2, nav h3, nav h4
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.)
nav h2, nav h3, nav h4 {display: inline; font-size: 1rem; font-weight: normal; line-height: 0;}
<body> is 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.
nav h2, nav h3 en nav h4 zijn alle <h2>'s, <h3>'s en <h4>'s binnen <nav>, binnen het uitklapmenu.
font-size: 1.1rem;
In deze bredere browservensters is meer ruimte, daarom een iets grotere letter.
Als eenheid wordt de relatieve eenheid rem 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.
De rem is ongeveer hetzelfde als de bekendere eenheid em, maar is gebaseerd op de lettergrootte van <html>. Hierdoor is de rem, anders dan de em, overal op de pagina even groot. Ook als de bezoeker de lettergrootte heeft veranderd.
Omdat de meeste maten in het menu als eenheid de rem gebruiken, blijft de lay-out van het menu ook intact, als de bezoeker de lettergrootte verandert.
#checkbox-open-menu:not(:checked) ~ nav
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.
nav {width: 15.9rem; overflow: auto; position: fixed; top: 2rem;}
#checkbox-open-menu: het element met id="open-menu". Dit is een aankruisvakje dat met behulp van JavaScript wordt aangevinkt, als de bezoeker het menu voor gebruik met de Tab-toets of de schermlezer in één keer helemaal wil openen. (Voor gebruik met de Tab-toets kan dat eventueel ook zonder JavaScript.)
:not(): wat tussen de haakjes staat, mag niet het geval zijn bij het element.
:not(:checked): tussen de haakjes van :not() staat :checked, dus #checkbox-open-menu mag niet aangevinkt zijn.
#checkbox-open-menu:not(:checked): het hele eerste deel van de selector. input#checkbox-open-menu wordt alleen gebruikt om te voorkomen dat deze css wordt gebruikt voor het menu voor schermlezers en Tab-toets. Daar volgt later aparte css voor.
Veel van de css die niet voor schermlezers en Tab-toets is bedoeld, werkt echter ook gewoon als #checkbox-open-menu is aangevinkt en het menu voor Tab-toets en schermlezers is geopend. Terwijl dat niet de bedoeling is.
Dit maakt een puinhoop van het menu voor schermlezers en Tab-toets. Door de css die problemen voor dat menu oplevert alleen te laten werken, als #checkbox-open-menu niet is aangevinkt, wordt dat voorkomen.
(Er zijn ook andere manieren, zoals het weer uitschakelen van al deze css als het menu voor Tab-toets en schermlezers is geopend, maar dit is het simpelste.)
~: 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: #checkbox-open-menu voor de ~ en nav na de ~ hebben beide als ouder <header>.
nav: de hierboven genoemde <nav>.
De hele selector in normale mensentaal: doe iets met de <nav>, waar het hele uitklapmenu in zit, maar alleen als input#checkbox-open-menu niet is aangevinkt.
width: 760px;
Breedte.
top: 0;
In browservensters smaller dan 760 px staat boven de <nav> een menubalk. Hier ontbreekt, die, daarom mag <nav> hier helemaal bovenin het venster komen te staan.
bottom: auto;
In browservensters smaller dan 760 px stond de <nav> vast tussen 2 rem vanaf de bovenkant en de onderkant van het venster, zodat het menu gescrold kon worden, als dat te hoog was voor het venster.
Hier wordt het menu helemaal niet gescrold, dus wordt bottom teruggezet naar de standaardinstelling auto. Omdat <nav> een blok-element is, wordt het hierdoor automatisch precies hoog genoeg om de inhoud ervan weer te geven.
left: 50%;
Halverwege neerzetten. Omdat <nav> eerder fixed is gepositioneerd ten opzichte van het browservenster, betekent dat hier: halverwege het venster neerzetten.
overflow: visible;
Eerder is overflow: auto; gebruikt, zodat het menu in browservensters minder dan 760 px breed binnen de <nav> kan worden gescrold, als dat nodig is. In deze bredere vensters is scrollen niet nodig. Als een submenu wordt geopend, kan dat gewoon zonder scrollen worden getoond.
Iets hoger is de <nav> 760 px breed gemaakt en halverwege het browservenster neergezet. Als je de <nav> de helft van z'n breedte naar links terugzet, staat daardoor de helft van de <nav> links van het midden van het venster, en de andere helft staat rechts van het midden. Oftewel: de <nav> staat horizontaal gecentreerd binnen het venster.
Dat verplaatsen naar links kan het makkelijkst met 'n eigenschap, waarbij je 50% van de breedte van de <nav> kunt gebruiken. Als de breedte van de <nav> ooit veranderd, is 50% toch nog de helft van die breedte.
Normaal genomen doe je dat met transform: translateX(-50%);, zoals gelijk hieronder ook wordt gedaan. Met translateX() wordt alleen in horizontale richting verplaatst, en dat is hier voldoende.
Maar in oudere op webkit gebaseerde browsers zit een bug, waardoor het menu niet opent, als je alleen dit gebruikt. Door translate3d() te gebruiken wordt de hele <nav> door de browser op een eigen laag gezet, en dan werkt het wel. Deze bug speelt alleen nog in Android browser op Android, in alle andere browsers is hij gerepareerd.
(Android browser wordt al jaren niet meer geleverd en verdwijnt in rap tempo. Deze bug is echter zo ernstig, dat die niet genegeerd kan worden. Als je meer wilt weten over deze bug, kun je zoeken naar 'translate3d bug layer'.)
translate3d() is bedoeld voor driedimensionale verplaatsingen. Dat is natuurlijk nep, maar het ziet er driedimensionaal uit. Omdat er drie dimensies zijn, moeten ook drie waarden worden ingevuld. De eerste waarde is de horizontale verplaatsing: -50%. De tweede waarde is voor de verticale verplaatsing, de derde voor de verplaatsing in de diepte. Die waarden zijn beide 0, want er wordt alleen in horizontale richting verplaatst.
transform: translateX(-50%);
De <nav> halverwege z'n eigen breedte naar links verplaatsen, zodat de <nav> horizontaal gecentreerd is binnen het browservenster. Een uitgebreider verhaal staat gelijk hierboven bij transform: translate3d(-50%, 0, 0);.
#menubalk-voor-smal, nav input, nav label
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.)
Het element met id="menubalk-voor-smal", alle <input>'s binnen een <nav> en alle <label>'s binnen een <nav>.
#menubalk-voor-smal is de <div> die in browservensters smaller dan 760 px voor de menubalk boven in het venster zorgt. De <input>'s en <label>'s binnen de <nav> met het uitklapmenu worden in zulke vensters gebruikt om de submenu's te openen en te sluiten.
display: none;
Al deze elementen zijn in browservensters minimaal 760 breed overbodig, dus worden ze verborgen.
#checkbox-open-menu:not(:checked) ~ nav 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. (Alleen wat binnen deze media query geldig is, wordt binnen dit blokje herhaald.)
nav ul {height: 0; overflow: hidden; list-style-type: none; margin: 0; padding: 0;}
#checkbox-open-menu:not(:checked) ~ nav: de <nav> die ergens in de html op #checkbox-open-menu volgt, maar alleen als #checkbox-open-menu niet is aangevinkt.
Als #checkbox-open-menu is aangevinkt, is het menu voor gebruikers van Tab-toets of schermlezers geopend. Deze regel veroorzaakt in dat menu problemen. Daarom mag deze selector alleen werken, als #checkbox-open-menu niet is aangevinkt. Een uitgebreidere beschrijving staat bij #checkbox-open-menu:not(:checked) ~ nav.
ul: de <ul>'s binnen de hierboven beschreven <nav>.
De hele selector in normale mensentaal: doe iets met de <ul>'s in de <nav>, waar het hele uitklapmenu in zit, maar alleen als input#checkbox-open-menu niet is aangevinkt.
height: auto;
Bij nav ul is een hoogte van 0 px aan de <ul>'s gegeven, zodat ze kunnen worden verborgen. Hier is dat niet meer nodig. De buitenste <ul>, waar de zes bovenste knoppen in zitten, is in browservensters minimaal 760 px breed altijd zichtbaar. De andere <ul>'s met de submenu's worden in deze vensters verborgen door ze aan de bovenkant buiten het scherm te zetten.
height: auto; is de standaardinstelling. Omdat een <ul> een blok-element is, wordt het nu automatisch precies hoog genoeg om de inhoud ervan weer te geven.
overflow: visible;
Bij nav ul is overflow: hidden; gebruikt, zodat de inhoud van de <ul>'s in browservensters minder dan 760 px breed verborgen kan worden. In deze bredere vensters is dat niet nodig.
position: static;
Bij nav > ul is de buitenste <ul> absoluut gepositioneerd. Hier is dat niet meer nodig en het levert bovendien problemen op.
De voorouder van alle <ul>'s is de <nav>, waarin het hele menu zit. Een <nav> is een blok-element en wordt daarom normaal genomen automatisch even breed als z'n ouder. Dat is echter niet zo, als het blok-element fixed is gepositioneerd, zoals hier het geval is. In dat geval wordt het element niet breder dan nodig is om de inhoud ervan weer te geven.
Alle nakomelingen van de <nav> zijn echter absoluut gepositioneerd, en absoluut gepositioneerde elementen hebben geen invloed op de breedte van hun voorouders. Hierdoor is de breedte van de <nav> 0 px: van zichzelf heeft de <nav> geen breedte, en de nakomelingen zorgen ook niet voor breedte. (Een <nav> is weliswaar een blok-element en zou daarom normaal genomen even breed worden als z'n ouder, maar bij nav is de <nav> fixed gepositioneerd, en dan is dat niet zo.)
Hierdoor is ook de <ul> met de buitenste zes knoppen van het menu onzichtbaar, want ook die kan niet worden getoond in een <nav> met een breedte van 0 px. Door de buitenste <ul> statisch te positioneren, heeft deze wel invloed op de breedte van de <nav>. De <nav> wordt nu even breed als de buitenste <ul<, waardoor deze (en alles wat erin zit) zichtbaar wordt.
#checkbox-open-menu:not(:checked) ~ nav li
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.)
nav li {background: #e4e4e4; color: black; box-sizing: border-box; line-height: 2.5rem; text-indent: 0.3rem; border: black solid 1px; border-top: none; position: relative; -ms-user-select: none;}
#checkbox-open-menu:not(:checked) ~ nav: de <nav> die ergens in de html op #checkbox-open-menu volgt, maar alleen als #checkbox-open-menu niet is aangevinkt.
Als #checkbox-open-menu is aangevinkt, is het menu voor gebruikers van Tab-toets of schermlezers geopend. Deze regel veroorzaakt in dat menu problemen. Daarom mag deze selector alleen werken, als #checkbox-open-menu niet is aangevinkt. Een uitgebreidere beschrijving staat bij #checkbox-open-menu:not(:checked) ~ nav.
li: de <li>'s binnen de hierboven besproken <nav>.
width: 9.5rem;
Breedte.
Als eenheid wordt de relatieve eenheid rem gebruikt, omdat bij een absolute eenheid zoals px de breedte niet mee verandert met de lettergrootte.
De rem werkt ongeveer hetzelfde als de bekendere eenheid em. Ook de rem is op de lettergrootte gebaseerd, maar altijd op de lettergrootte van <html>. Daardoor is de rem altijd overal even groot, ongeacht de lettergrootte van het element zelf. En ongeacht eventuele afwijkende lettergroottes in voorouders van het element. Als de bezoeker de lettergrootte verandert, wordt in feite de lettergrootte van <html> veranderd. Hierdoor verandert ook de grootte van de rem, maar die verandering is overal op de pagina precies hetzelfde.
Omdat de meeste maten in het menu als eenheid de rem gebruiken, blijft de lay-out van het menu ook intact, als de bezoeker de lettergrootte verandert.
max-height: 6.7vh;
In lagere browservensters kunnen de laagste submenu's beneden het venster komen te staan. Er kan niet worden gescrold, omdat bij nav de <nav> met het menu fixed is gepositioneerd. Daardoor zijn onder het venster staande links onbereikbaar.
(Op sommige touchscreens kun je het menu wel scrollen, maar als je dat doet, is er een grote kans dat het menu dicht floept, of dat 'n verkeerde link wordt gevolgd. Praktisch gezien kun je het menu ook daar niet scrollen.)
Daarom moet de hoogte van de <li>'s op een of andere manier worden beperkt. Dat gebeurt hier. Althans: eigenlijk gebeurt dat hieronder bij calc(), maar oudere browsers kennen dat niet. Voor die browsers wordt hier een maximumhoogte gegeven.
1 vh is één procent van de hoogte van het browservenster. Een <li> kan dus nooit hoger worden dan 6,7% van het venster.
Als het laagste submenu is geopend, staan er veertien items onder elkaar. 14 x 6,7 = 93,8. De maximale hoogte van het menu is dus 93,8% van de hoogte van het browservenster. Geen 100%, want sommige browsers gebruiken ook ruimte voor een menubalk en dergelijke.
Met een maximale hoogte 6,7 vh blijkt elke browser alle menu-items altijd te kunnen tonen.
line-height: calc(1rem + 3vh);
Hier gelijk boven is een maximumhoogte aan de <li>'s gegeven, zodat ze altijd binnen het browservenster passen. Dat werkt op zich goed, alleen staat de tekst in de <li>'s nu niet meer altijd netjes verticaal gecentreerd binnen de <li>.
Door niet de hoogte, maar de regelhoogte aan te passen, blijft de tekst altijd netjes verticaal gecentreerd staan. Omdat geen hoogte is opgegeven, is de regelhoogte ook de hoogte van de <li>.
Browsers die calc() ondersteunen, gebruiken deze regel. Omdat de regelhoogte altijd beneden de hier gelijk boven opgegeven maximumhoogte van 6,7 vh voor de <li> blijft, wordt die maximumhoogte nooit bereikt. Voor browsers die calc() ondersteunen, is de hierboven staande regel max-height: 6.7rem; dus eigenlijk overbodig.
Browsers die calc() niet ondersteunen, negeren de regel hier. Bij die browsers voorkomt de hierboven opgegeven maximumhoogte voor de <li> dat delen van het menu onder het browservenster kunnen komen te staan. Het werkt, alleen is het wat minder netjes.
De regel hier is een simpele formule, waarmee de regelhoogte wordt aangepast aan de hoogte van het browservenster. Dit voorkomt heel hoge <li>'s in hoge vensters, en te lage <li>'s in lage vensters.
Omdat calc() bij berekeningen met lengtematen alles eerst omzet naar de eenheid px, kun je berekeningen met verschillende eenheden maken, zoals hier met rem en vh.
De lettergrootte, waarmee deze calc() rekent, is altijd 1 rem. De rem is ongeveer hetzelfde als de bekendere eenheid em , maar is gebaseerd op de lettergrootte van <html>. Als de bezoeker de lettergrootte wijzigt, verandert de grootte van de rem. Omdat die verandering overal op de pagina even groot is, is ook de rem overal op de pagina altijd even groot. Een door de bezoeker gewijzigde lettergrootte wordt dus meegenomen in de berekening hier met calc().
Omdat op veel plaatsen in het menu de eenheid rem wordt gebruikt, blijft de lay-out van het menu ook bij een door de bezoeker gewijzigde lettergrootte intact.
Als de lettergrootte niet is gewijzigd door de bezoeker, is 1 rem gelijk aan 18 px. De regelhoogte wordt dus ook 18 px. Dat is wat weinig, want een letter van 18 px heeft standaard een regelhoogte van ongeveer 21,5 px (1,2 em).
Bij de regelhoogte van 18 px, waar de 1rem voor zorgt, wordt 3vh opgeteld. 1 vh is één procent van de hoogte van het browservenster. 3 vh is dus drie procent van die hoogte. Hoe hoger het venster is, hoe groter de regelhoogte wordt.
De tekst in de <li>'s is altijd minstens 1 rem: minstens de (eventueel door de bezoeker veranderde) standaardlettergrootte. Plus 3 vh, waardoor de regelhoogte ook altijd groot genoeg is om overlappingen te voorkomen, maar niet te hoog wordt in hoge browservensters.
text-align: center;
Tekst horizontaal centreren.
text-indent: 0;
Eerste regel hoeft niet meer in te springen, dus de eerder opgegeven text-indent weghalen.
#checkbox-open-menu:not(:checked) ~ nav > ul > li
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.)
nav li {background: #e4e4e4; color: black; box-sizing: border-box; line-height: 2.5rem; text-indent: 0.3rem; border: black solid 1px; border-top: none; position: relative; -ms-user-select: none;}
#checkbox-open-menu:not(:checked) ~ nav: de <nav> die ergens in de html op #checkbox-open-menu volgt, maar alleen als #checkbox-open-menu niet is aangevinkt.
Als #checkbox-open-menu is aangevinkt, is het menu voor gebruikers van Tab-toets of schermlezers geopend. Deze regel veroorzaakt in dat menu problemen. Daarom mag deze selector alleen werken, als #checkbox-open-menu niet is aangevinkt. Een uitgebreidere beschrijving staat bij #checkbox-open-menu:not(:checked) ~ nav.
> ul > li: alle <li>'s binnen een <ul>, die weer binnen de hierboven beschreven <nav> staat. Het teken > geeft aan, dat het alleen <li>'s mogen zijn die een direct kind zijn van een <ul>, die zelf weer een direct kind van een <nav> is.. Onderstaande <li> voldoet aan die voorwaarde: de <li> is een direct kind van een <ul>, die weer een direct kind van nav#uitklapmenu is:
<nav id="uitklapmenu"><ul><li></li></ul></nav>
De binnenste <li> hieronder voldoet niet aan deze eis, omdat de binnenste <ul> geen direct kind van nav#uitklapmenu is:
De hele selector in normale mensentaal: doe iets met de <li>'s die een direct kind van de <ul> zijn, die weer een direct van de <nav> is, maar alleen als #checkbox-open-menu niet is aangevinkt.
In dit voorbeeld is er maar één <ul> die een direct kind van nav#uitklapmenu is. Alleen de <li>'s uit die ene <ul> vallen onder deze selector. Het zijn de zes <li>'s met de bovenste knoppen: 'Kop 1' tot en met 'Kop 6'. In browservensters minimaal 760 px breed zijn deze <li>'s altijd zichtbaar.
width: 16.66%;
Breedte.
Een breedte in procenten is normaal genomen ten opzichte van de ouder van het element. Dat is hier de buitenste <ul> van het menu, waarin de zes knoppen zitten die altijd zichtbaar zijn.
Er zitten zes <li>'s in deze <ul>. Als je elke <li> 16,66% breed maakt, passen er precies zes naast elkaar. (6 x 16,66 = 99,96%. Degene die die 0,04% afwijking kan zien, moet zich onmiddellijk aanmelden voor Holland's Got Talent vanwege superogen.)
max-height: none;
Bij nav li hebben alle <li>'s in het menu een maximale hoogte van 6,7 vh gekregen. Deze <li>'s met de zes altijd zichtbare knoppen hebben geen maximumhoogte, maar krijgen hieronder bij line-height een hoogte van 2 rem.
float: left;
Een <li> is een blok-element. Daardoor komt elke <li> op een nieuwe regel te staan. Door ze naar links te floaten, komen ze naast elkaar te staan. Pas als ze niet meer naast elkaar passen, komen ze op een volgende regel te staan, net als gewone tekst.
line-height: 2rem;
Regelhoogte. Omdat geen hoogte is opgegeven voor de <li>, is dit tevens de hoogte van de <li>. Tekst wordt automatisch halverwege de regelhoogte gezet, dus de tekst staat gelijk verticaal gecentreerd.
Als eenheid wordt de relatieve eenheid rem 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.
De rem is ongeveer hetzelfde als de bekendere eenheid em, maar is gebaseerd op de lettergrootte van <html>. Hierdoor is de rem, anders dan de em, overal op de pagina even groot. Ook als de bezoeker de lettergrootte heeft veranderd.
Omdat de meeste maten in het menu als eenheid de rem gebruiken, blijft de lay-out van het menu ook intact, als de bezoeker de lettergrootte verandert.
#checkbox-open-menu:not(:checked) ~ nav > ul > li:first-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. (Alleen wat binnen deze media query geldig is, wordt binnen dit blokje herhaald.)
nav li {background: #e4e4e4; color: black; box-sizing: border-box; line-height: 2.5rem; text-indent: 0.3rem; border: black solid 1px; border-top: none; position: relative; -ms-user-select: none;}
Deze selector is precies hetzelfde als die gelijk hierboven bij #checkbox-open-menu:not(:checked) ~ nav > ul > li, alleen staat hier nog :first-of-type achter de <li>.
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 selector :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 selector: :first-of-type. Het gaat hier dus om het eerste element van de soort <li>: de <li> met de meest linkse knop met de tekst 'Kop 1'.
Omdat voor li:first-of-type onder andere nav > ul > li staat, geldt dit alleen voor de eerste <li> in die buitenste <ul>. Zonder deze beperking zou het voor álle eerste <li>'s in álle <ul>'s gelden.
border-left: black solid 1px;
Bij nav > ul > li is de linkerborder bij deze <li>'s verwijderd. Hier krijgt de meest linkse knop die weer terug.
#checkbox-open-menu:not(:checked) ~ nav ul 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. (Alleen wat binnen deze media query geldig is, wordt binnen dit blokje herhaald.)
nav ul {height: 0; overflow: hidden; list-style-type: none; margin: 0; padding: 0;}
nav ul ul {opacity: 0; box-shadow: black -5px -4px 10px; width: 14rem; margin: 1px 0 0 1rem; transition: opacity 1s;}
nav ul ul ul {width: 12.95rem; width: calc(13rem - 1px);}
#checkbox-open-menu:not(:checked) ~ nav: de <nav> die ergens in de html op #checkbox-open-menu volgt, maar alleen als #checkbox-open-menu niet is aangevinkt.
Als #checkbox-open-menu is aangevinkt, is het menu voor gebruikers van Tab-toets of schermlezers geopend. Deze regel veroorzaakt in dat menu problemen. Daarom mag deze selector alleen werken, als #checkbox-open-menu niet is aangevinkt. Een uitgebreidere beschrijving staat bij #checkbox-open-menu:not(:checked) ~ nav.
ul ul: de <ul>'s die binnen een andere <ul> zitten, die weer binnen de hierboven beschreven <nav> zit. Dit zijn de <ul>'s, waarin de submenu's van het tweede, derde en vierde niveau zitten. (De <ul>'s voor het derde en vierde niveau worden later op diverse punten aangepast.)
De hele selector in normale mensentaal: doe iets met de <ul>'s die in een andere <ul> zitten, die weer binnen de <nav> zit, maar alleen als #checkbox-open-menu niet is aangevinkt.
opacity: 1;
Bij nav ul ul zijn deze <ul>'s onzichtbaar gemaakt. In browservensters minimaal 760 px breed is dat niet nodig.
box-shadow: none;
Ook de eerder opgegeven box-shadow is niet meer nodig.
width: 9.5rem;
Breedte aanpassen.
Als eenheid wordt de relatieve eenheid rem gebruikt, omdat bij een absolute eenheid zoals px de breedte niet mee verandert met de lettergrootte.
De rem werkt ongeveer hetzelfde als de bekendere eenheid em. Ook de rem is op de lettergrootte gebaseerd, maar altijd op de lettergrootte van <html>. Daardoor is de rem altijd overal even groot, ongeacht de lettergrootte van het element zelf. En ongeacht eventuele afwijkende lettergroottes in voorouders van het element. Als de bezoeker de lettergrootte verandert, wordt in feite de lettergrootte van <html> veranderd. Hierdoor verandert ook de grootte van de rem, maar die verandering is overal op de pagina precies hetzelfde.
Omdat de meeste maten in het menu als eenheid de rem gebruiken, blijft de lay-out van het menu ook intact, als de bezoeker de lettergrootte verandert.
margin-left: 0;
De eerder opgegeven marge links verwijderen.
position: absolute;
Om de <ul>'s met het daarin zittende submenu op de juiste plaats neer te kunnen zetten.
Er wordt gepositioneerd ten opzichte van de eerste voorouder die zelf een andere positie dan statisch heeft. Dat zijn hier de <li>'s, waar de <ul> in zit. Deze hebben bij nav li een relatieve positie gekregen.
top: -2000px;
De <ul>'s, en daarmee de erin zittende submenu's, onzichtbaar aan de bovenkant buiten het scherm parkeren. Pas als ze zichtbaar moeten worden, worden ze binnen het scherm gezet.
Het is gebruikelijker om elementen met iets als left: -20000px; links buiten het scherm te parkeren en pas zichtbaar te maken, als dat nodig is. Dat kan hier niet.
Om problemen bij het gebruik van een muis te voorkomen, wordt transition gebruikt. Hierdoor wordt het sluiten van het menu iets vertraagd, waardoor het niet onmiddellijk sluit als de muis even buiten het menu komt.
Bij gebruik van top om de submenu's te tonen, is er altijd een waarde met getallen, bijvoorbeeld top: 2rem;. Maar bij left moet de waarde af en toe auto zijn. transition kan niet worden gebruikt bij de waarde auto, dus is left hier onbruikbaar.
Het komt min of meer toevallig zo uit dat bij het tonen bij top altijd een waarde met getallen wordt gebruikt en nooit auto. Daarom kan transition wel met top overweg.
left: -1px;
Hiermee komen de submenu's van het tweede niveau netjes onder de bijbehorende kop te staan. Voor diepere submenu's wordt dit later aangepast.
transition: top 0s 0.15s;
transition wordt meestal gebruikt om een verandering geleidelijk aan te laten plaatsvinden. Het kan echter ook worden gebruikt om een verandering pas na 'n tijdje te laten plaatsvinden. (Of voor een combinatie van beiden.)
Als je met de muis over een kop in het menu hovert, opent het bijbehorende submenu. Als je weer over 'n kop in dat submenu hovert, opent weer 'n ander submenu, en eventueel nog een. Het probleem met een muis: als je ook maar 'n milliseconde buiten het menu komt, sluit dit onmiddellijk. Op een touchscreen speelt dit probleem niet, het is typisch iets voor de muis.
Om dit frustrerende dichtklappen-net-als-je-$)⁽*$)⁽*-vier-niveaus-diep-bent-aangekomen te voorkomen, wordt een minieme vertraging aangebracht bij het sluiten. Niet echt storend, maar het geeft voldoende tijd om met de muis weer boven het menu te komen. (Als je deze vertraging wilt zien, kun je snel met de muis van links naar rechts over de zes knoppen in de menubalk bewegen. Gedurende 0,15 seconde zijn er twee submenu's gelijktijdig open.)
Hierboven is met top: -2000px; een verplaatsing naar boven opgegeven, waardoor de <ul>'s met de submenu's aan de bovenkant buiten het scherm komen te staan. Als je nu bij :hover, :focus, :checked, en dergelijke een andere waarde bij top opgeeft, kan transition voor een vertraging zorgen bij die verandering. De nieuwe waarde voor top gaat niet gelijk in, maar pas na een (korte) tijd.
De eerste tijdswaarde achter transition is 0s. Dit is de tijd die de verandering duurt. Als je hier bij voorbeeld 10s invult, duurt de verandering van bijvoorbeeld top: 10px; naar top: 2px; tien seconden: het element verschuift. Deze mogelijkheid van transition wordt hier niet gebruikt, maar je moet wel een waarde opgeven, anders kan de tweede mogelijkheid niet worden gebruikt. Vandaar de 0s.
De tweede tijdswaarde is 0.15s. Dit is de waarde, waar het hier om gaat. Het submenu is met iets als top: -1px; op het scherm gezet, waardoor het zichtbaar wordt. De verandering terug naar top: -2000px; (en weer onzichtbaar) gebeurt pas na een vertraging van 0,15 seconde. Dit is zo kort dat het niet storend is, maar je hebt wel de tijd om de muis terug te brengen boven het menu, als deze er even af is geschoten.
Omdat bij #checkbox-open-menu:not(:checked) ~ nav li:hover > ultransition: top 0s 0s; wordt opgegeven, gebeurt het openen van het submenu wel zonder vertraging. Bij het openen zou een vertraging, hoe minimaal ook, wel bijzonder storend zijn.
transition 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 niet opgeeft, voor welke eigenschappen transition geldt, geldt het voor álle eigenschappen die bij :hover, :focus, :checked, en dergelijke worden veranderd. Als daar 'n eigenschap bij zit die op dit moment niet door transition kan worden vertraagd, verandert die gewoon in één keer. 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. Daarom moet je absoluut zeker weten dat ook in de toekomst geen problemen kunnen ontstaan. Bij twijfel: geef aan welke eigenschap(pen) transition mag aansturen.
In dit voorbeeld wordt :checked nogal uitbundig gebruikt om hele reeksen eigenschappen te veranderen. In mindere mate worden ook nog :focus en :hover gebruikt. Er is dus een kans dat transition onbedoeld voor meer eigenschappen dan alleen top blijkt te werken. Ook kan de code in de toekomst worden gewijzigd, waardoor dat alsnog zou kunnen gebeuren.
Mogelijk is het hier overbodig, maar om alle risico's uit te sluiten wordt top hier genoemd. Zodra één of meer eigenschappen met name worden genoemd bij transition, werkt transition alleen nog voor die eigenschappen.
#checkbox-open-menu:not(:checked) ~ nav .naar-links
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.)
nav ul {height: 0; overflow: hidden; list-style-type: none; margin: 0; padding: 0;}
nav ul ul {opacity: 0; box-shadow: black -5px -4px 10px; width: 14rem; margin: 1px 0 0 1rem; transition: opacity 1s;}
nav ul ul ul {width: 12.95rem; width: calc(13rem - 1px);}
#checkbox-open-menu:not(:checked) ~ nav ul ul {opacity: 1; box-shadow: none; width: 9.5rem; margin-left: 0; position: absolute; top: -2000px; left: -1px; transition: top 0s 0.15s;}
#checkbox-open-menu:not(:checked) ~ nav: de <nav> die ergens in de html op #checkbox-open-menu volgt, maar alleen als #checkbox-open-menu niet is aangevinkt.
Als #checkbox-open-menu is aangevinkt, is het menu voor gebruikers van Tab-toets of schermlezers geopend. Deze regel veroorzaakt in dat menu problemen. Daarom mag deze selector alleen werken, als #checkbox-open-menu niet is aangevinkt. Een uitgebreidere beschrijving staat bij #checkbox-open-menu:not(:checked) ~ nav.
.naar-links: de elementen met class="naar-links" binnen de hierboven besproken <nav>.
De hele selector in normale mensentaal: doe iets met de elementen met class="naar-links" in de <nav>, waar het hele uitklapmenu in zit, maar alleen als input#checkbox-open-menu niet is aangevinkt.
Dit zijn de submenu's die naar links geopend moeten worden. Een deel van de submenu's onder knop vijf en alle submenu's onder knop zes worden niet naar rechts, maar naar links geopend, omdat ze anders buiten het browservenster kunnen komen te staan.
margin-left: 0;
Marge aan de linkerkant weghalen.
Dit is ook al gebeurd bij #checkbox-open-menu:not(:checked) ~ nav ul ul iets hierboven. Alle elementen met class="naar-links" zijn <ul>'s, die ook onder die selector vallen. Dus eigenlijk is deze marge daar al verwijderd.
Maar verderop bij #checkbox-open-menu:not(:checked) ~ nav ul ul ul wordt met margin: 0 0 0 -2px; links weer een negatieve marge van 2px opgegeven, en ook onder die selector valt een aantal <ul>'s met class="naar-links". Omdat die selector verderop lager in de css staat (en een ul meer heeft), overruled die selector de selector hierboven.
Daarom wordt hier met #checkbox-open-menu:not(:checked) ~ nav .naar-links speciaal voor de <ul>'s met class="naar-links" nogmaals opgegeven, dat links geen marge aanwezig mag zijn.
Ook de selector hier staat voor de latere #checkbox-open-menu:not(:checked) ~ nav ul ul ul, en daardoor zou ook de selector hier worden overruled. Dat is echter niet zo. Als je de selector hier en die verderop onder elkaar zet, krijg je het volgende:
#checkbox-open-menu:not(:checked) ~ nav .naar-links#checkbox-open-menu:not(:checked) ~ nav ul ul ul
Het grootste deel van beide selectors is hetzelfde, maar de selector hier heeft een class .naar-links, en de selector verderop heeft drie keer ul, drie elementen. En een selector met een class heeft altijd meer gewicht (meer 'specificiteit') dan een element, hoeveel elementen er ook aanwezig zijn. Daarom 'wint' de selector hier toch van die verderop, ook al staat die lager in de css.
left: -9.5rem;
De <ul>'s zijn 9,5 rem breed. Door de <ul> 9,5 rem naar links te zetten, komt de rechterkant van de <ul> op de juiste plaats te staan.
Als eenheid wordt de relatieve eenheid rem gebruikt, omdat bij een absolute eenheid zoals px de afstand vanaf links niet mee verandert met de lettergrootte.
De rem werkt ongeveer hetzelfde als de bekendere eenheid em. Ook de rem is op de lettergrootte gebaseerd, maar altijd op de lettergrootte van <html>. Daardoor is de rem altijd overal even groot, ongeacht de lettergrootte van het element zelf. En ongeacht eventuele afwijkende lettergroottes in voorouders van het element. Als de bezoeker de lettergrootte verandert, wordt in feite de lettergrootte van <html> veranderd. Hierdoor verandert ook de grootte van de rem, maar die verandering is overal op de pagina precies hetzelfde.
Omdat de meeste maten in het menu als eenheid de rem gebruiken, blijft de lay-out van het menu ook intact, als de bezoeker de lettergrootte verandert.
Als twee of meer elementen ten opzichte van elkaar neergezet moeten worden, een bepaalde grootte ten opzichte van elkaar moeten hebben, en dergelijke, is de eenheid rem vrijwel altijd beter dan de eenheid em. (Even los van nieuwere mogelijkheden als flexbox en grid, maar die worden hier niet gebruikt.)
#checkbox-open-menu:not(:checked) ~ nav #rechterlijst
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.)
nav ul {height: 0; overflow: hidden; list-style-type: none; margin: 0; padding: 0;}
nav ul ul {opacity: 0; box-shadow: black -5px -4px 10px; width: 14rem; margin: 1px 0 0 1rem; transition: opacity 1s;}
#checkbox-open-menu:not(:checked) ~ nav ul ul {opacity: 1; box-shadow: none; width: 9.5rem; margin-left: 0; position: absolute; top: -2000px; left: -1px; transition: top 0s 0.15s;}
#checkbox-open-menu:not(:checked) ~ nav: de <nav> die ergens in de html op #checkbox-open-menu volgt, maar alleen als #checkbox-open-menu niet is aangevinkt.
Als #checkbox-open-menu is aangevinkt, is het menu voor gebruikers van Tab-toets of schermlezers geopend. Deze regel veroorzaakt in dat menu problemen. Daarom mag deze selector alleen werken, als #checkbox-open-menu niet is aangevinkt. Een uitgebreidere beschrijving staat bij #checkbox-open-menu:not(:checked) ~ nav.
#rechterlijst: het element met id="rechterlijst" binnen de hierboven besproken <nav>.
De hele selector in normale mensentaal: doe iets met het element met id="rechterlijst" in de <nav>, waar het hele uitklapmenu in zit, maar alleen als input#checkbox-open-menu niet is aangevinkt.
Dit is de <ul> met het eerste submenu onder Kop 6. Deze moet naar links worden geopend, omdat hij anders buiten het venster van de browser kan komen te staan.
left: auto;
De rechterkant van het submenu in deze <ul> moet gelijk komen te staan met de rechterkant van de <li> met Kop 6. Daarom moet deze <ul> vanaf rechts (en niet vanaf links) worden neergezet, wat gelijk hieronder gebeurt.
Eerder is bij #checkbox-open-menu:not(:checked) ~ nav ul ulleft: -1px; en width: 9.5rem; opgegeven. Ook deze <ul> valt onder die selector. Een combinatie van left, width én right is stomweg een onmogelijke opgave. In zo'n geval 'wint' left van right. Terwijl hier juist right nodig is.
Daarom wordt left hier teruggezet naar de standaardwaarde auto. Nu blijven alleen width en right over, en die combinatie kan wel.
right: -1px;
Rechterkant van de <ul> gelijk zetten met de rechterkant van de <li> met Kop 6.
#checkbox-open-menu:not(:checked) ~ nav .pijltje, #checkbox-open-menu:not(:checked) ~ nav .pijltje-links
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.)
#checkbox-open-menu:not(:checked) ~ nav: het eerste deel van de selector voor en na de komma is hetzelfde. De <nav> die ergens in de html op #checkbox-open-menu volgt, maar alleen als #checkbox-open-menu niet is aangevinkt.
Als #checkbox-open-menu is aangevinkt, is het menu voor gebruikers van Tab-toets of schermlezers geopend. Deze regel veroorzaakt in dat menu problemen. Daarom mag deze selector alleen werken, als #checkbox-open-menu niet is aangevinkt. Een uitgebreidere beschrijving staat bij #checkbox-open-menu:not(:checked) ~ nav.
De rest van de eerste selector .pijltje:
.pijltje: de elementen met class="pijltje" binnen de hierboven besproken <nav>
De rest van de tweede selector .pijltje-links:
.pijltje-links: de elementen met class="pijltje" binnen de hierboven besproken <nav>
Beide selectors in normale mensentaal: doe iets met de elementen met class="pijltje" en class="pijltje-links" in de <nav>, waar het hele uitklapmenu in zit, maar alleen als input#checkbox-open-menu niet is aangevinkt.
Dit zijn de <span>'s, waar de pijltjes in zitten die naar links of rechts wijzen en een submenu aangeven. Het zijn eigenlijk gewone Franse aanhalingstekens « en », die bij .pijltje, .pijltje-links zijn omgebouwd tot pijltjes.
display: inline-block;
Eerder zijn bij.pijltje, .pijltje-links de <span>'s met display: none; verborgen. In browservensters minimaal 760 px breed moeten ze worden getoond.
Een <span> is van zichzelf een inline-element. Daardoor kan de iets hieronder gegeven negatieve marge aan de bovenkant niet worden gebruikt. Als van de <span> een blok-element wordt gemaakt, komt deze met het erin zittende pijltje op een nieuwe regel te staan.
Door deze regel kan de marge wel worden gebruikt, maar komt de <span> toch niet op een nieuwe regel te staan.
font-size: 1.5rem;
Iets grotere lettergrootte.
Als eenheid wordt de relatieve eenheid rem 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.
De rem is ongeveer hetzelfde als de bekendere eenheid em, maar is gebaseerd op de lettergrootte van <html>. Hierdoor is de rem, anders dan de em, overal op de pagina even groot. Ook als de bezoeker de lettergrootte heeft veranderd.
Omdat de meeste maten in het menu als eenheid de rem gebruiken, blijft de lay-out van het menu ook intact, als de bezoeker de lettergrootte verandert.
margin-top: -2px;
Door de iets grotere lettergrootte hierboven, neemt ook de regelhoogte toe. Door de <span>'s 2 px terug naar boven te zetten, wordt dit verholpen. Dit werkt hier beter dan het aanpassen van de regelhoogte, want dat doet niet elke browser op precies dezelfde manier.
position: absolute;
Om de pijltjes op de juiste plaats neer te kunnen zetten. Er wordt gepositioneerd ten opzichte van de eerste voorouder die geen statische positie heeft. Dat is hier de <li>, waar de <span> in zit, want deze heeft bij nav li een relatieve positie gekregen.
right: 5px;
Op 5 px vanaf rechts neerzetten.
#checkbox-open-menu:not(:checked) ~ nav .pijltje-links
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.)
De meeste css is hierboven al opgegeven, maar hier wordt wat aangepast voor de pijltjes die naar links wijzen.
right: auto; left: 5px;
Hierboven bij de css voor alle pijltjes is right: 5px; opgegeven. Maar deze pijltjes moeten juist links komen te staan.
Als alleen left: 5px; wordt opgegeven, wordt de <span> op 5 px vanaf links én 5 px vanaf rechts neergezet. Dat kan alleen door de <span> uit te rekken. Op de afbeelding is de <span> even voorzien van een doorzichtige grijze achtergrond, waardoor deze uitrekking te zien is.
Eerder is bij #checkbox-open-menu:not(:checked) ~ nav litext-align: center; opgegeven. Omdat deze eigenschap door de <span> wordt geërfd van de <li>, wordt het pijltje netjes gecentreerd, waardoor het in het midden van de tekst in de <li> komt te staan, in plaats van links daarvan. Een computer is (nog) heel erg dom en voert gewoon hersenloos instructies uit, blijkt maar weer eens.
Door right terug te zetten naar de standaardwaarde auto wordt dit uitrekken voorkomen.
nav ul 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. (Alleen wat binnen deze media query geldig is, wordt binnen dit blokje herhaald.)
nav li {background: #e4e4e4; color: black; box-sizing: border-box; line-height: 2.5rem; text-indent: 0.3rem; border: black solid 1px; border-top: none; position: relative; -ms-user-select: none;}
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 selector :nth-of-type() worden alleen elementen van een bepaald soort geteld. Omdat het vaak voorkomt dat je het laatste element van 'n bepaald type wilt hebben, heeft dat een aparte selector: :last-of-type.
nav ul ul: alle <ul>'s die in een <ul> zitten, die weer in een <nav> zit. Dit zijn alle <ul>'s waarbinnen de submenu's van het tweede, derde en vierde niveau zitten.
li:last-of-type: het element met een bepaald volgnummer. In dit geval wordt geen volgnummer gebruikt, maar een speciaal voor het laatste element bedoelde selector: :last-of-type. Voor alle eerdere elementen gebruik je 'n soortgelijke selector, maar dan met een volgnummer: :nth-of-type(). Tussen de haakjes komt het volgnummer. :nth-of-type(1). Het voordeel van :last-of-type: dit past zich automatisch aan, als ooit een <li> wordt toegevoegd of verwijderd. Deze selector geldt altijd alleen voor de laatste <li>.
Omdat voor :last-of-type (of voor eerdere elementen :nth-of-type()) nav ul ul staat, geldt deze selector alleen voor <li>'s die binnen een minimaal dubbel geneste <ul> zitten. Ook bijvoorbeeld <li>'s binnen nav ul ul ul vallen onder deze selector, want ook die zitten in een dubbel geneste <ul>.
De hele selector in normale mensentaal: alle <li>'s die in een minstens dubbel geneste <ul> zitten, maar alleen de laatste van die <li>'s.
Dit zijn de laatste <li>'s van de submenu's van het tweede, derde en vierde niveau.
border-bottom: black solid 1px;
Eerder is de border aan de onderkant weggehaald bij deze elementen om een dubbele border in browservensters smaller dan 760 px te voorkomen. Hier worden de <ul>'s met de submenu's niet onder elkaar gezet en moet elke onderste <li> dus ook weer een border krijgen.
#checkbox-open-menu:not(:checked) ~ nav ul ul 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. (Alleen wat binnen deze media query geldig is, wordt binnen dit blokje herhaald.)
nav ul {height: 0; overflow: hidden; list-style-type: none; margin: 0; padding: 0;}
nav ul ul {opacity: 0; box-shadow: black -5px -4px 10px; width: 14rem; margin: 1px 0 0 1rem; transition: opacity 1s;}
nav ul ul ul {width: 12.95rem; width: calc(13rem - 1px);}
#checkbox-open-menu:not(:checked) ~ nav: de <nav> die ergens in de html op #checkbox-open-menu volgt, maar alleen als #checkbox-open-menu niet is aangevinkt.
Als #checkbox-open-menu is aangevinkt, is het menu voor gebruikers van Tab-toets of schermlezers geopend. Deze regel veroorzaakt in dat menu problemen. Daarom mag deze selector alleen werken, als #checkbox-open-menu niet is aangevinkt. Een uitgebreidere beschrijving staat bij #checkbox-open-menu:not(:checked) ~ nav.
ul ul ul: <ul>'s die in een andere <ul> zitten, die zelf ook weer in een andere <ul> zit.
De hele selector in normale mensentaal: doe iets met minstens driedubbel geneste <ul>'s, maar alleen als input#checkbox-open-menu niet is aangevinkt.
Dit zijn de <ul>'s waar de submenu's van het derde en vierde niveau in zitten.
margin: 0 0 0 -2px;
Eerdere marges boven, rechts en onder verwijderen en links een negatieve marge van 2 px geven, waardoor de hele <ul> 2 px naar links verschuift.
Hier gelijk onder worden deze <ul>'s met left: 9.5rem; op 9,5 rem vanaf links neergezet: netjes tegen het submenu aan, waar het submenu in deze <ul> weer onderdeel van is. Die left: 9.5rem; geldt ten opzichte van de eerste voorouder die zelf een andere positie dan statisch heeft. Dat is de <li> waar deze <ul> in zit.
Je zou dus denken dat, als je de <ul> op 9,5 van de linkerkant van die <li> zou zetten, het geheel mooi aan zou sluiten. Dat is echter niet zo: er ontstaat een kier van 1 px tussen beide submenu's. Die kier zit tussen ouder <li> met de koptekst, en kind <ul> met het submenu. Op de afbeelding is de negatieve marge links weggehaald en kun je die kier zien.
Wat gebeurt hier?
Bij nav li heeft de <li> ook een border gekregen van 1 px breed. Bovendien is daar met box-sizing: border-box; opgegeven, dat die border - anders dan standaard - binnen de breedte van 9,5 rem komt te vallen. De left: 9.5rem; van de <ul> geldt echter ten opzichte van de breedte van de <li>, dus die begint pas te tellen vanaf die border: 1 px vanaf links.
De <li> is echter 9,5 rem breed, inclusief die border. Met als resultaat dat de <ul> 9,5 rem + 1 px links van de <li> komt te staan. Om de ontstane kier van 1 px weg te halen, wordt een negatieve marge links van 1 px gegeven. Nu staan <li> en <ul> netjes tegen elkaar aan.
(Dit is één van de erfenissen uit het verleden. Als css nu zou worden ontwikkeld, zou het hele model van borders en paddings die bij de breedte komen, waarschijnlijk helemaal niet bestaan. En dan zou left hier waarschijnlijk ook op een meer intuïtieve manier werken.)
Nou, netjes... Alle <li>'s hebben links en rechts een border. Als de twee submenu's precies tegen elkaar aan komen te staan, komen deze borders tegen elkaar aan te staan. Op de afbeelding is te zien dat dit op het raakvlak tussen twee submenu's een dubbele border oplevert.
Door de <ul> niet 1, maar 2 px naar links te verschuiven, komen de borders over elkaar heen te staan en zijn ze ook op het raakvlak maar 1 px breed.
left: 9.5rem;
9,5 rem vanaf links neerzetten, waardoor de <ul> met het submenu netjes naast het submenu komt te staan, waar het submenu bij hoort. Deze 9,5 rem moet nog iets worden gecorrigeerd. Dat gebeurt gelijk hierboven met een negatieve linkermarge van 2px. Daar staat ook een uitgebreider verhaal over deze correctie.
Als eenheid wordt de relatieve eenheid rem gebruikt, omdat bij een absolute eenheid zoals px de afstand vanaf links niet mee verandert met de lettergrootte.
De rem werkt ongeveer hetzelfde als de bekendere eenheid em. Ook de rem is op de lettergrootte gebaseerd, maar altijd op de lettergrootte van <html>. Daardoor is de rem altijd overal even groot, ongeacht de lettergrootte van het element zelf. En ongeacht eventuele afwijkende lettergroottes in voorouders van het element. Als de bezoeker de lettergrootte verandert, wordt in feite de lettergrootte van <html> veranderd. Hierdoor verandert ook de grootte van de rem, maar die verandering is overal op de pagina precies hetzelfde.
Omdat de meeste maten in het menu als eenheid de rem gebruiken, blijft de lay-out van het menu ook intact, als de bezoeker de lettergrootte verandert.
Als twee of meer elementen ten opzichte van elkaar neergezet moeten worden, een bepaalde grootte ten opzichte van elkaar moeten hebben, en dergelijke, is de eenheid rem vrijwel altijd beter dan de eenheid em. (Even los van nieuwere mogelijkheden als flexbox en grid, maar die worden hier niet gebruikt.)
#checkbox-open-menu:not(:checked) ~ nav ul ul ul li: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. (Alleen wat binnen deze media query geldig is, wordt binnen dit blokje herhaald.)
nav li {background: #e4e4e4; color: black; box-sizing: border-box; line-height: 2.5rem; text-indent: 0.3rem; border: black solid 1px; border-top: none; position: relative; -ms-user-select: none;}
#checkbox-open-menu:not(:checked) ~ nav: de <nav> die ergens in de html op #checkbox-open-menu volgt, maar alleen als #checkbox-open-menu niet is aangevinkt.
Als #checkbox-open-menu is aangevinkt, is het menu voor gebruikers van Tab-toets of schermlezers geopend. Deze regel veroorzaakt in dat menu problemen. Daarom mag deze selector alleen werken, als #checkbox-open-menu niet is aangevinkt. Een uitgebreidere beschrijving staat bij #checkbox-open-menu:not(:checked) ~ nav.
ul ul ul: <ul>'s die in een andere <ul> zitten, die zelf ook weer in een andere <ul> zit.
li: alle <li>'s die binnen zo'n <ul> zitten.
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 de <li>'s die het eerste kind van zo'n <ul> zijn.
De hele selector in normale mensentaal: doe iets met <li>'s die het eerste kind zijn van een minstens driedubbel geneste <ul>, maar alleen als input#checkbox-open-menu niet is aangevinkt.
Dit zijn de eerste <li>'s in submenu's van het derde en vierde niveau.
border-top: black solid 1px;
In browservensters minstens 760 px breed staan de submenu's van het derde en vierde niveau soms een eind onder de menubalk. Daarom moet de eerder voor smallere vensters bij nav li verwijderde border aan de bovenkant weer worden aangebracht.
nav li:hover
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.)
nav li {background: #e4e4e4; color: black; box-sizing: border-box; line-height: 2.5rem; text-indent: 0.3rem; border: black solid 1px; border-top: none; position: relative; -ms-user-select: none;}
Alle <li>'s binnen een <nav>, maar alleen als over een <li> wordt gehoverd. Op een touchscreen werkt een aanraking in dit geval hetzelfde als hoveren. Er is maar één <nav> aanwezig in dit voorbeeld, daarin zit het hele uitklapmenu. Elke kop en link in dat menu zit in een eigen <li>.
background: white;
Achtergrond wit maken.
Omdat elke link en kop in het menu in een <li> zit, wordt de achtergrond van elke link en kop wit, als je daar met de muis over hovert.
Als je door hoveren een submenu van het tweede niveau opent en daar met de muis naartoe gaat, hover je ook nog steeds over de eerdere <li>. Dat hele submenu van het tweede niveau zit immers in die eerdere <li>.
Hetzelfde geldt voor submenu's van het derde en vierde niveau: ook die zitten steeds in een eerdere <li>. Hierdoor blijft de achtergrond van elke <li> die is gevolgd om een bepaald submenu te openen wit. Waardoor je makkelijk kunt zien, bij welke kop een bepaald submenu hoort.
Op de afbeelding is te zien, hoe dit werkt. Het submenu onder Kop 6 - 1 valt onder Kop 6 met de witte achtergrond, het submenu onder Link 6 - 1 - 1 valt onder Kop 6 - 1 met de witte achtergrond, en het submenu onder Link 6 - 1 - 3 - 1 valt onder Kop 6 - 1 - 3 met de witte achtergrond. Link 6 - 1 - 3 - 2 ten slotte heeft een witte achtergrond, omdat daarover wordt gehoverd.
(Met de genummerde links en koppen is de indeling ook makkelijk te zien, maar normaal genomen zul je niet zo'n soort nummering hebben.)
Op touchscreens werkt hoveren uiteraard niet, maar een aanraking heeft hier hetzelfde effect: elke <li>, die is aangeraakt om een bepaald submenu te openen, blijft wit.
#checkbox-open-menu:not(:checked) ~ nav li:hover > 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. (Alleen wat binnen deze media query geldig is, wordt binnen dit blokje herhaald.)
nav ul {height: 0; overflow: hidden; list-style-type: none; margin: 0; padding: 0;}
nav ul ul {opacity: 0; box-shadow: black -5px -4px 10px; width: 14rem; margin: 1px 0 0 1rem; transition: opacity 1s;}
nav ul ul ul {width: 12.95rem; width: calc(13rem - 1px);}
#checkbox-open-menu:not(:checked) ~ nav: de <nav> die ergens in de html op #checkbox-open-menu volgt, maar alleen als #checkbox-open-menu niet is aangevinkt.
Als #checkbox-open-menu is aangevinkt, is het menu voor gebruikers van Tab-toets of schermlezers geopend. Deze regel veroorzaakt in dat menu problemen. Daarom mag deze selector alleen werken, als #checkbox-open-menu niet is aangevinkt. Een uitgebreidere beschrijving staat bij #checkbox-open-menu:not(:checked) ~ nav.
li: alle <li>'s binnen de hierboven beschreven <nav>. Elke link en kop binnen het menu zit binnen een eigen <li>.
:hover: maar alleen als over die <li> wordt gehoverd. Op een touchscreen werkt een aanraking in dit geval hetzelfde als hoveren met de muis.
> ul: alle <ul>'s binnen bovenstaande <li>. Het teken > geeft aan, dat het alleen <ul>'s mogen zijn die een direct kind van die <li> zijn. Onderstaande <ul> is een direct kind van de <li>:
<li><ul></ul></li>
De middelste <ul> hieronder is geen direct kind van de buitenste <li>, omdat er een <ul> en een <li> tussen de buitenste <li> en de binnenste <ul> zitten:
<li><ul><li><ul></ul></li></ul></li>
In dit voorbeeld heeft elke <li> maar één <ul>, die een direct kind van de <li> is. Binnen deze <ul> zit een submenu. Alle andere <ul>'s zijn weer nakomelingen van die <ul> en dus geen direct kind van de eerste <li>. Deze selector geldt dus alleen voor die ene <ul>, die een direct kind is van de <li>, waarover wordt gehoverd.
li:hover > ul:
dit hele stukje samen: als over een <li> wordt gehoverd, doe dan iets met de <ul> die een direct kind van die <li> is.
Als de > in de selector zou worden weggelaten, zou deze selector voor álle <ul>'s die een nakomeling van de <li> zijn gelden. Omdat deze selector wordt gebruikt om submenu's te openen, zou dat betekenen dat álle submenu's die ergens binnen die <li> zitten in één keer worden geopend. Op de afbeelding is het resultaat te zien van hoveren over Kop5: álle submenu's die ergens onder die kop vallen, op welk niveau dan ook, zijn geopend. Omdat veel van die submenu's gedeeltelijk op dezelfde plaats staan, dekken ze elkaar ook nog 'ns af.
Op een touchscreen werkt het precies hetzelfde, alleen worden dan alle submenu's in de <li> geopend, als je de <li> aanraakt.
Het weglaten van één rottige > leidt tot volkomen andere resultaten. Als je je nog 'ns afvraagt, hoe 'n programmeerfout 'n ramp kan veroorzaken, dan weet je nu één van de mogelijke antwoorden: het vergeten van 'n simpel teken.
De hele selector in normale mensentaal: doe iets met de ul die een direct kind is van een <li>, waarover wordt gehoverd (of die wordt aangeraakt), maar alleen als input#checkbox-open-menu niet is aangevinkt.
Dit zijn de <ul>'s waarin de submenu's van het tweede, derde en vierde niveau zitten. Omdat alleen in <li>'s met een kop een <ul> zit, en niet in <li>'s met een link, heeft deze selector alleen effect bij hoveren over een <li> met een kop. Als dat gebeurt, opent het bij die kop horende submenu.
top: 2rem;
Om ze te verbergen zijn deze <ul>'s eerder bij #checkbox-open-menu:not(:checked) ~ nav ul ul met top: -2000px; aan de bovenkant buiten het scherm gezet. Hier wordt de <ul> binnen het scherm gezet en daardoor zichtbaar.
Bij #checkbox-open-menu:not(:checked) ~ nav > ul > li hebben de zes <li>'s in de menubalk een regelhoogte van 2 rem gekregen. Daardoor is ook de menubalk 2 rem hoog. Met top: 2rem; staan de submenu's precies tegen de menubalk aan.
Als eenheid wordt de relatieve eenheid rem gebruikt, omdat bij een absolute eenheid zoals px de afstand tot de bovenkant niet mee verandert met de lettergrootte.
De rem werkt ongeveer hetzelfde als de bekendere eenheid em. Ook de rem is op de lettergrootte gebaseerd, maar altijd op de lettergrootte van <html>. Daardoor is de rem altijd overal even groot, ongeacht de lettergrootte van het element zelf. En ongeacht eventuele afwijkende lettergroottes in voorouders van het element. Als de bezoeker de lettergrootte verandert, wordt in feite de lettergrootte van <html> veranderd. Hierdoor verandert ook de grootte van de rem, maar die verandering is overal op de pagina precies hetzelfde.
Omdat de meeste maten in het menu als eenheid de rem gebruiken, blijft de lay-out van het menu ook intact, als de bezoeker de lettergrootte verandert.
Als twee of meer elementen ten opzichte van elkaar neergezet moeten worden, een bepaalde grootte ten opzichte van elkaar moeten hebben, en dergelijke, is de eenheid rem vrijwel altijd beter dan de eenheid em. (Even los van nieuwere mogelijkheden als flexbox en grid, maar die worden hier niet gebruikt.)
transition: top 0s 0s;
Eerder is bij #checkbox-open-menu:not(:checked) ~ nav ul ul met transition: top 0 0.15s; gezorgd dat een submenu nog heel even blijft geopend, als de muis eraf schiet. Dit geeft de tijd om te corrigeren, als dat per ongeluk gebeurt. (Bij aanraken op een touchscreen speelt dit niet, het is typisch iets voor een muis.)
Openen van een submenu moet echter wel snel gebeuren, anders is dat heel verwarrend. Daarom staat hier nogmaals transition, maar nu met een vertraging van 0s: gelijk uitvoeren.
Een langer verhaal is bij bovenstaande selector te vinden.
#checkbox-open-menu:not(:checked) ~ nav li:hover li:hover > 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. (Alleen wat binnen deze media query geldig is, wordt binnen dit blokje herhaald.)
nav ul {height: 0; overflow: hidden; list-style-type: none; margin: 0; padding: 0;}
nav ul ul {opacity: 0; box-shadow: black -5px -4px 10px; width: 14rem; margin: 1px 0 0 1rem; transition: opacity 1s;}
nav ul ul ul {width: 12.95rem; width: calc(13rem - 1px);}
Deze selector is grotendeels hetzelfde als die gelijk hierboven bij #checkbox-open-menu:not(:checked) ~ nav li:hover > ul. Het enige verschil: in bovenstaande selector zit slechts één keer li:hover, en in de selector hier zit twee keer li:hover.
Op de afbeelding is bovenaan de <nav> uit de selector te zien. Eerst is over de bovenste witte <li> gehoverd, waardoor het submenu daaronder opent. Vervolgens wordt over de daarin zittende tweede <li> gehoverd, waardoor de <ul> met het submenu links opent.
Omdat de tweede <li> ook binnen de eerste <li> zit, hover je ook nog steeds over de eerste <li>, als je over de tweede <li> hovert.
Ook als je over de <ul> links hovert, hover je nog steeds over de eerste <li>, want ook die <ul> zit binnen de eerste <li> (en ook binnen de tweede <li>). En een submenu van die <ul> zit ook nog steeds binnen de eerste <li> en tweede <li>.
(Op een touchscreen werkt het precies hetzelfde, maar daar moet je 'hoveren' vervangen door 'aanraken'.)
Omdat in deze selector twee keer li:hover voor ul staat, geldt deze selector alleen voor <ul>'s die in minstens twee <li>'s zitten. Dit zijn de <ul>'s met de submenu's van het derde en vierde niveau.
top: -1px;
Hier gelijk boven bij #checkbox-open-menu:not(:checked) ~ nav li:hover > ul worden de <ul>'s met de submenu's bij hoveren over een kop op een hoogte van 2 rem neergezet, gelijk onder de menubalk. Dat is mooi voor de submenu's van het tweede niveau, maar niet voor die van het derde en vierde niveau. Dit moeten op gelijke hoogte komen met de kop, waar ze bij horen. Anders is lastig te zien, onder welke kop een submenu valt.
Helaas werkt die sector hierboven voor alle <ul>'s die een direct kind van een <li> zijn. En aangezien elke <ul> in het menu een direct kind van een <li> is, worden dus álle <ul> met submenu's op die hoogte gezet.
Deze selector geldt alleen voor <ul>'s met submenu's van het derde en vierde niveau. Er wordt gepositioneerd ten opzichte van de eerste voorouder die een andere positie dan statisch heeft. Dat is, niet geheel toevallig, de <li> waar de kop in zit, die bij het submenu in de <ul> hoort.
position: absolute; werkt ten opzichte van de eerste voorouder met een andere positie dan statisch, maar laat daarbij een eventuele border van die voorouder buiten beschouwing. Om de horizontale borders van de <ul> met het submenu op gelijke hoogte te krijgen als die van het menu erboven, moet daarom niet 0, maar -1px als waarde worden gebruikt.
main p
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.)
De eerder opgegeven padding links en rechts verwijderen.
#voor-tab: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. (Alleen wat binnen deze media query geldig is, wordt binnen dit blokje herhaald.)
Het element met id="voor-tab", maar alleen als dit element focus heeft. De <a> waarin tekst voor gebruikers van de Tab-toets staat en die gebruikers laat kiezen tussen het openen van het menu, waardoor dit toegankelijk wordt voor de Tab-toets, of het in één keer passeren van het hele menu.
Omdat dit element eerder onzichtbaar links buiten het scherm is geparkeerd, kan het alleen focus krijgen als de Tab-toets wordt gebruikt. En dat komt goed uit, want deze tekst is nou net bedoeld voor gebruikers van de Tab-toets. Zodra de Tab-toets nogmaals wordt ingedrukt, verdwijnt deze melding weer, omdat #voor-tab dan geen focus meer heeft.
left: 100px;
Binnen het scherm zetten, waardoor de tekst in de <a> zichtbaar wordt en gelezen kan worden.
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.)
.xgeen-jsx: de elementen met class="xgeen-jsx". Bij het maken van de pagina moet <body> class="xgeen-jsx" krijgen. Een van de eerste dingen die het JavaScript doet, is het verwijderen van deze class. Als JavaScript niet aanstaat, wordt de class niet verwijderd. Oftewel: deze selector werkt alleen, als JavaScript uitstaat. Hierdoor kun je hier tekst in stoppen, die alleen wordt getoond, als JavaScript uitstaat.
('xgeen-jsx' is een rare naam. Dit voorkomt verwarring met mogelijke andere bij <body> gebruikte classes. Als je deze naam in de html wilt wijzigen, moet je dat ook in het script en in de css doen.)
Het verwijderen van 'xgeen-jsx' heeft geen enkele invloed op eventuele andere classes of een id, deze kunnen gewoon worden gebruikt.
Om het verwijderen van 'xgeen-jsx' te zien moet je niet de gewone broncode, maar de Gegenereerde code bekijken.
#checkbox-open-menu:focus: als een gebruiker van de Tab-toets de Tab-toets een tweede keer indrukt, wordt het menu toegankelijk voor de Tab-toets. Dat wordt geregeld door middel van JavaScript: het script vinkt input#checkbox-open-menu aan, waardoor het menu voor de Tab-toets wordt geopend.
Als dit gebeurt, wordt de focus door het script verplaatst naar het eerste item in het menu. Daarbij wordt #checkbox-open-menu gepasseerd en krijgt dus geen focus.
Als JavaScript uitstaat, gebeurt dit allemaal niet. Als nu de Tab-toets voor de tweede keer wordt ingedrukt, krijgt #checkbox-open-menu focus. Omdat #checkbox-open-menu bij #checkbox-open-menu onzichtbaar links buiten het scherm is gezet, kan deze <input> alleen door gebruik van de Tab-toets focus krijgen.
+: het element achter de + moet in de html direct volgen op het element voor de +. In dit geval gaat het om #label-open-menu die gelijk op #checkbox-open-menu moet volgen. Beide elementen moeten ook nog dezelfde ouder hebben. Dat is hier het geval, ze hebben beide als ouder <header>.
#label-open-menu: de <label> die bij input#checkbox-open-menu hoort. In deze <label> staat tekst voor gebruikers van de Tab-toets, die JavaScript uit hebben staan.
De hele selector in normale mensentaal: doe iets met de #label-open-menu die in de html gelijk volgt op input#checkbox-open-menu, maar alleen als die <input> focus heeft en bij <body> class 'xgeen-jsx' aanwezig is.
Het is relatief makkelijk om het menu voor gebruikers van de Tab-toets te laten openen met behulp van het toetsenbord, zonder JavaScript. Daarom wordt hier die mogelijkheid geboden.
display: block;
Bij #label-open-menu is de <label> met display: none; verborgen. Als de bij de <label> horende input#checkbox-open-menufocus heeft, wordt de <label> getoond en kan de tekst erin worden gelezen.
#checkbox-open-menu:checked ~ nav
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.)
css bij selectors die beginnen met #checkbox-open-menu:not(:checked) is weggelaten.
nav {width: 15.9rem; overflow: auto; position: fixed; top: 2rem;}
#checkbox-open-menu:checked: als het element met id="checkbox-open-menu" is aangevinkt. Deze <input> is alleen aangevinkt, als het menu voor gebruikers van de Tab-toets of van een schermlezer is geopend.
~: 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: #checkbox-open-menu voor de ~ en nav na de ~ hebben beide als ouder <header>.
nav: de <nav> waar het hele uitklapmenu in zit.
De hele selector in gewone mensentaal: doe iets met de <nav> als input#checkbox-open-menu is aangevinkt.
overflow: visible;
Bij nav is er met overflow: auto; voor gezorgd dat het menu in browservensters smaller dan 760 px binnen de <nav> gescrold kan worden, als het niet binnen het venster past, zonder dat de rest van de pagina meescrolt. Hier mag de rest van de pagina meescrollen, omdat het menu hier volkomen anders werkt. Daarom wordt overflow teruggezet naar de standaardinstelling: alles zichtbaar.
position: static;
Omdat de in de <nav> zittende menubalk ook bij scrollen in browservensters smaller dan 760 px bovenaan het venster moet blijven staan, is de <nav> bij nav fixed gepositioneerd. Hier is helemaal geen menubalk, dus er hoeft ook niets te blijven staan.
#checkbox-open-menu:checked ~ #checkbox-voor-schermlezers-1:not(:checked) ~ nav #uitleg-tab
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.)
#checkbox-open-menu:checked: als het element met id="checkbox-open-menu" is aangevinkt. Deze <input> is alleen aangevinkt, als het menu voor gebruikers van de Tab-toets of van een schermlezer is geopend.
~: 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: #checkbox-open-menu voor de ~ en #checkbox-voor-schermlezers-1 na de ~ hebben beide als ouder <header>.
#checkbox-voor-schermlezers-1:not(:checked):
#checkbox-voor-schermlezers-1: is een <input> voor schermlezers. Als deze is aangevinkt, is mogelijk het menu voor schermlezers geopend (mogelijk, want er is nog een tweede <input> die moet zijn aangevinkt).
:not(): wat tussen de haakjes staat, mag niet aanwezig zijn.
:not(:checked): de <input> mag niet aangevinkt zijn.
#checkbox-voor-schermlezers-1:not(:checked): #checkbox-voor-schermlezers-1, maar alleen als deze niet is aangevinkt.
~: 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: #checkbox-voor-schermlezers-1 voor de ~ en nav na de ~ hebben beide als ouder <header>.
nav: de <nav> waar het hele uitklapmenu in zit.
#uitleg-tab: het element met id="uitleg-tab". Dit is een <div> met uitleg voor gebruikers van de Tab-toets.
De hele selector in normale mensentaal: doe iets met de <div> in de <nav>, waarin de de uitleg voor gebruikers van de Tab-toets zit, maar alleen als input#checkbox-open-menu is aangevinkt én input#checkbox-voor-schermlezers-1 juist niet is aangevinkt.
Bij de meeste selectors voor het menu voor gebruikers van de Tab-toets volstaat de selector #checkbox-open-menu:checked ~ nav. Hier is daartussen opgenomen dat de eerste <input> voor het menu voor gebruikers van schermlezers niet aangevinkt mag zijn.
In sommige browsers wordt div#uitleg-tab met de uitleg voor gebruikers van de Tab-toets getoond (en voorgelezen), als de pagina wordt herladen, terwijl het menu voor gebruikers van de schermlezer wordt getoond. Dat is uiterst verwarrend. Door de selector niet te laten werken als de eerste <input> voor het menu voor schermlezers is aangevinkt, wordt dit voorkomen.
(Dit wordt mede veroorzaakt, doordat veel html en css voor gebruikers van Tab-toets én schermlezers wordt gebruikt. Dit spaart veel html en css uit. Maar heel soms zorgt dat voor verwarring.)
Vrijwel alle css is al eerder overzichtelijk op één plek opgegeven, zodat de <div> hier alleen nog zichtbaar hoeft te worden gemaakt. Die eerdere css is echter ook voor div#uitleg-schermlezer, en die mag deze regel niet krijgen. Daarom wordt left hier alsnog apart voor #uitleg-tab opgegeven, zodat de uitleg voor gebruikers van de Tab-toets niet aan de bovenkant onder het menu, maar er links naast komt te staan.
Als eenheid wordt de relatieve eenheid rem gebruikt, omdat bij een absolute eenheid zoals px de afstand vanaf links niet mee verandert met de lettergrootte.
De rem werkt ongeveer hetzelfde als de bekendere eenheid em. Ook de rem is op de lettergrootte gebaseerd, maar altijd op de lettergrootte van <html>. Daardoor is de rem altijd overal even groot, ongeacht de lettergrootte van het element zelf. En ongeacht eventuele afwijkende lettergroottes in voorouders van het element. Als de bezoeker de lettergrootte verandert, wordt in feite de lettergrootte van <html> veranderd. Hierdoor verandert ook de grootte van de rem, maar die verandering is overal op de pagina precies hetzelfde.
Omdat de meeste maten in het menu als eenheid de rem gebruiken, blijft de lay-out van het menu ook intact, als de bezoeker de lettergrootte verandert.
Als twee of meer elementen ten opzichte van elkaar neergezet moeten worden, een bepaalde grootte ten opzichte van elkaar moeten hebben, en dergelijke, is de eenheid rem vrijwel altijd beter dan de eenheid em. (Even los van nieuwere mogelijkheden als flexbox en grid, maar die worden hier niet gebruikt.)
#checkbox-open-menu:checked ~ nav > 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. (Alleen wat binnen deze media query geldig is, wordt binnen dit blokje herhaald.)
css bij selectors die beginnen met #checkbox-open-menu:not(:checked) is weggelaten.
nav ul {height: 0; overflow: hidden; list-style-type: none; margin: 0; padding: 0;}
#checkbox-open-menu:checked ~ nav: als het element met id="checkbox-open-menu" is aangevinkt, doe dan iets met de <nav> met het uitklapmenu. Deze <input> is alleen aangevinkt, als het menu voor gebruikers van de Tab-toets of van een schermlezer is geopend. (Een iets uitgebreidere beschrijving van dit deel van de selector staat bij #checkbox-open-menu:checked ~ nav.)
> ul: de <ul>'s binnen de hierboven beschreven <nav>, maar alleen de <ul>'s die een
direct kind van de <nav> zijn. Dat is er hier maar één: de <ul> waar de zes <li>'s met 'Kop 1' tot en met 'Kop 6' in zitten. (Een uitgebreidere beschrijving van > staat bij nav > ul.)
De hele selector in normale mensentaal: doe iets met de <ul> die een direct kind is van de <nav>, maar alleen als input#checkbox-open-menu is aangevinkt.
height: auto;
Bij nav ul hebben alle <ul>'s een hoogte van 0 px gekregen. En dat leest een beetje lastig. Hier moet het hele menu zichtbaar zijn. Door de hoogte terug te zetten naar de standaardwaarde auto wordt de <ul> precies hoog genoeg om de inhoud ervan weer te kunnen geven.
padding-bottom: 30px;
Wat extra ruimte aan de onderkant. Omdat dit alleen voor de buitenste <ul> geldt (dat is de enige <ul> die een direct kind van de <nav> is), komt deze extra ruimte alleen aan de onderkant van het hele menu.
Sommige browsers zetten de bestemming van een link links onderin het browservenster, als de link focus heeft (of als erover wordt gehoverd). Dit menu is speciaal voor gebruikers van de Tab-toets, dus er zal heel vaak een link met focus zijn. Omdat de bestemming van de link met focus precies over de onderste link in het menu komt te staan, is die onderste link niet te lezen. Door wat extra ruimte onder die onderste link te zetten, is deze in ieder geval na wat scrollen te lezen.
#checkbox-open-menu:checked ~ nav 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. (Alleen wat binnen deze media query geldig is, wordt binnen dit blokje herhaald.)
css bij selectors die beginnen met #checkbox-open-menu:not(:checked) is weggelaten.
nav ul {height: 0; overflow: hidden; list-style-type: none; margin: 0; padding: 0;}
#checkbox-open-menu:checked ~ nav: als het element met id="checkbox-open-menu" is aangevinkt, doe dan iets met de <nav> met het uitklapmenu. Deze <input> is alleen aangevinkt, als het menu voor gebruikers van de Tab-toets of van een schermlezer is geopend. Een iets uitgebreidere beschrijving van dit deel van de selector staat bij #checkbox-open-menu:checked ~ nav.
ul: de <ul>'s in de hierboven beschreven <nav>.
De hele selector in normale mensentaal: doe iets met de <ul>'s in de <nav>, maar alleen als input#checkbox-open-menu is aangevinkt.
opacity: 1;
Bij nav ul ul zijn de <ul>'s met de submenu's van het tweede, derde en vierde niveau onzichtbaar gemaakt met opacity: 0;, zodat ze wat geleidelijker konden worden geopend. Hier zijn die submenu's altijd geopend, dus worden ze gewoon zichtbaar gemaakt.
height: auto;
Bij nav ul hebben de <ul>'s een hoogte van 0 px gekregen, zodat ze onzichtbaar zijn. Hier zijn ze voortdurend zichtbaar. Met de standaardwaarde height: auto; worden ze precies hoog genoeg om de inhoud ervan weer te geven.
#checkbox-open-menu:checked ~ nav :focus
Alleen de <li>'s met een kopregel (<h>) en de <a>'s binnen <nav> kunnen focus krijgen. Omdat dit deels in de html wordt geregeld met het tabindex-attribuut, is hier niet goed weer te geven, voor welke elementen eerder css is opgegeven.
#checkbox-open-menu:checked ~ nav: als het element met id="checkbox-open-menu" is aangevinkt, doe dan iets met de <nav> met het uitklapmenu. Deze <input> is alleen aangevinkt, als het menu voor gebruikers van de Tab-toets of van een schermlezer is geopend. Een iets uitgebreidere beschrijving van dit deel van de selector staat bij #checkbox-open-menu:checked ~ nav.
:focus: het element dat focus heeft. Omdat voor :focus geen element, class, of wat dan ook staat, geldt dit voor álle elementen die focus hebben. Met behulp van het tabindex-attribuut is in de html geregeld, welke elementen focus kunnen krijgen.
Alle links (<a>) kunnen gewoon focus krijgen, dat is standaard.
<input>'s en <label>'s hebben geen nut in het menu voor gebruikers van Tab-toets of schermlezer en kunnen geen focus krijgen.
De <li>'s met een kopregel kunnen juist wel weer focus krijgen. Dit geeft een wat rustiger beeld, dan wanneer deze worden overgeslagen, als de Tab-toets wordt ingerukt.
De hele selector in normale mensentaal: doe iets met het element binnen de <nav> dat de focus heeft, maar alleen als input#checkbox-open-menu is aangevinkt.
background: white;
Witte achtergrond. Door bij focus de achtergrond wit te kleuren, is altijd duidelijk, welk element focus heeft.
#checkbox-open-menu:checked ~ nav a:focus
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.)
nav a {color: black; display: block; text-decoration: none;}
#checkbox-open-menu:checked ~ nav: als het element met id="checkbox-open-menu" is aangevinkt, doe dan iets met de <nav> met het uitklapmenu. Deze <input> is alleen aangevinkt, als het menu voor gebruikers van de Tab-toets of van een schermlezer is geopend. Een iets uitgebreidere beschrijving van dit deel van de selector staat bij #checkbox-open-menu:checked ~ nav.
a:focus: de <a>'s binnen de hierboven beschreven <nav>, maar alleen als de <a> de focus heeft.
De hele selector in normale mensentaal: doe iets met de <a> die de focus heeft binnen de <nav>, maar alleen als input#checkbox-open-menu is aangevinkt.
text-decoration: underline;
Hier gelijk boven bij #checkbox-open-menu:checked ~ nav :focus is de achtergrond wit gemaakt, als een element binnen de <nav> focus heeft. Maar dat geldt voor álle elementen die focus kunnen krijgen, ook de elementen die geen link zijn.
Bij de links is eerder bij nav a de onderstreping weggehaald. Die is lelijk en bij gebruik van de muis of van een touchscreen weet je al, welke link je gaat volgen. Maar voor gebruikers van de Tab-toets is niet duidelijk, wat een link is en wat niet. Daarom wordt hier voor de <a>'s de standaard-onderstreping van een link weer teruggezet. Nu wordt een <a> die focus heeft onderstreept en is duidelijk, welke link wordt gevolgd, als op Enter wordt gedrukt.
#checkbox-open-menu:checked ~ nav ul 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. (Alleen wat binnen deze media query geldig is, wordt binnen dit blokje herhaald.)
css bij selectors die beginnen met #checkbox-open-menu:not(:checked) is weggelaten.
nav ul {height: 0; overflow: hidden; list-style-type: none; margin: 0; padding: 0;}
nav ul ul {opacity: 0; box-shadow: black -5px -4px 10px; width: 14rem; margin: 1px 0 0 1rem; transition: opacity 1s;}
nav ul ul ul {width: 12.95rem; width: calc(13rem - 1px);}
#checkbox-open-menu:checked ~ nav: als het element met id="checkbox-open-menu" is aangevinkt, doe dan iets met de <nav> met het uitklapmenu. Deze <input> is alleen aangevinkt, als het menu voor gebruikers van de Tab-toets of van een schermlezer is geopend. Een iets uitgebreidere beschrijving van dit deel van de selector staat bij #checkbox-open-menu:checked ~ nav.
ul ul: <ul>'s die binnen een andere <ul> zitten binnen de hierboven beschreven <nav>. Dit zijn de <ul>'s, waarin de submenu's van het tweede, derde en vierde niveau zitten.
De hele selector in normale mensentaal: doe iets met de <ul>'s met de submenu's van het tweede, derde en vierde niveau binnen de <nav>, maar alleen als input#checkbox-open-menu is aangevinkt.
box-shadow: none;
Eerder is bij nav ul ul een schaduw voor deze <ul>'s opgegeven. Dat zou hier ook prima op z'n plaats zijn, maar kan helaas niet.
Als in Internet Explorer 11 de waarde voor spread wordt gebruikt bij box-shadow, verdwijnen delen van het menu, als de Tab-toets wordt ingedrukt. Waardoor het menu volledig onbruikbaar wordt. En een mooi menu is natuurlijk wel leuk en aardig en zo, maar als het onbruikbaar is, heb je er toch niet zoveel aan.
Daarom wordt de schaduw hier weggehaald en iets hieronder bij border vervangen door een gewone border.
width: auto;
De eerder opgegeven breedte voor deze <ul>'s laat de border aan de rechterkant van het menu wegvallen, omdat die breedte iets te groot is.
Die border rechts is de bij nav li opgegeven border. Omdat de <li>'s in dit menu boven elkaar staan, is de border aan de rechterkant van de <li>'s gelijk de border van het menu. De iets te brede <ul>'s komen net over die border heen te staan.
Omdat een <ul> een blok-element is, vullen de <ul>'s met de standaardwaarde auto nu automatisch de breedte van de ouder volledig, maar niet meer dan die breedte. Waardoor de border weer zichtbaar wordt.
border: #999 solid;
Grijze rand. De breedte wordt hier gelijk onder opgegeven.
Deze border vervangt de schaduw, die hier niet kan worden gebruikt, zoals iets hoger bij box-shadow wordt beschreven.
border-width: 5px 0 0 5px;
Breedte van de border. Kleur en stijl zijn gelijk hierboven al opgegeven.
Boven en links een border van 5 px breed, rechts en onder geen border.
#checkbox-open-menu:checked ~ nav ul ul li
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.)
css bij selectors die beginnen met #checkbox-open-menu:not(:checked) is weggelaten.
nav li {background: #e4e4e4; color: black; box-sizing: border-box; line-height: 2.5rem; text-indent: 0.3rem; border: black solid 1px; border-top: none; position: relative; -ms-user-select: none;}
#checkbox-open-menu:checked ~ nav: als het element met id="checkbox-open-menu" is aangevinkt, doe dan iets met de <nav> met het uitklapmenu. Deze <input> is alleen aangevinkt, als het menu voor gebruikers van de Tab-toets of van een schermlezer is geopend. Een iets uitgebreidere beschrijving van dit deel van de selector staat bij #checkbox-open-menu:checked ~ nav.
ul: <ul>'s die binnen de hierboven beschreven <nav> zitten.
ul: <ul>'s die weer binnen die <ul>'s zitten. Dit zijn de <ul>'s met de submenu's van het tweede, derde en vierde niveau.
li: <li>'s die weer binnen die <ul> zitten. Dit zijn de <li>'s uit de submenu's van het tweede, derde en vierde niveau.
De hele selector in normale mensentaal: doe iets met de <li>'s uit de submenu's van het tweede, derde en vierde niveau, maar alleen als input#checkbox-open-menu is aangevinkt.
border-right: none;
Bij nav li hebben alle <li>'s rondom een border gekregen. Omdat de submenu's hier als het ware in elkaar staan, komen de borders aan de rechterkant tegen elkaar aan te staan. Waardoor die border bij elk submenu 1 px breder wordt, zoals op de afbeelding is te zien.
Door bij alle <li>'s in de submenu's van het tweede, derde en vierde niveau de border rechts weg te halen, blijft alleen de border bij de <li>'s van de buitenste <ul> over. Waardoor je ook rechts 'n border van 1 px breed hebt.
#checkbox-open-menu:checked ~ nav ul 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. (Alleen wat binnen deze media query geldig is, wordt binnen dit blokje herhaald.)
css bij selectors die beginnen met #checkbox-open-menu:not(:checked) is weggelaten.
nav li {background: #e4e4e4; color: black; box-sizing: border-box; line-height: 2.5rem; text-indent: 0.3rem; border: black solid 1px; border-top: none; position: relative; -ms-user-select: none;}
#checkbox-open-menu:checked ~ nav: als het element met id="checkbox-open-menu" is aangevinkt, doe dan iets met de <nav> met het uitklapmenu. Deze <input> is alleen aangevinkt, als het menu voor gebruikers van de Tab-toets of van een schermlezer is geopend. Een iets uitgebreidere beschrijving van dit deel van de selector staat bij #checkbox-open-menu:checked ~ nav.
ul ul li: tot nu toe is dit precies dezelfde selector als gelijk hierboven bij #checkbox-open-menu:checked ~ nav ul ul li: de <li>'s uit de submenu's van het tweede, derde en vierde niveau.
:last-of-type: anders dan bij de selector hierboven, geldt deze regel alleen voor de laatste <li>'s uit elke <ul>. Met :nth-of-type() wordt een element met een bepaald volgnummer geselecteerd. In dit geval wordt geen volgnummer gebruikt, maar een speciaal voor het laatste element bedoelde selector: :last-of-type. Voor alle eerdere elementen gebruik je 'n soortgelijke selector, maar dan met een volgnummer: :nth-of-type(). Tussen de haakjes komt het volgnummer. :nth-of-type(1). Het voordeel van :last-of-type: dit past zich automatisch aan, als ooit een <li> wordt toegevoegd of verwijderd. Deze selector geldt altijd alleen voor de laatste <li>.
Omdat voor :last-of-type (of voor eerdere elementen :nth-of-type()) nav ul ul staat, geldt deze selector alleen voor <li>'s die binnen een minimaal dubbel geneste <ul> zitten. Ook bijvoorbeeld <li>'s binnen nav ul ul ul vallen onder deze selector, want ook die zitten in een dubbel geneste <ul>.
De hele selector in normale mensentaal: doe iets met elke laatste <li> uit de submenu's van het tweede, derde en vierde niveau, maar alleen als input#checkbox-open-menu is aangevinkt.
border-bottom: none;
Bij nav ul ul li:last-of-type hebben al deze <li>'s een border aan de onderkant gekregen. Hier komen die borders echter tegen de border aan de bovenkant van de eerste <li> uit het volgende submenu te staan. Precies hetzelfde probleem als gelijk hierboven bij #checkbox-open-menu:checked ~ nav ul ul li, maar daar ging het om de border aan de rechterkant.
Omdat dit alleen bij elke laatste <li> uit een <ul> speelt, wordt nu alleen bij de laatste <li> uit elke <ul> de border onderaan weggehaald.
.xschermlezerx #skippy
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 innen dit blokje herhaald.)
css bij selectors die beginnen met #checkbox-open-menu:not(:checked) is weggelaten, want als class 'xschermlezerx' aanwezig is bij <body>, is input#checkbox-open-menu met behulp van JavaScript aangevinkt.
Het element met id="skippy" binnen een element met class="xschermlezerx".
De class 'xschermlezerx' wordt door JavaScript aan <body> toegevoegd, als is gekozen om het menu voor schermlezers te openen. Er is meer over te lezen bij .xschermlezerx #menubalk-voor-smal.
Het element met id="skippy" is een zogenaamde skip-link. Het geeft aan gebruikers van een schermlezer de mogelijkheid in één keer het menu te passeren.
(Om het verwijderen en plaatsen van 'xschermlezerx' te zien moet je niet de gewone broncode, maar de Gegenereerde code bekijken.)
margin-left: 2px;
Omdat het menu voor schermlezers vrijwel hetzelfde is als dat voor gebruikers van de Tab-toets, is er geen ruimte voor de skip-link recht boven het menu. Daarom staat de link rechtsboven naast het menu. Maar omdat in de <html> deze link voor het menu komt, is het wel degelijk de eerste link. De marge zorgt voor een kleine ruimte tussen menu en skip-link.
left: 15rem;
Bij nav > ul > li zijn de <li>'s in de buitenste <ul> 15 rem breed gemaakt. Die bepalen hier de breedte van het menu. Als de skip-link op 15 rem vanaf links wordt neergezet, komt hij dus precies naast het menu te staan.
Als eenheid wordt de relatieve eenheid rem gebruikt, omdat bij een absolute eenheid zoals px de afstand vanaf links niet mee verandert met de lettergrootte.
De rem werkt ongeveer hetzelfde als de bekendere eenheid em. Ook de rem is op de lettergrootte gebaseerd, maar altijd op de lettergrootte van <html>. Daardoor is de rem altijd overal even groot, ongeacht de lettergrootte van het element zelf. En ongeacht eventuele afwijkende lettergroottes in voorouders van het element. Als de bezoeker de lettergrootte verandert, wordt in feite de lettergrootte van <html> veranderd. Hierdoor verandert ook de grootte van de rem, maar die verandering is overal op de pagina precies hetzelfde.
Omdat de meeste maten in het menu als eenheid de rem gebruiken, blijft de lay-out van het menu ook intact, als de bezoeker de lettergrootte verandert.
Als twee of meer elementen ten opzichte van elkaar neergezet moeten worden, een bepaalde grootte ten opzichte van elkaar moeten hebben, en dergelijke, is de eenheid rem vrijwel altijd beter dan de eenheid em. (Even los van nieuwere mogelijkheden als flexbox en grid, maar die worden hier niet gebruikt.)
.xschermlezerx #uitleg-schermlezer
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.)
css bij selectors die beginnen met #checkbox-open-menu:not(:checked) is weggelaten, want als class 'xschermlezerx' aanwezig is bij <body>, is input#checkbox-open-menu met behulp van JavaScript aangevinkt.
Het element met id="uitleg-schermlezer" binnen een element met class="xschermlezerx".
De class 'xschermlezerx' wordt door JavaScript aan <body> toegevoegd, als is gekozen om het menu voor schermlezers te openen. Er is meer over te lezen bij .xschermlezerx #menubalk-voor-smal.
Het element met id="uitleg-schermlezer" is een <div> met een korte uitleg voor gebruikers van een schermlezer.
margin-left: 2px;
Zelfde marge geven als de boven deze <div> staande skip-link hier gelijk boven bij .xschermlezerx #skippy heeft gekregen, zodat skip-link en <div> netjes boven elkaar staan.
border-top-width: 3px;
Bij #uitleg-tab, #uitleg-schermlezer heeft deze <div> aan alle kanten een rode border van 1 px breed gekregen. Hier wordt de border aan de bovenkant 3 px breed gemaakt. Dat heeft te maken met de boven de <div> staande skip-link.
Deze <div> staat tegen de skip-link erboven aan. <div> en skip-link moeten netjes op elkaar aansluiten. Als de <div> te hoog komt te staan, dekt deze een deel van de skip-link af. De <div> staat later in de html en komt daarom boven de skip-link te staan. Maar als de <div> te laag komt te staan, heb je 'n lelijke kier tussen <div> en skip-link.
Dit blijkt onverwacht lastig in alle browsers op alle systemen goed voor elkaar te krijgen. Door de <div> aan de bovenkant een even brede border te geven als de skip-link, kan de <div> iets te hoog worden neergezet, zodat je geen kier krijgt. En toch de border van 3 px breed aan de onderkant van de skip-link ziet (hoewel dat dus in werkelijkheid de border aan de bovenkant van deze <div> is).
Dat de <div> eigenlijk iets te hoog staat, is met het blote oog niet te zien. Afdeling list en bedrog dus, maar het werkt wel. En in dit geval hebben list en bedrog geen vervelende bijwerkingen, wat bij bijvoorbeeld het gaswinningsbeleid van Wiebes anders ligt. (Ik heb 'Wiebes' en 'fatsoenlijk' ooit in één zin genoemd, terwijl de man al minister was. En ik was nog nuchter ook. Dit blijft een levenslang trauma.)
top: 3rem;
Op deze hoogte sluit de <div> goed op de skip-link erboven aan.
Als eenheid wordt de relatieve eenheid rem gebruikt, omdat bij een absolute eenheid zoals px de afstand tot de bovenkant niet mee verandert met de lettergrootte.
De rem werkt ongeveer hetzelfde als de bekendere eenheid em. Ook de rem is op de lettergrootte gebaseerd, maar altijd op de lettergrootte van <html>. Daardoor is de rem altijd overal even groot, ongeacht de lettergrootte van het element zelf. En ongeacht eventuele afwijkende lettergroottes in voorouders van het element. Als de bezoeker de lettergrootte verandert, wordt in feite de lettergrootte van <html> veranderd. Hierdoor verandert ook de grootte van de rem, maar die verandering is overal op de pagina precies hetzelfde.
Omdat de meeste maten in het menu als eenheid de rem gebruiken, blijft de lay-out van het menu ook intact, als de bezoeker de lettergrootte verandert.
Als twee of meer elementen ten opzichte van elkaar neergezet moeten worden, een bepaalde grootte ten opzichte van elkaar moeten hebben, en dergelijke, is de eenheid rem vrijwel altijd beter dan de eenheid em. (Even los van nieuwere mogelijkheden als flexbox en grid, maar die worden hier niet gebruikt.)
left: 15rem;
Zelfde afstand tot links geven als de boven deze <div> staande skip-link hier gelijk boven bij .xschermlezerx #skippy heeft gekregen, zodat skip-link en <div> netjes boven elkaar staan.
.xschermlezerx nav > 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. (Alleen wat binnen deze media query geldig is, wordt binnen dit blokje herhaald.)
css bij selectors die beginnen met #checkbox-open-menu:not(:checked) is weggelaten, want als class 'xschermlezerx' aanwezig is bij <body>, is input#checkbox-open-menu met behulp van JavaScript aangevinkt.
css bij selectors die beginnen met #checkbox-open-menu:not(:checked) is weggelaten, want .xschermlezerx kan alleen aanwezig zijn, als #checkbox-open-menu is aangevinkt.
nav ul {height: 0; overflow: hidden; list-style-type: none; margin: 0; padding: 0;}
De <ul> die een direct kind is van een <nav>, die binnen een element met class="xschermlezerx' zit. Er is maar één <ul> een direct kind van de <nav>, waar het hele uitklapmenu in zit. Hoe de selector nav > ul precies werkt, is te vinden bij nav > ul.
De class 'xschermlezerx' wordt door JavaScript aan <body> toegevoegd, als is gekozen om het menu voor schermlezers te openen. Er is meer over te lezen bij .xschermlezerx #menubalk-voor-smal.
position: absolute;
Om de buitenste <ul>, en daarmee het hele menu, op de juiste plaats neer te kunnen zetten.
Er wordt gepositioneerd ten opzichte van de eerste voorouder die een andere positie dan statisch heeft. Als die er niet is, zoals hier het geval is, wordt gepositioneerd ten opzichte van het venster van de browser.
Het menu komt dus tegen de bovenkant van het browservenster aan te staan.
top: 0;
Door de bij nav > ul opgegeven top: 1px; ontstaat hier een klein kiertje aan de bovenkant van het menu. Dit corrigeert dat.
.xschermlezerx main
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.)
css bij selectors die beginnen met #checkbox-open-menu:not(:checked) is weggelaten, want als class 'xschermlezerx' aanwezig is bij <body>, is input#checkbox-open-menu met behulp van JavaScript aangevinkt.
Het element <main> dat element met class="xschermlezerx' zit. Er is maar één <main>, daarin zit de belangrijkste inhoud van de pagina. (Hier is dat alleen Latijnse flauwekultekst.)
De class 'xschermlezerx' wordt door JavaScript aan <body> toegevoegd, als is gekozen om het menu voor schermlezers te openen. Er is meer over te lezen bij .xschermlezerx #menubalk-voor-smal.
width: auto; max-width: 760px;
De eerder bij main opgegeven breedte van 760 px wordt veranderd in een maximumbreedte van 760 px.
Omdat het menu voor schermlezers naast de tekst en dergelijke in <main> komt te staan, wordt daarvoor hieronder met padding aan de linkerkant ruimte gemaakt. Daardoor is er minder ruimte voor <main>.
Deze css geldt voor browservensters minimaal 760 px breed. De padding hieronder wordt van die breedte afgetrokken, dus <main> kan nu nooit breder worden dan het venster.
padding: 3rem 0 0 15.5rem;
Padding van 3 rem aan de bovenkant, zodat <main> met inhoud onder de skip-link en uitleg voor de schermlezer komt te staan.
(Bij #checkbox-open-menu:checked ~ nav is de fixed positie van de <nav> veranderd in een statische. Hierdoor wordt de <nav> niet meer door andere elementen, zoals de <main>, genegeerd. Bij .xschermlezerx nav heeft de <nav> een marge aan de bovenkant gekregen van 7,3 rem. Omdat de <nav> nu niet meer wordt genegeerd, wordt ook <main> 7,3 rem omlaag gezet. Samen met de hier gegeven 3 rem padding is dat genoeg om onder uitleg en skip-link te komen staan.)
Rechts en links geen padding.
Links een padding van 15,5 rem. Bij nav > ul > li zijn de <li>'s in de buitenste <ul> 15 rem breed gemaakt. Die bepalen hier de breedte van het menu. Als <main> links een padding van 15,5 rem krijgt, kan de inhoud van <main> nooit onder het menu komen te staan en daardoor niet meer te zien zijn.
Omdat <main> bij main horizontaal is gecentreerd en iets hierboven een maximumbreedte van 760 px heeft gekregen, ontstaat er in bredere browservensters een ruimte tussen het menu links en de horizontaal gecentreerde <main>. Dat zal echter geen probleem zijn. Schermlezers lezen alles gewoon voor, en de tekst kan ook gewoon worden vergroot.
Als eenheid wordt de relatieve eenheid rem gebruikt, omdat bij een absolute eenheid zoals px de grootte van de padding niet mee verandert met de lettergrootte.
De rem werkt ongeveer hetzelfde als de bekendere eenheid em. Ook de rem is op de lettergrootte gebaseerd, maar altijd op de lettergrootte van <html>. Daardoor is de rem altijd overal even groot, ongeacht de lettergrootte van het element zelf. En ongeacht eventuele afwijkende lettergroottes in voorouders van het element. Als de bezoeker de lettergrootte verandert, wordt in feite de lettergrootte van <html> veranderd. Hierdoor verandert ook de grootte van de rem, maar die verandering is overal op de pagina precies hetzelfde.
Omdat de meeste maten in het menu als eenheid de rem gebruiken, blijft de lay-out van het menu ook intact, als de bezoeker de lettergrootte verandert.
Als twee of meer elementen ten opzichte van elkaar neergezet moeten worden, een bepaalde grootte ten opzichte van elkaar moeten hebben, en dergelijke, is de eenheid rem vrijwel altijd beter dan de eenheid em. (Even los van nieuwere mogelijkheden als flexbox en grid, maar die worden hier niet gebruikt.)
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. Alle niet-essentiële code is bruin. (In de inhoudsopgave staat alles in een gewone letter vanwege de leesbaarheid.)
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.
Als je onderstaande code ergens aanraakt of ‑klikt, ga je rechtstreeks naar de bijbehorende uitleg.
Als je bovenstaande code ergens aanraakt of ‑klikt, ga je rechtstreeks naar de bijbehorende uitleg.
In dit script worden een aantal zaken geregeld.
Een klein stukje script om het menu te laten openen op iOS 9 (en mogelijk iOS 10, maar daar wordt niet op getest. iOS 11 heeft het niet nodig).
Het menu toegankelijker maken voor gebruikers van de Tab-toets. Zonder JavaScript werkt het ook, maar allerlei sneltoetsen werken dan niet.
Het menu toegankelijker maken voor schermlezers. Dit werkt niet zonder JavaScript.
Omdat het om een iets groter script gaat, is dit in een apart bestand gezet: menu-035-dl.js in de map 035-js-dl. In deze map zit ook het bestand menu-035-dl-met-commentaar.js. Dit is precies hetzelfde script, maar dan met beknopt commentaar erbij.
/* menu-035-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: 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 in 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.
;: met de puntkomma wordt in JavaScript een regel afgesloten. Het is te vergelijken met een gewone punt in een tekst.
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 ). 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 gelijk uitgevoerd. Maar dat brengt belangrijke risico's met zich mee. In dit script worden namen voor variabelen gebruikt als 'uitklapMenu' en 'len'. 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.
Een klein aantal oudere browsers ondersteunt dit niet, maar die hebben er verder geen last van, omdat ze de regel gewoon negeren.
var body = document.getElementsByTagName("body")[0],
In dit deel van de code worden een paar dingen voorbereid. Het écht uitvoerende deel van het script, waarin bijvoorbeeld een toetsaanslag daadwerkelijk wordt herkend, volgt later.
Het is gebruikelijk dit soort voorbereidende zaken bovenin het script te zetten.
Deze regel valt in een aantal delen uiteen, die worden gescheiden door een komma. Het eerste deel begint met var. Dit sleutelwoord var wordt automatisch ook voor de andere delen geplaatst, omdat die delen op een komma volgen. Door die komma weet het script dat het hier om bij elkaar horende delen van één regel gaat.
Na het laatste deel staat een puntkomma. Dit is het echte einde van deze regel code. In gewone tekst zou je hier een punt gebruiken.
Het is gebruikelijk zo'n tweede, derde, ... deel op een nieuwe regel te laten beginnen en iets in te laten springen. Zo zie je in één oogopslag dat het sleutelwoord var voor alle delen geldt, dat hier dertien variabelen worden aangemaakt: in elke regel één.
Pardon? Variwiewatwaar? Ha, leuk dat je het vraagt.
var: het begin van het eerste deel van de regel. Met het sleutelwoord var wordt aangegeven dat elk van de erop volgende woorden de naam van een 'variabele' is. Een variabele is een soort portemonnee: er kan van alles in zitten, en de inhoud kan veranderen.
Gelijk na var volgen de namen van de variabelen. Als er meerdere variabelen zijn, zoals hier het geval is, worden die gescheiden door een komma. Hier zijn de variabelen 'body', 'laatsteItem', 'i', 'voorTab', 'uitklapMenu', enzovoort, tot en met 'inputsVoorSmal'. Elke variabele staat óf helemaal alleen op een eigen regel, óf aan het begin van een regel, gelijk gevolgd door een isgelijkteken.
In 'body', 'laatsteItem', 'i', enzovoort wordt dus iets opgeborgen. Omdat de variabele een naam heeft, kan de rest van het script de variabele aanroepen 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.
Wat er precies in die variabelen wordt opgeslagen, kun je hier opgeven of elders in het script. Als het om iets simpels gaat, wordt het hier opgegeven. Als het om iets meer ingewikkelds gaat, of als nog niet bekend is wat moet worden opgeslagen, gebeurt het later.
(Heel vaak zou je in plaats van iets opbergen in deze variabelen iets elke keer dat het nodig is opnieuw kunnen opzoeken. Zo wordt hieronder <body> opgezocht met document.getElementsByTagName("body")[0] en opgeslagen in de variabele 'body'. Je zou ook elke keer <body> opnieuw kunnen opzoeken. Maar dat is een bijzonder slecht idee. De code wordt veel minder leesbaar en vooral: dat opzoeken kost relatief veel tijd. Daarom kun je vaak beter iets één keer opzoeken en het resultaat van die speurtocht opslaan en gebruiken.)
De variabelen krijgen hier hun naam, ze worden hier 'gedeclareerd' of 'aangemaakt'. Dat 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 het script aan te maken, maar dat is wel overzichtelijker.
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: 'inputsVoorSmal'. In css zou je dit kunnen schrijven als 'inputs-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...).
Omdat deze variabelen helemaal bovenaan de bij (function () { beschreven buitenste functie al worden aangemaakt, kunnen deze variabelen overal binnen die functie worden gebruikt. En omdat dit hele script binnen die functie staat, kunnen ze overal in het script worden gebruikt. Later worden ook nog variabelen aangemaakt, die maar in delen van het script zijn te gebruiken.
Hieronder worden de aangemaakte variabelen één voor één doorgenomen.
body = document.getElementsByTagName("body")[0],
body: dit is de variabele, waarin iets moet worden opgeborgen.
=: 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.getElementsByTagName("body"): het middelste stukje getElementsByTagName 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 getElementsByTagName() uit document kan JavaScript alle elementen van 'n bepaalde soort, zoals alle <div>'s, opzoeken. Het element waarnaar wordt gezocht, staat tussen haakjes: getElementsByTagName("body")
Er wordt gezocht naar de tags <body>. Jij en ik weten dat er maar één <body> is, maar een browser is nou eenmaal niet zo slim, dus die zoekt trouwhartig naar álle <body>'s. En vindt er uiteraard maar eentje.
[0]: de gevonden tags worden geretourneerd in de vorm van een lijst. Als er bijvoorbeeld honderd <div>'s zouden worden gevonden, heeft deze lijst honderd items. Zo'n item kun je aanspreken door het volgnummer te gebruiken. En omdat computers dol zijn op '0', is het volgnummer van het eerste item '0', en geen '1'.
Uiteraard is er maar één <body> gevonden. Het volgnummer van die <body> is dus '0', het eerste item.
document.getElementsByTagName("body")[0]: samen levert dit het element <body> op.
,: de regel eindigt hier niet, er moeten nog meer variabelen worden aangemaakt.
De hele declaratie: sla in variabele body het element <body> op. Dat wil zeggen dat in variabele body een ongelooflijke hoeveelheid informatie over <body> wordt gestopt, waar het script later gebruik van kan maken. Zo zit bijvoorbeeld alle css, die aan <body> is gegeven, ook in body. Maar niet alleen de in de stylesheet opgegeven css, ook alle standaardwaarden, en ook alle css die van <html> wordt geërfd. Alle kinderen van <body> en hun css, attributen, enzovoort, zitten ook in body. Van al deze informatie in body kan het script gebruik maken. En ook kunnen veel van deze dingen worden veranderd door het script, zoals een andere kleur, of een element verwijderen, of juist toevoegen.
Feitelijk is ook <body> in de vorm van een object in body opgeslagen. Naast allerlei informatie die in body wordt opgeslagen, kun je daardoor ook gebruik maken van allerlei methoden (functies), die JavaScript gratis en voor niets toevoegt aan body.
Het script gebruikt body om bepaalde classes toe te voegen en te verwijderen bij <body>, om te luisteren of bepaalde toetsen worden ingedrukt, en om te zorgen dat het menu kan worden geopend op iOS 9.
laatsteItem,
In deze variabele wordt het laatste item van het menu opgeslagen. Omdat dit wat lastiger is te vinden dan iets simpels als <body> hierboven, wordt dit pas later opgezocht.
Deze variabele wordt op een aantal plaatsen als teller gebruikt. Aan het begin van een berekening wordt er 'n getal in gestopt. Het is gebruikelijk om dit soort tellers de naam 'i' te geven. Dat soort gewoontes maakt het makkelijker code van anderen te lezen. Hier wordt de variabele alleen aangemaakt, er wordt pas iets in gestopt, als er geteld moet worden.
=: 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.getElementById("voor-tab"): het middelste stukje getElementById 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 getElementById() uit document kan JavaScript een element met een bepaalde id opzoeken. De id waarnaar wordt gezocht, staat tussen haakjes:
getElementById("voor-tab")
Er wordt gezocht naar het element met id="voor-tab", oftewel: div#voor-tab. Dit is een <a> met informatie voor gebruikers van de Tab-toets.
,: de regel eindigt hier niet, er moeten nog meer variabelen worden aangemaakt.
De hele regel: sla in variabele voorTab het element met id 'voor-tab' (a#voor-tab) op. Dat wil zeggen dat in variabele voorTab een ongelooflijke hoeveelheid informatie over a#voor-tab wordt gestopt, waar het script later gebruik van kan maken. Zo zit bijvoorbeeld de bestemming van de link (de waarde van href) ook in voorTab. En alle css die aan a#voor-tab is gegeven. 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 a#voor-tab en hun css, attributen, enzovoort, zitten ook in voorTab. Van al deze informatie in voorTab kan het script gebruik maken. En ook kunnen veel van deze dingen worden veranderd door het script, zoals een andere kleur, of een element verwijderen, of juist toevoegen.
Feitelijk is ook a#voor-tab in de vorm van een object in voorTab opgeslagen. Naast allerlei informatie die in voorTab wordt opgeslagen, kun je daardoor ook gebruik maken van allerlei methoden (functies), die JavaScript gratis en voor niets toevoegt aan voorTab.
Als je in de html een andere id dan 'voor-tab' wilt gebruiken, moet je die in deze regel ook veranderen. Dit is de enige plaats in het script, waar je dit moet veranderen.
Het script gebruikt voorTab om te luisteren, of een bepaalde toets is ingedrukt als a#voor-tabfocus heeft.
Het verhaal voor variabele uitklapMenu is precies hetzelfde als dat hierboven voor voorTab, alleen gaat het nu om het element met id="uitklapmenu": nav#uitklapmenu. De <nav> met het uitklapmenu wordt in de vorm van een object opgeborgen in variabele uitklapMenu.
Als je in de html een andere id dan 'uitklapmenu' wilt gebruiken, moet je die in deze regel ook veranderen. Dit is de enige plaats in het script, waar je dit moet veranderen.
Het script gebruikt uitklapMenu om te luisteren, welke toetsen worden ingedrukt binnen de <nav>. Hierdoor kunnen gebruikers van de Tab-toets met behulp van bepaalde toetsen sneller door het menu navigeren.
Ook het verhaal voor variabelen checkboxSchermlezers1 en checkboxSchermlezers2 is precies hetzelfde als dat iets hierboven voor voorTab, alleen gaat het nu om deze twee variabelen en de elementen met id="checkbox-voor-schermlezers-1" en "checkbox-voor-schermlezers-2". Dit zijn twee <input type="checkbox">'s, waarmee de bezoeker het menu voor schermlezers toegankelijk kan maken. In de in deze twee variabelen opgeslagen objecten zit onder andere, of de <input>'s zijn aangevinkt of niet.
Als je in de html andere id's dan 'checkbox-voor-schermlezers-1' of 'checkbox-voor-schermlezers-2' wilt gebruiken, moet je die in deze regels ook veranderen. Dit is de enige plaats in het script, waar je dit moet veranderen.
Als deze twee <input>'s worden aan- of uitgevinkt, regelt het script het tonen of verbergen van het menu voor schermlezers, het plaatsen en verwijderen van class 'xschermlezerx' bij <body>, en gedeeltelijk het aan- en uitvinken van deze <input>'s.
Weer hetzelfde verhaal als iets hierboven voor voorTab, maar nu wordt input#checkbox-open-menu als object in variabele openMenu opgeborgen.
Als je in de html een andere id dan 'checkbox-open-menu' wilt gebruiken, moet je die in deze regel ook veranderen. Dit is de enige plaats in het script, waar je dit moet veranderen.
Het aan- en uitvinken van input#checkbox-open-menu wordt grotendeels door het script gedaan. Als #checkbox-open-men is aangevinkt, wordt het menu voor gebruikers van Tab-toets of schermlezers getoond.
content = document.getElementById("content"),
Het wordt eentonig, maar ook bij variabele content wordt op dezelfde manier als iets hierboven bij voorTab het element met id="content" opgezocht: main#content. In <main> zit de belangrijkste inhoud van de pagina (hier is dat Latijnse flauwekultekst).
Als je in de html een andere id dan 'content' wilt gebruiken, moet je die in deze regel ook veranderen. Dit is de enige plaats in het script, waar je dit moet veranderen.
Als gebruikers van de Tab-toets het menu in één keer willen passeren, of als ze het geopende menu willen sluiten, gebruikt het script content om de focus naar <main> te verplaatsen.
=: 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.querySelector("nav ul li"): het middelste stukje querySelector() 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, daarom heet een functie uit een object een '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 het eerste element worden gevonden dat bij een bepaalde selector hoort. Het element waarnaar wordt gezocht, staat tussen haakjes:
querySelector("nav ul li")
Er wordt gezocht naar een <li> binnen een <ul> binnen een <nav>. Het selectiecriterium tussen de haakjes schrijf je op precies dezelfde manier als een css-selector, alleen komt het tussen aanhalingstekens te staan. (Anders weet JavaScript niet dat het om een letterlijk stukje tekst gaat.) Zou je willen zoeken naar het eerste element met class="marietje", dan zou je querySelector(".marietje") gebruiken.
Het eerste element dat aan de selector nav ul li voldoet is per definitie het eerste item in het menu. Als er later eventueel een andere <li> voor deze eerste <li> wordt ingevoegd, wordt dat automatisch de eerste <li>.
(Je kunt ook zoeken naar álle elementen die aan een selector voldoen zoeken. Dat gebeurt iets verderop bij inputsVoorSmal met querySelectorAll().)
,: de regel eindigt hier niet, er moeten nog meer variabelen worden aangemaakt.
De hele regel: sla in eersteItem het eerste element op dat aan selector nav ul li voldoet. Dat wil zeggen dat in variabele eersteItem een ongelooflijke hoeveelheid informatie over die eerste <li> wordt gestopt, waar het script later gebruik van kan maken. Zo zit bijvoorbeeld alle css, die aan de eerste <li> is gegeven, ook in eersteItem. 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 eerste <li> en hun css, attributen, enzovoort, zitten ook in eersteItem. Van al deze informatie in eersteItem 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 ook die eerste <li> in de vorm van een object in eersteItem opgeslagen. Naast allerlei informatie die in eersteItem wordt opgeslagen, kun je daardoor ook gebruik maken van allerlei methoden (functies), die JavaScript gratis en voor niets toevoegt aan eersteItem.
Het script gebruikt eersteItem om de focus op het eerste item in het menu te zetten.
De inhoud voor variabele checkboxVoorSmal wordt weer op dezelfde manier opgezocht als iets hoger bij voorTab = document.getElementById("voor-tab"),. Het gaat hier om het element met id="checkbox-voor-smal": input#checkbox-voor-smal. In browservensters smaller dan 760 px wordt deze <input> aangevinkt, als het menu moet worden geopend.
Als je in de html een andere id dan 'checkbox-voor-smal' wilt gebruiken, moet je die in deze regel ook veranderen. Dit is de enige plaats in het script, waar je dit moet veranderen.
Als in browservensters smaller dan 760 px de bezoeker het menu voor schermlezers wil openen, vinkt het script input#checkbox-voor-smal aan.
len,
Deze variabele wordt op een aantal plaatsen gebruikt om er de lengte van iets in op te bergen. Hier wordt de variabele alleen aangemaakt, er wordt nog niets in gestopt.
=: 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.
uitklapMenu.querySelectorAll("input[type='checkbox']"): in uitklapMenu is bij uitklapMenu = document.getElementById("uitklapmenu"), in de vorm van een object nav#uitklapmenu opgeslagen. Een object is een bij elkaar horende verzameling van functies en andere code.
Een functie is een stukje bij elkaar horende code, waarmee je iets kunt doen. Bij een object werkt een functie alleen een klein beetje anders dan een gewone functie, daarom heet een functie uit een object een 'methode'. JavaScript heeft een groot aantal ingebakken objecten, waar je gebruik van kunt maken. Ook in het object in uitklapMenu zit een aantal methodes, waarvan er hier eentje gebruikt wordt.
Het middelste stukje querySelectorAll() is zo'n methode. De werking van deze methode moet beperkt blijven tot de <nav> met het menu. Daarom staat voor de punt uitklapMenu de werking van deze methode blijft nu beperkt tot nav#uitklapMenu.
Met de methode querySelectorAll() worden alle elementen gevonden, die bij een bepaalde selector horen. De elementen waarnaar wordt gezocht, staan tussen haakjes:
querySelectorAll("input[type='checkbox']")
Er wordt gezocht naar alle <input type="checkbox">'s. Het selectiecriterium tussen de haakjes schrijf je op precies dezelfde manier als een css-selector, alleen komt het tussen aanhalingstekens te staan. (Anders weet JavaScript niet dat het om een letterlijk stukje tekst gaat.) Zou je willen zoeken naar alle elementen met class="marietje", dan zou je querySelectorAll(".marietje") gebruiken.
;: de regel waarin variabelen worden aangemaakt, eindigt hier. Aan het eind van de eerdere regels, waarin een variabele werd aangemaakt, stond steeds een komma: er moest nog een variabele worden aangemaakt. De puntkomma geeft het definitie einde van de regel aan, er hoeven geen verder variabelen te worden aangemaakt.
De hele regel: sla alle <input>'s binnen het uitklapmenu, 32 in totaal, op in variabele inputsVoorSmal. Dat wil zeggen dat in variabele inputsVoorsmal een ongelooflijke hoeveelheid informatie over elke <input> wordt gestopt, waar het script later gebruik van kan maken. Zo zit bijvoorbeeld alle css, die aan een <input> is gegeven, ook in inputsVoorSmal. 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 elke <input> en hun css, attributen, enzovoort, zitten ook in inputsVoorSmal.
Van al deze informatie in inputsVoorSmal 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 elke <input> in de vorm van een object in inputsVoorSmal opgeslagen. Er zitten dus eigenlijk 32 objecten in inputsVoorSmal. Naast allerlei informatie die in elk object in inputsVoorSmal wordt opgeslagen, kun je daardoor ook gebruik maken van allerlei methoden, die JavaScript gratis en voor niets toevoegt aan de objecten in inputsVoorSmal.
Tot nu toe werd in elke variabele steeds maar één element opgeslagen. Hier worden er 32 in opgeslagen. Om in een variabele 32 elementen te kunnen bewaren, worden ze bewaard in de vorm van een lijst. Elk item in die lijst is dan één van de 32 objecten, met allerlei informatie en dergelijke over die ene <input>, die bij het betreffende object hoort.
Om een bepaald object te kunnen vinden, wordt een volgnummer gebruikt. Omdat een computer dol is op het getal 0, heeft het eerste object als volgnummer 0: inputsVoorSmal[0]. Tussen de teksthaken staat het volgnummer. Het tiende object kan het script vinden met inputsVoorSmal[9]. Enzovoort.
(Dat die nummering begint met 0 is even wennen, maar dit is heel gebruikelijk in computertalen. En je went er snel aan. Het heeft een groot aantal voordelen boven de voor mensen gebruikelijke manier om een nummering met 1 te beginnen.)
Als in browservensters smaller dan 760 px het menu voor schermlezers wordt geopend, vinkt het script alle 32 <input>'s' aan, zodat het menu volledig wordt geopend.
Tot nu toe is alleen een aantal noodzakelijke gegevens opgevraagd en opgeborgen. Vanaf nu gaat het script echt beginnen.
body.classList.remove("xgeen-jsx");
body: bij body = document.getElementsByTagName("body")[0], is het element <body> in de vorm van een object opgeslagen in variabele body. Hierdoor is allerlei informatie uit <body> toegankelijk voor het script, en kan het script allerlei dingen met <body> doen.
classList: dit is zo'n stukje informatie uit body. In classList zitten alle classes die bij het element <body> aanwezig zijn.
remove("xgeen-jsx"): met methode remove() kan een class worden verwijderd uit classList. Deze class wordt ook daadwerkelijk verwijderd uit de html. Bij het maken van de pagina is aan het element <body> class 'xgeen-jsx' toegevoegd. Die class wordt hier door het script verwijderd. Dat kan alleen, als JavaScript aanstaat. Oftewel: een selector in de css die met .xgeen-jsx begint, werkt alleen, als JavaScript uitstaat.
;: met de puntkomma wordt aangegeven dat de regel hier eindigt. In gewone tekst zou je hier een punt gebruiken.
'xgeen-jsx' is een tamelijk idiote naam. Dit maakt de kans kleiner dat deze class al in gebruik is. Als je de naam van de class in de html (en de css) wilt veranderen, moet je dat ook in de regel hierboven doen.
In de css bij dit voorbeeld beginnen twee selectors met .xgeen-jsx. Met die selectors worden meldingen zichtbaar gemaakt die van belang zijn, als JavaScript uitstaat.
Het door het script verwijderen van 'xgeen-jsx' is alleen te zien in de Gegenereerde code, niet in de gewone code. Als om een of andere reden class 'xgeen-jsx' niet aanwezig is, vliegt je computer niet in de fik of zo. Er gebeurt dan gewoon helemaal niets.
Ook kun je gewoon andere classes of een id gebruiken, het script heeft hier geen enkele invloed op.
laatsteItem = "nav ul li:last-of-type";
Bij laatsteItem, is variabele laatsteItem aangemaakt, maar daar is toen verder niets mee gedaan, omdat het bepalen van de inhoud van deze variabele wat ingewikkelder is, dan het simpelweg opzoeken van een bepaalde id of zo.
Om te beginnen wordt in laatsteItem iets opgeslagen dat verdacht veel op een css-selector lijkt. Dat lijkt niet alleen zo, dat ís zo. Deze selector wordt iets hieronder bij querySelector() gebruikt.
nav ul li:last-of-type: de laatste <li> binnen een <ul> die weer binnen een <nav> zit. De selector staat tussen aanhalingstekens, zodat JavaScript dat het geen variabele of zo is, maar echt letterlijke tekst.
Er is een hele reeks <li>'s die binnen deze selector valt: elke laatste <li> in welke <ul> dan ook binnen de <nav> met het menu. Bij het uiteindelijke opslaan van het laatste item in het menu wordt echter gebruik gemaakt van methode querySelector(), en die slaat alleen het eerst gevonden element op. Waardoor er uiteindelijk toch maar één element gevonden gaat worden.
Als er verder niets zou gebeuren, wordt met deze selector de laatste <li> van de buitenste <ul> geselecteerd, de <li> met 'Kop 6'. Want dat is de eerste <li> die aan de selector nav ul li:last-of-type voldoet. Maar dat is niet het laatste item uit het menu, want deze <li> heeft een submenu, en dat submenu heeft mogelijk ook weer submenu's, die mogelijk ook weer submenu's hebben.
Daarom moet de selector worden aangepast.
Als het menu nooit zou worden aangepast, zou je de selector hier zo kunnen aanpassen dat deze precies het juiste item selecteert. ('hard coderen' heet dat.) Maar zodra je ergens 'n submenu toevoegt of verwijdert, kun je dan in de problemen komen.
In het voorbeeld wordt het laatste item uit het menu gevonden met de selector nav ul li:last-of-type ul li:last-of-type ul li:last-of-type ul li:last-of-type. Maar haal het laatste submenu bij Kop 6 - 6 - 5 weg, en de selector klopt niet meer. Er wordt dan niets gevonden en variabele laatsteItem blijft leeg.
Daarom wordt de selector gelijk hieronder samengesteld met behulp van JavaScript. Als het menu wordt gewijzigd, wordt de selector automatisch ook aangepast, zodat altijd het laatste item uit het menu wordt gevonden.
Het eerste deel van de selector is altijd hetzelfde: nav ul li:last-of-type. Dat kan dus gewoon hier worden opgegeven. De rest van de selector wordt gelijk hieronder opgebouwd.
for (i = 0; i < 3; i++) {
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. (In de echte code staat de laatste } lager, achter wat er waarmee moet gebeuren.)
Hier eerst het deel dat ervoor zorgt dat maximaal vier niveaus diep wordt gezocht. Er zijn maximaal vier niveaus, dus dieper zoeken is zonde van de tijd.
(: met dit haakje opent het deel dat ervoor zorgt, dat maximaal vier niveaus diep wordt gezocht. Aan het eind van de regel staat de bijbehorende ), waardoor het script weet dat dit het einde van dit deel van de code is.
i = 0;: 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. In dit geval is dat resultaat heel simpel: het is het getal 0.
De variabele i is al eerder aangemaakt bij i, en kan daarom hier nu gewoon worden gebruikt. Omdat i een variabele is, kan de waarde ervan veranderen, variëren. Wat later in de regel ook gaat gebeuren: elke keer als een niveau in het menu 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 z'n beginwaarde krijgt, hier eindigt.
i++: eerst het laatste deel, het middelste deel komt gelijk hieronder.
Elke keer als een niveau in het menu is afgehandeld, 1 optellen bij 'i'. Omdat programmeurs liederlijk lui zijn, wordt 'er 1 bij optellen' afgekort 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). En meer dan 3 wordt het niet, vanwege redenen die gelijk hieronder staan.
i < 3;: het middelste deel. Er zijn maximaal 4 niveaus in het uitklapmenu aanwezig. Het eerste niveau is altijd hetzelfde: nav ul li:last-of-type, dat hoeft niet meer te worden bekeken. Blijven er drie niveaus over. Daarom moet de code binnen de {} achter for drie keer worden uitgevoerd: één keer voor elk niveau.
i bevat een getal. Voordat het tweede niveau is verwerkt, is dat getal 0, want dat is hierboven bij i = 0; 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 mensentaal staat hier: zolang teller i kleiner is dan 3.
Als niet meer aan deze voorwaarde wordt voldaan, als teller i niet meer kleiner is dan 3, stop dan met het uitvoeren van de code die tussen de {} staat. En omdat 3 even groot is als het aantal niveaus in het menu dat moet worden bekeken, geeft dit een mogelijkheid om elk niveau één keer te verwerken. En er, als ze allemaal verwerkt zijn, mee te stoppen.
(Dit is mogelijk wat verwarrend. De computer begint, anders dan mensen, te tellen met 0. Dus als i achtereenvolgens 0, 1 en 2 is, wordt de code tussen de {} drie keer uitgevoerd. Ondanks dat wordt gestopt als i 3 is.)
): dit haakje hoort bij de ( gelijk achter for. Tussen deze twee haakjes staat de code, die ervoor zorgt dat elk niveau in het menu 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 niveau van het menu íé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 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 niveau van het menu 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. Waarmee i niet meer kleiner is dan 3, het aantal te verwerken niveaus (het eerste niveau is altijd hetzelfde en telt niet mee).
Omdat niet meer aan de voorwaarde i < 3 wordt voldaan, wordt gestopt met het bekijken van diepere niveaus. En dat komt goed uit, want alle niveaus zijn precies allemaal één keer aan de beurt gekomen. 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 de eerste keer wordt uitgevoerd. Herhaal de for zolang teller i lager is dan het aantal 3. Verhoog teller i elke keer als de for wordt uitgevoerd met 1.
if (document.querySelector(laatsteItem + " ul li:last-of-type")) {
Deze regel zit binnen een for-lus en wordt drie keer uitgevoerd.
Er zijn maximaal vier niveaus in het uitklapmenu, waarvan er één altijd hetzelfde is, dus er moeten maximaal drie niveaus worden bekeken. Maar er kunnen wel minder dan vier niveaus zijn. Daarom wordt eerst gekeken, of er nog wel een dieper niveau aanwezig is. Alleen als dat aanwezig, wordt de selector 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. Die voorwaarde is hier wat lang, het is het hele deel tussen het eerste en het laatste haakje achter de if.
document.querySelector(laatsteItem + " ul li:last-of-type"): het middelste stukje querySelector() is een zogenaamde 'methode'. Een methode is een stukje in de browser ingebakken code, waarmee je iets kunt doen. Deze methode 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, daarom heet een functie uit een object een '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 het eerste element worden gevonden dat bij een bepaalde selector hoort. Het element waarnaar wordt gezocht, staat tussen haakjes:
querySelector(laatsteItem + " ul li:last-of-type")
Het eerste deel tussen de haakjes laatsteItem staat niet tussen aanhalingstekens. Daardoor weet het script dat dit een variabele is en dat deze vervangen moet worden door de inhoud van de variabele. Die inhoud is iets hierboven bij laatsteItem = "nav ul li:last-of-type"; opgegeven. Als je de variabele door die inhoud vervangt, staat tussen de haakjes "nav ul li:last-of-type" + " ul li:last-of-type".
Omdat het om twee letterlijke teksten (een letterlijke tekst heet in JavaScript een 'string') gaat met een plus ertussen, mogen die teksten worden samengevoegd tot "nav ul li:last-of-type ul li:last-of-type". Voor dit samenvoegen is de spatie voor de tweede ul van belang, want zonder die spatie krijg je "nav ul li:last-of-type ulli:last-of-type", wat geen geldige css-selector is en dus wordt genegeerd.
Het eindresultaat is een doodgewone css-selector. Wel wat lang, maar het blijft een gewone selector:
nav ul li:last-of-type: de laatste <li> van een <ul> die weer in een <nav> zit. Dit is de laatste <li> van de buitenste <ul> in de <nav> met het uitklapmenu, de <li> met 'Kop 6'.
ul li:last-of-type: de laatste <li> die in een <ul> zit die weer een nakomeling is van de hierboven beschreven <li>. In het voorbeeld is dit de <li> met 'Kop 6 - 6'.
document.querySelector(laatsteItem + " ul li:last-of-type") zoekt dus naar een <ul> en <li> die een niveau dieper zitten dan de selector die al in laatsteItem zit. Omdat dit tussen de haakjes staat achter de if, is dit de voorwaarde voor de if: de code tussen de {} achter de if wordt alleen uitgevoerd, als die <li> een niveau dieper bestaat. Als die niet bestaat, is er dus geen dieper niveau meer. Althans: niet bij de laatste <li> van het submenu onder 'Kop 6'. (Of bij de laatste <li> van dat submenu, of bij de laatste <li> daar weer van.)
Omdat het om het laatste item in het uitklapmenu gaat, is steeds alleen de laatste <li> van belang. Ook al zou 'Kop 1' veertien submenu's hebben, daar kan nooit het laatste item uit het menu in zitten.
{: 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: als je achter de selector die al in laatsteItem zit " ul li:last-of-type" toevoegt, en er bestaat een element dat aan die selector voldoet, voer dan de code tussen de {} achter de if uit.
laatsteItem += " ul li:last-of-type";
Dit deel van het script wordt alleen uitgevoerd, als aan deze eerder gestelde voorwaarde is voldaan:
- Er is nog een dieper niveau aanwezig in het uitklapmenu.
Als aan de bij de if hierboven gestelde voorwaarde is voldaan, is er nog een niveau dieper aanwezig in het uitklapmenu. Als er nog 'n dieper niveau aanwezig is, kan de selector die querySelector() bij de if hierboven gebruikt langer worden gemaakt, zodat gekeken kan worden of er mogelijk nóg 'n dieper niveau aanwezig is.
In laatsteItem is al een selector aanwezig. In ieder geval zit daar al "nav ul li:last-of-type" in, zoals beschreven bij laatsteItem = "nav ul li:last-of-type";. Omdat de if waarbinnen deze regel staat zelf weer binnen de for-lus for (i = 0; i < 3; i++) { staat, wordt de if (en dus ook deze regel) drie keer uitgevoerd. Als het niet de eerste keer is dat deze regel wordt uitgevoerd, kan de selector in laatsteItem al langer zijn, omdat deze dan al 'ns is verlengd.
Hoe dan ook: de al in laatsteItem aanwezige selector wordt nu verlengd met " ul li:last-of-type". Waardoor gekeken kan worden, of in de tot nu toe gevonden laatste <ul> nog een <ul> met een laatste <li> aanwezig is.
laatsteItem: de variabele laatsteItem, waarin al een selector zit.
+=: dit is een verkorte manier (programmeurs zijn immers aartslui) om iets op te tellen bij laatsteItem. Omdat het om stuk letterlijke tekst (een 'string') gaat, is het eigenlijk geen optellen, maar samenvoegen. Zet wat na het isgelijkteken staat achter wat al in laatsteItem aanwezig is.
" ul li:last-of-type": dit wordt toegevoegd aan de al in laatsteItem aanwezige selector.
De hele regel in gewone mensentaal: als er nog een dieper niveau in het uitklapmenu aanwezig is, voeg dan de <ul> en de laatste <li> van dat diepere niveau toe aan de selector die al in laatsteItem zit.
Tot nu toe is hierboven de selector gevonden, waarmee het laatste item in het menu kan worden geselecteerd. JavaScript kan verder niets met zo'n selector. Wel met het element dat bij die selector hoort. Dat wordt hier opgeslagen in laatsteItem.
laatsteItem: dit is de variabele, waarin iets moet worden opgeborgen.
=: 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.querySelector(laatsteItem): het middelste stukje querySelector() 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, daarom heet een functie uit een object een '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 het eerste element worden gevonden dat bij een bepaalde selector hoort. Het element waarnaar wordt gezocht, staat tussen haakjes:
querySelector(laatsteItem)
Omdat laatsteItem niet tussen aanhalingstekens staat, weet het script dat geen letterlijke tekst is, maar een variabele of iets soortgelijks. Het script gebruikt daarom niet letterlijk 'laatsteItem', maar de inhoud van laatsteItem. Die inhoud is hierboven gevonden en is de selector, waarmee het laatste item uit het menu wordt geselecteerd.
De hele regel: sla in laatsteItem het eerste element op dat aan de eerder gevonden selector in variabele laatsteItem voldoet. Dat is de laatste <li> uit het menu. Dat wil zeggen dat in variabele laatsteItem een ongelooflijke hoeveelheid informatie over die laatste <li> wordt gestopt, waar het script later gebruik van kan maken. Zo zit bijvoorbeeld alle css, die aan de laatste <li> is gegeven, ook in laatsteItem. 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 eerste <li> en hun css, attributen, enzovoort, zitten ook in laatsteItem. Van al deze informatie in laatsteItem kan het script gebruik maken. En ook kunnen veel van deze dingen worden veranderd door het script, zoals een kleur wijzigen, of een element verwijderen, of juist toevoegen.
Feitelijk is ook die laatste <li> in de vorm van een object in laatsteItem opgeslagen. Naast allerlei informatie die in laatteItem wordt opgeslagen, kun je daardoor ook gebruik maken van allerlei methoden (functies), die JavaScript gratis en voor niets toevoegt aan laatsteIstem.
Het script gebruikt laatsteItem om de focus op het eerste item in het menu te zetten, als een gebruiker van de Tab-toets op het laatste item op de Tab-toets drukt. En omgekeerd: als op het eerste item in het menu op Shift+Tab wordt gedrukt, gaat de focus naar het laatste item.
body.addEventListener("touchstart", function () {return null;});
Zonder deze regel opent op iOS 9 het menu alleen in Firefox en Opera Mini en niet in Safari en UC Browser. Mogelijk is dit ook zo op iOS 10, maar daar wordt niet op getest.
iOS heeft nogal moeite met het omzetten van :hover en dergelijke naar aanrakingen op een touchscreen. Applefans zullen ongetwijfeld zeggen dat het veel beter werkt dan op andere systemen, maar normale mensen worden er knettergestoord van. (Mogelijk werkt dit op iOS 11 eindelijk beter, maar omdat Apple zo gesloten is als een oester, is dit onduidelijk. Dit voorbeeld werkt in ieder geval ook zonder JavaScript goed in iOS 11.)
body: bij body = document.getElementsByTagName("body")[0], is het element <body> in de vorm van een object opgeslagen in variabele body. Hierdoor is allerlei informatie uit <body> toegankelijk voor het script, en kan het script allerlei dingen met <body> doen.
addEventListener: voeg aan <body> een 'eventlistener' toe. Dit is een methode die luistert, of er iets gebeurt. Zo'n gebeurtenis kan een aanraking van het scherm zijn, een liefdevolle aai van de muis, een ram op het toetsenbord, van alles.
"touchstart": in dit geval wordt naar het begin van een aanraking van het touchscreen geluisterd. Dat is nog niet zo interessant, het wordt pas echt opwindend wat er ná die eerste, inleidende aanraking gaat gebeuren... Vinden zij elkaar, of wordt het moord- en doodslag?
function () {return null; }: Helaas, ze vinden elkaar niet én het wordt geen moord- en doodslag.
function () is gewoon een soort aankondiging dat er iets gaat gebeuren. Erachter tussen de accolades staat, wat er gaat gebeuren: return null;. Doe niets. Helemaal niets. Vanavond niet, schat, ik heb hoofdpijn.
Op een of andere manier is het touchscreen nu gevoelig gemaakt voor het aanraken van het menu, waardoor submenu's nu wel openen.
(Je zou mogelijk kunnen denken dat je deze eventlistener niet aan <body>, maar aan nav#uitklapmenu kunt koppelen. Uiteindelijk is het die <nav>, waarbinnen het menu zit. Als je dat doet, openen de submenu's inderdaad. Alleen krijg je dan het andere uiterste: ze sluiten niet meer. Door de eventlistener te koppelen aan <body>, kun je de submenu's zowel openen als sluiten. Openen door 'n pijltje aan te raken, sluiten door het scherm ergens buiten het menu aan te raken.)
voorTab.addEventListener("keydown", openVoorTab);
Als in browservensters minimaal 760 px breed na openen van de pagina de Tab-toets wordt ingedrukt, krijgt a#voor-tabfocus. Daardoor wordt een mededeling voor gebruikers van de Tab-toets zichtbaar. Deze regel zorgt ervoor dat het script weet, welke toets vervolgens wordt ingedrukt.
voorTab: bij voorTab = document.getElementById("voor-tab"), is het element a#voor-tab in de vorm van een object opgeslagen in variabele voorTab. Hierdoor is allerlei informatie uit a#voor-tab toegankelijk voor het script, en kan het script allerlei dingen met a#voor-tab doen.
addEventListener: er wordt een zogenaamde 'eventlistener' gekoppeld aan het voor de punt staande deel. Dat is hier voorTab, de a#voor-tab met informatie voor gebruikers van de Tab-toets.
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 'bel 112'.
"keydown": 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: 'keydown'. Er wordt geluisterd, welke toets wordt ingedrukt. Dit luisteren gebeurt, zolang a#voor-tabfocus heeft.
openVoorTab: deze naam staat niet tussen aanhalingstekens, omdat het hier niet om een letterlijke naam of zo gaat. De naam verwijst naar een 'functie', iets wat moet gebeuren. Die functie staat iets hieronder bij function openVoorTab (e) { en dwingt de bezoeker te kiezen tussen openen van het menu (Tab-toets), of het menu helemaal overslaan (Enter). Als een andere toets dan de Tab-toets of Enter wordt ingedrukt, wordt deze genegeerd.
(Probeer op dit moment vooral niet de logica van wel of geen aanhalingstekens te begrijpen. Het makkelijkste is om dat soort dingen maar gewoon te accepteren. Nederlands heeft ook zo z'n eigenaardigheden...)
;: aan het eind van elke regel staat een puntkomma. Daarmee geef je aan dat de regel is afgelopen. In een gewoon boek zou je hier een punt gebruiken, om aan te geven dat een nieuwe zin begint.
Als de bezoeker ervoor kiest het menu voor gebruik met de Tab-toets te openen, zorgt deze regel ervoor dat het script weet, welke toets wordt ingedrukt. Daardoor kunnen gebruikers van de Tab-toets gebruikmaken van een aantal toetsen, waarmee ze sneller door het menu kunnen navigeren. Zonder deze toetsen zouden ze 160 keer op de Tab-toets moeten drukken, om bij het laatste item in het menu aan te komen.
uitklapMenu: bij uitklapMenu = document.getElementById("uitklapmenu"), is het element nav#uitklapmenu in de vorm van een object opgeslagen in variabele uitklapMenu. Hierdoor is allerlei informatie uit nav#uitklapmenu toegankelijk voor het script, en kan het script allerlei dingen met nav#uitklapmenu doen.
addEventListener: er wordt een zogenaamde 'eventlistener' gekoppeld aan het voor de punt staande deel. Dat is hier uitklapMenu, de nav#uitklapmenu met het uitklapmenu.
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 'bel 112'.
"keydown": 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: 'keydown'. Er wordt geluisterd, welke toets wordt ingedrukt. Dit luisteren gebeurt, zolang een element binnen nav#uitklapmenufocus heeft.
(In het in uitklapMenu opgeslagen object zit niet alleen nav#uitklapmenu, maar ook alle nakomelingen daarvan. Daarom geldt deze eventlistener ook, als een toets wordt ingedrukt, terwijl bijvoorbeeld een link binnen nav#uitklapmenu focus heeft. Welk element binnen het menu precies focus had, op het ogenblik dat een toets werd ingedrukt, kan ook worden nagegaan.)
navigatieVoorTab: deze naam staat niet tussen aanhalingstekens, omdat het hier niet om een letterlijke naam of zo gaat. De naam verwijst naar een 'functie', iets wat moet gebeuren. Die functie staat iets hieronder bij function navigatieVoorTab (e) { en luistert, welke toets wordt ingedrukt. Met bepaalde toetsen kan snel door het menu worden genavigeerd.
;: aan het eind van elke regel staat een puntkomma. Daarmee geef je aan dat de regel is afgelopen. In een gewoon boek zou je hier een punt gebruiken, om aan te geven dat een nieuwe zin begint.
body.addEventListener("click", function () {
Als het menu voor gebruikers van de Tab-toets is geopend, kan het worden gesloten door op Escape te drukken. Een probleem is echter dat Escape alleen werkt, zolang een van de onderdelen van het menu focus heeft. Zodra het scherm buiten het menu wordt aangeraakt of -geklikt, komt de focus buiten het menu en werkt Escape niet meer. Hierdoor is het menu nauwelijks nog te sluiten.
Daarom wordt het menu ook gesloten, als het scherm buiten het menu wordt aangeraakt of -geklikt. Dit is trouwens hoe dan ook handig, want mensen zijn gewend een pop-up, uitklapmenu, en dergelijke op deze manier te sluiten.
body: bij body = document.getElementsByTagName("body")[0], is het element <body> in de vorm van een object opgeslagen in variabele body. Hierdoor is allerlei informatie uit <body> toegankelijk voor het script, en kan het script allerlei dingen met <body> doen.
addEventListener: er wordt een zogenaamde 'eventlistener' gekoppeld aan het voor de punt staande deel. Dat is hier body, het element <body>.
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 'bel 112'.
"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'. Er wordt geluisterd of ergens op <body> wordt geklikt, of dat <body> ergens wordt aangeraakt. (Toen dit werd bedacht, kon je alleen klikken, dus de naam 'click' loopt wat achter.)
function (): Vaak staat hier de naam van een functie, die moet worden uitgevoerd, als de eventlistener wordt geactiveerd (in dit geval door klikken of aanraken). In die functie staat dan de code die moet worden uitgevoerd.
Als het echter om heel weinig code gaat, is het simpeler om de code gelijk achter de eventlistener neer te zetten. Dat gebeurt hier. Dit heeft één nadeel: je kunt de functie niet aanroepen (de code erin uitvoeren) vanaf een andere plaats in het script, want de functie heeft geen naam. Het is een zogenaamde 'anonieme functie'. Maar dat elders aanroepen van deze functie gebeurt hier toch niet, dus dat mogelijke probleem speelt niet.
Achter het sleutelwoord function staan twee haakjes. 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. Hier wordt niets doorgegeven, want dat is niet nodig.
{: geeft het begin van de code binnen de functie aan. Aan het eind van de functie staat een bijbehorende }.
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.
body: bij body = document.getElementsByTagName("body")[0], is het element <body> in de vorm van een object opgeslagen in variabele body. Hierdoor is allerlei informatie uit <body> toegankelijk voor het script, en kan het script allerlei dingen met <body> doen.
classList: dit is zo'n stukje informatie uit body. In classList zitten alle classes die bij het element <body> aanwezig zijn.
contains("xschermlezerx"): met methode contains() kan worden gekeken, of een bepaalde class aanwezig is in classList. De class waarnaar wordt gezocht, staat tussen aanhalingstekens: 'xschermlezerx'.
Als de bezoeker heeft gekozen om het menu toegankelijk te maken voor schermlezers, heeft het script deze class bij <body> toegevoegd. Deze class is niet te zien in de gewone code, maar alleen in de Gegenereerde code.
Als je de naam van class 'xschermlezerx' wilt veranderen, moet je die in het script en in de css veranderen. Dit is één van de plaatsen in het script, waar die naam gewijzigd moet worden.
{: de code die wordt uitgevoerd, als aan deze if-voorwaarde wordt voldaan, wordt tussen 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: als bij <body> class 'xschermlezerx' aanwezig is, voer dan de code tussen de {} uit. Als dat niet het geval is, sla die code dan over en ga verder na de afsluitende }.
Dit deel van het script wordt alleen uitgevoerd, als aan deze eerder gestelde voorwaarde is voldaan:
- bij <body> moet class 'xschermlezerx' aanwezig zijn.
Met het sleutelwoord return wordt de functie afgebroken: de rest van de code wordt gewoon niet uitgevoerd. De ; geeft weer het eind van de regel aan.
Deze functie sluit het menu voor gebruik met de Tab-toets, als de pagina buiten het menu wordt aangeraakt of -geklikt. Het menu voor schermlezers is echter grotendeels hetzelfde als dat voor de Tab-toets, en dat moet niet gesloten worden bij klikken of aanraken buiten het menu.
Als het menu voor schermlezers is geopend, heeft het script class 'xschermlezerx' bij <body> aangebracht. Als die class aanwezig is bij <body>, is dus het menu voor schermlezers geopend. Door de functie dan gewoon af te breken met return, gebeurt er verder niets.
openMenu: bij openMenu = document.getElementById("checkbox-open-menu"), is het element input#checkbox-open-menu in de vorm van een object opgeslagen in variabele openMenu. Hierdoor is allerlei informatie uit input#checkbox-open-menu toegankelijk voor het script, en kan het script allerlei dingen met input#checkbox-open-menu doen.
checked: in de eigenschap checked is opgeslagen, of de <input> is aangevinkt of niet.
=: 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. In dit geval staat voor het isgelijkteken geen variabele, maar een eigenschap van het in openMenu opgeslagen object, dat bij input#checkbox-open-menu hoort. In dit geval wordt die eigenschap veranderd in wat achter het isgelijkteken staat: de <input> wordt aan- of uitgevinkt.
false: omdat dit achter het isgelijkteken staat, wordt dit de nieuwe waarde van de eigenschap checked van het object in openMenu, dat bij input#checkbox-open-menu hoort. Het resultaat hiervan is dat input#checkbox-open-menu wordt uitgevinkt.
Omdat in de css selectors met #checkbox-open-menu:checked nu niet meer werken, sluit het menu voor gebruikers van de Tab-toets.
uitklapMenu.addEventListener("click", function (e) {e.stopPropagation();});
In de anonieme functie body.addEventListener("click", function () { iets hoger is geregeld dat het menu voor gebruikers van de Tab-toets wordt gesloten, als het scherm buiten het menu wordt aangeraakt of -geklikt. Dat was een beetje gejokt.
Dat sluiten gebeurt door te luisteren naar een klik of aanraking ergens binnen <body>. Maar nav#uitklapmenu zit ook binnen <body>. Oftewel: niet alleen als je het scherm buiten het menu aanraakt of -klikt, maar ook als je bínnen het menu het scherm aanraakt of -klikt, sluit het menu.
Het betere sadisme: je ziet 'n menu met de mooiste links van je dromen, maar zodra je 'n link aanraakt of -klikt sluit het menu. Omdat sadisme om een of andere reden toch niet zo prettig voelt, wordt dit hier voorkomen.
uitklapMenu: bij uitklapMenu = document.getElementById("uitklapmenu"), is het element nav#uitklapmenu in de vorm van een object opgeslagen in variabele uitklapMenu. Hierdoor is allerlei informatie uit nav#uitklapmenu toegankelijk voor het script, en kan het script allerlei dingen met nav#uitklapmenu doen.
addEventListener: er wordt een zogenaamde 'eventlistener' gekoppeld aan het voor de punt staande deel. Dat is hier body, het element <body>.
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 'bel 112'.
"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'. Er wordt geluisterd of ergens op het in uitklapmenu opgeslagen object dat bij nav#uitklapmenu hoort, wordt geklikt, of dat dat wordt aangeraakt. (Toen dit werd bedacht, kon je alleen klikken, dus de naam 'click' loopt wat achter.)
Omdat in dit object ook alle nakomelingen van nav#uitklapmenu zitten, wordt ook geluisterd naar een klik of aanraking op een kop of link binnen het menu.
function: Vaak staat hier de naam van een functie, die wordt uitgevoerd, als de eventlistener wordt geactiveerd (in dit geval door klikken of aanraken). In die functie staat dan de code die moet worden uitgevoerd.
Als het echter om heel weinig code gaat, is het simpeler om de code gelijk achter de eventlistener neer te zetten. Dat gebeurt hier. Dit heeft één nadeel: je kunt de functie niet aanroepen (de code erin uitvoeren) vanaf een andere plaats in het script, want de functie heeft geen naam. Het is een zogenaamde 'anonieme functie'. Maar dat aanroepen elders van deze functie gebeurt hier toch niet, dus dat mogelijke probleem speelt niet.
(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 het klikken op of aanraken van een element binnen de <nav>. In e zit bijvoorbeeld de hoogte en breedte van de <nav>. En of toevallig ook Shift is ingedrukt. En nog 'n waanzinnige hoop andere informatie, waarvan het overgrote deel hier verder niet wordt gebruikt.
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 het aanraken of -klikken van de <nav>. 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 }.
e.stopPropagation(): e is het aan de code doorgegeven object, waarin onder andere zit, welk element is aangeraakt of -geklikt (en nog veel meer informatie, maar dat wordt hier allemaal niet gebruikt).
stopPropagation() is een methode die zorgt dat een klik of aanraking niet wordt doorgegeven aan de voorouders van het element. Dit voorkomt dat een klik of aanraking binnen het menu, wordt doorgegeven aan <body> en het menu gelijk sluit.
Nu blijft het uitklapmenu gewoon open, als een link wordt aangeraakt of -geklikt, zodat de link gevolgd kan worden.
;});: zijn nog wat afsluitende tekens die voor het script het einde van regels, code en functie aangeven.
Deze functie voorkomt, dat het aanraken of -klikken van de <nav> met het uitklapmenu of klik aan <body> wordt doorgegeven, waardoor het menu gelijk weer zou worden gesloten. Dit werkt ook voor nakomelingen van de <nav>, dus ook voor de links, kopregels, en dergelijke die binnen de <nav> zitten: de klik of aanraking 'stopt' binnen de <nav>.
body.addEventListener("keydown", function (e) {
Als het menu voor gebruikers van de Tab-toets is gesloten, is het heel lastig weer te openen. Om het menu te openen, moet je helemaal terug naar het begin van de pagina. Om daar weer te komen moet, als je pech hebt, meer dan 160 keer Shift+Tab worden ingedrukt (één keer voor elk item in het menu, en mogelijk vaker als er nog links en dergelijke op de rest van de pagina zitten.) Of je moet de Tab-toets zo vaak indrukken dat deze weer opnieuw bovenaan begint.
(Voor schermlezers speelt dit probleem niet, want die kunnen simpel naar het begin van de pagina teruggaan.)
Daarom wordt hier geregeld dat het menu altijd geopend kan worden met een speciale toetscombinatie. Dat is wat listig, want mogelijk is die toetscombinatie al in gebruik. Daarom is de combinatie Control+Alt+Shift+Home gekozen. Voor zover bekend is deze exotische combinatie nergens in gebruik. (Overigens moet die combinatie binnen de browser in gebruik zijn. Als die combinatie binnen bijvoorbeeld een tekstverwerker wordt gebruikt, bijt dat de browser niet.)
body: bij body = document.getElementsByTagName("body")[0], is het element <body> in de vorm van een object opgeslagen in variabele body. Hierdoor is allerlei informatie uit <body> toegankelijk voor het script, en kan het script allerlei dingen met <body> doen.
addEventListener: er wordt een zogenaamde 'eventlistener' gekoppeld aan het voor de punt staande deel. Dat is hier body, het element <body>.
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 'bel 112'.
"keydown": 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: 'keydown'. Er wordt geluisterd of een toets wordt ingedrukt. Omdat dit aan het in body opgeslagen object dat bij <body> hoort is gekoppeld, maakt het niet uit, waar op de pagina een toets wordt ingedrukt.
function: vaak staat hier de naam van een functie, die wordt uitgevoerd, als de eventlistener wordt geactiveerd (in dit geval het indrukken van een toets). In die functie staat dan de code die moet worden uitgevoerd.
Als het echter om heel weinig code gaat, is het simpeler om de code gelijk achter de eventlistener neer te zetten. Dat gebeurt hier. Dit heeft één nadeel: je kunt de functie niet aanroepen (de code erin uitvoeren) vanaf een andere plaats in het script, want de functie heeft geen naam. Het is een zogenaamde 'anonieme functie'. Maar dat aanroepen elders van deze functie gebeurt hier toch niet, dus dat mogelijke probleem speelt niet.
(e): 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 }.
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 haakjes achter de if.
Het deel tussen de haakjes is wat lang, maar het is samengesteld uit een aantal heel simpele voorwaarden.
ctrlKey is de naam voor de Control-toets. Als deze toets is ingedrukt, is ctrlKey waar en wordt aan dit stukje van de voorwaarde bij if voldaan.
&&: dit is de manier, waarop je in JavaScript 'en' aangeeft: wat voor de && staat moet waar zijn, én wat achter de && staat moet waar zijn.
e.altKey && e.shiftKey &&: hetzelfde verhaal als iets hierboven bij e.ctrlKey, maar nu voor de Alt- en de Shift-toets.
e.key: alleen sommige toetsen zoals Control, Alt en Shift hebben eigen namen. Andere toetsen moeten het met het meer algemene key doen. Daarin zit de naam van de betreffende toets. Als je 'n a indrukt is de waarde van key 'a', als je End indrukt 'End', enzovoort.
===: 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.
"Home": voor de drie isgelijktekens staat e.key. Erachter staat 'Home'. De inhoud van e.key moet exact 'Home' zijn, de naam van de Home-toets.
&&: weer het én-teken van JavaScript.
!openMenu.checked:
openMenu: bij openMenu = document.getElementById("checkbox-open-menu"), is het element input#checkbox-open-menu in de vorm van een object opgeslagen in variabele openMenu. Hierdoor is allerlei informatie uit input#checkbox-open-menu toegankelijk voor het script, en kan het script allerlei dingen met input#checkbox-open-menu doen.
checked: als de <input> is aangevinkt, is checked waar.
openMenu.checked wil dus zeggen: als input#checkbox-open-menu is aangevinkt.
!: dit betekent 'niet'. Omdat dit voor openMenu.checked staat, betekent dit dat de <input> juist niet aangevinkt mag zijn.
{: de code die wordt uitgevoerd, als aan deze if-voorwaarde wordt voldaan, wordt tussen code 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 voorwaarde bij elkaar: de Control-toets én de Alt-toets én de Shift-toets én de Home-toets moeten zijn ingedrukt én input#checkbox-open-menu mag niet zijn aangevinkt. Als aan één (of meer) van deze voorwaarden niet wordt voldaan, wordt de code tussen de {} achter de if niet uitgevoerd.
Als input#checkbox-open-menu is aangevinkt, is het menu voor gebruikers van de Tab-toets al geopend. Deze toetscombinatie toont de keuzemogelijkheid om het menu te openen of te passeren weer. Als het menu al is geopend, is dat overbodig en bovendien enorm storend, want de focus gaat dan weer naar a#voor-tab. Daarom moet de code tussen de {} achter de if niet worden uitgevoerd, als input#checkbox-open-menu is aangevinkt.
preventDefault(): dit is een van de ingebouwde methoden, die JavaScript gratis en voor niets toevoegt aan het hierboven genoemde object e.
Het zou kunnen dat de exotische toetscombinatie Control + Alt + Shift + Home toch al ergens in gebruik is op 'n manier, die botst met de toetscombinatie binnen het menu. Onwaarschijnlijk, maar Rutte is ook al tien jaar minister-president. Deze regel voorkomt elk mogelijk conflict. De standaardwerking van deze toetscombinatie wordt hier geblokkeerd. Dus áls deze combinatie bijvoorbeeld de geheime code zou zijn om het atoomkoffertje van Trump te activeren, dan wordt hiermee de Derde Wereldoorlog voorkomen.
;: de puntkomma geeft weer het einde van de regel aan.
Dit deel van het script wordt alleen uitgevoerd, als aan deze eerder gestelde voorwaarde is voldaan:
- Control én Alt én Shift én Home zijn ingedrukt én input#checkbox-open-menuis niet aangevinkt.
voorTab: bij voorTab = document.getElementById("voor-tab"), is het element a#voor-tab in de vorm van een object opgeslagen in variabele voorTab. Hierdoor is allerlei informatie uit a#voor-tab toegankelijk voor het script, en kan het script allerlei dingen met a#voor-tab doen.
focus(): dit is een van de ingebouwde methoden, die JavaScript gratis en voor niets toevoegt aan het hierboven genoemde object in variabele voorTab. Hiermee wordt de focus gegeven aan het bij dat object horende element a#voor-tab.
Met behulp van css is geregeld dat a#voor-tab zichtbaar wordt, als dit element de focus krijgt.
De toetscombinatie Control + Alt + Shift + Home zorgt er dus voor, dat deze <a> weer verschijnt. En omdat dit element het eerste is dat de gebruiker van de Tab-toets na openen van de pagina ziet, is het alsof de pagina opnieuw wordt geopend en kan de gebruiker van de Tab-toets weer kiezen tussen openen van het menu, of in één keer passeren van het menu.
;: de puntkomma geeft weer het einde van de regel aan.
checkboxSchermlezers1: bij checkboxSchermlezers1 = document.getElementById("checkbox-voor-schermlezers-1"), is het element input#checkbox-voor-schermlezers-1 in de vorm van een object opgeslagen in variabele checkboxSchermlezers1. Hierdoor is allerlei informatie uit input#checkbox-voor-schermlezers-1 toegankelijk voor het script, en kan het script allerlei dingen met input#checkbox-voor-schermlezers doen.
addEventListener: er wordt een zogenaamde 'eventlistener' gekoppeld aan het voor de punt staande deel. Dat is hier checkboxSchermlezers1, de input#checkbox-voor-schermlezers-1 die moet worden aangevinkt om het menu toegankelijk te maken voor schermlezers.
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 'bel 112'.
"change": 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: 'change'. Er wordt geluisterd, of input#checkbox-voor-schermlezers-1 wordt aan- of uitgevinkt ('veranderd').
voorSchermlezers1: deze naam staat niet tussen aanhalingstekens, omdat het hier niet om een letterlijke naam of zo gaat. De naam verwijst naar een 'functie', iets wat moet gebeuren. Die functie staat verderop bij function voorSchermlezers1 () { en regelt wat er moet gebeuren, als het eerste keuzevakje om het menu voor schermlezers toegankelijk te maken wordt aan- of uitgevinkt.
De bijbehorende variabele heet hier dan ook uiterst origineel checkboxSchermlezers2, en de bijbehorende functie die het aan- en uitvinken van input#checkbox-voor-schermlezers-2 afhandelt staat verderop bij function voorSchermlezers2 () {.
function openVoorTab (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 een toets wordt ingedrukt, terwijl a#voor-tabfocus heeft. Het luisteren naar de toets wordt geregeld bij voorTab.addEventListener("keydown", openVoorTab);.
De code in deze functie luistert, welke toets wordt ingedrukt. Als dat Enter is, wordt het menu overgeslagen. Als het de Tab-toets is, wordt het menu voor de Tab-toets geopend. Andere toetsen worden genegeerd: a#voor-tab houdt de focus, tot de Tab-toets of Enter is ingedrukt.
(Als iemand uit gewoonte de Tab-toets snel achter elkaar indrukt en onbedoeld het menu opent, is dat geen ramp. Met Escape kan het menu weer worden gesloten en wordt het alsnog gepasseerd.)
function: het sleutelwoord waarmee het begin van een functie wordt aangegeven.
openVoorTab: 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): 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 }.
switch: achter het sleutelwoord switch staat een variabele, waarin een bepaalde waarde is opgeslagen. Hier is dat e.key. Het mag ook een functie of zoiets zijn die iets uitrekent, als het eindresultaat maar een bepaalde waarde is, zoals een getal of een string (letterlijke tekst).
Op de switch volgt één of meer keer het sleutelwoord case: 'in geval dat', 'als'. Als de waarde die hier is gevonden hetzelfde is als de waarde achter case, dan wordt de code die bij die case hoort, uitgevoerd. Als dat niet zo is, dan wordt met de volgende case verder gegaan.
Als aan geen enkele case wordt voldaan, gebeurt er gewoon niets. Of, zoals hier, de code achter het sleutelwoord default ('standaard') wordt uitgevoerd.
(e.key): e is het bij function openVoorTab (e) { aan de code in de functie doorgegeven object, waarin onder andere zit, welke toetsen zijn ingedrukt.
Er zit veel meer informatie in e, maar welke toets is ingedrukt, zit in de eigenschap key. Elke toets heeft een unieke code, zodat je kunt nagaan, welke toets is ingedrukt.
Bij de switch hierboven is bepaald dat gekeken moet worden, welke toets is ingedrukt. Het zal niet als een grote verrassing komen dat het hier om de Enter-toets gaat. Als de Enter-toets is ingedrukt, wordt de code hieronder uitgevoerd, tot de volgende case of default wordt ontmoet. Dan wordt de rest van de code bij een eventuele andere case of default overgeslagen. (Dat is te zeggen: als je het sleutelwoord break gebruikt, zoals hier gebeurt.)
Dit deel van het script wordt alleen uitgevoerd, als aan deze eerder gestelde voorwaarde is voldaan:
- Enter is ingedrukt, terwijl a#voor-tabfocus heeft.
e: is het bij function openVoorTab (e) { aan de code in de functie doorgegeven object, waarin onder andere zit, welke toetsen zijn ingedrukt.
preventDefault(): dit is een van de ingebouwde methoden, die JavaScript gratis en voor niets toevoegt aan het hierboven genoemde object e.
De Enter-toets wordt voor van alles en nog wat gebruikt. Hiermee wordt de normale werking van de Enter-toets geblokkeerd. Dit voorkomt een mogelijke botsing tussen de normale werking van Enter en de werking van Enter in deze functie.
Dit deel van het script wordt alleen uitgevoerd, als aan deze eerder gestelde voorwaarde is voldaan:
- Enter is ingedrukt, terwijl a#voor-tabfocus heeft.
openMenu: bij openMenu = document.getElementById("checkbox-open-menu"), is het element input#checkbox-open-menu in de vorm van een object opgeslagen in variabele openMenu. Hierdoor is allerlei informatie uit input#checkbox-open-menu toegankelijk voor het script, en kan het script allerlei dingen met input#checkbox-open-menu doen.
checked: in de eigenschap checked van het hierboven genoemde object is opgeslagen, of input#checkbox-open-menu is aangevinkt of niet.
=: 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. In dit geval staat voor het isgelijkteken geen variabele, maar een eigenschap van het in openMenu opgeslagen object, dat bij input#checkbox-open-menu hoort. In dit geval wordt die eigenschap veranderd in wat achter het isgelijkteken staat: de <input> wordt aan- of uitgevinkt.
false: omdat dit achter het isgelijkteken staat, wordt dit de nieuwe waarde van de eigenschap checked van het object in openMenu, dat bij input#checkbox-open-menu hoort. Het resultaat hiervan is dat input#checkbox-open-menu wordt uitgevinkt.
Omdat in de css selectors met #checkbox-open-menu:checked nu niet meer werken, sluit het menu voor gebruikers van de Tab-toets, als dat eventueel al was geopend. Dat is precies de bedoeling, want met het indrukken van Enter als a#voor-tabfocus heeft, moet het menu in één keer worden gepasseerd.
Dit deel van het script wordt alleen uitgevoerd, als aan deze eerder gestelde voorwaarde is voldaan:
- Enter is ingedrukt, terwijl a#voor-tabfocus heeft.
content: bij content = document.getElementById("content"), is het element main#content in de vorm van een object opgeslagen in variabele content. Hierdoor is allerlei informatie uit main#content toegankelijk voor het script, en kan het script allerlei dingen met main#content doen.
focus();: dit is een van de ingebouwde methoden, die JavaScript gratis en voor niets toevoegt aan het hierboven genoemde object in variabele content. Hiermee wordt de focus gegeven aan het bij dat object horende element main#content.
Hiermee wordt voorkomen dat een gebruiker van de Tab-toets 160 keer de Tab-toets moet indrukken, voordat bij de eigenlijke inhoud van de pagina is aangekomen. (Ook als het menu niet is geopend, moet toch voor elke link en kop in het menu één keer de Tab-toets worden ingedrukt.) Nu wordt in één keer naar <main> gegaan, als de bezoeker op Enter drukt als a#voor-tab focus heeft.
Om te zorgen dat het script aan <main> focus kan geven, is in de html het attribuut tabindex="-1" aan <main> toegevoegd.
Dit deel van het script wordt alleen uitgevoerd, als aan deze eerder gestelde voorwaarde is voldaan:
- Enter is ingedrukt, terwijl a#voor-tabfocus heeft.
Deze regel is precies hetzelfde als die gelijk hierboven bij content.focus();, alleen wordt hier een andere methode gebruikt: scrollIntoView();. Deze methode plaatst het element - dat is hier main#content - binnen het zichtbare deel van het venster van de browser. Meestal is dat aan de bovenkant van het venster, maar dat hoeft niet.
Dit is ook de reden dat de menubalk bovenaan het browservenster wordt gesloten, als het menu voor de Tab-toets is geopend. Als <main> helemaal bovenaan het venster wordt gezet, zou anders het bovenste stukje wegvallen onder de menubalk.
Dit deel van het script wordt alleen uitgevoerd, als aan deze eerder gestelde voorwaarde is voldaan:
- Enter is ingedrukt, terwijl a#voor-tabfocus heeft.
De code binnen case "Enter" : is afgehandeld. Maar vervolgens wordt vrolijk verdergegaan met case "Tab" : gelijk hieronder. Dat kan soms handig zijn, want je kunt dan met één switch-lus meerdere dingen combineren. Maar hier is dat niet de bedoeling.
break zorgt ervoor dat de rest van de code binnen de switch-lus niet wordt uitgevoerd.
Bij de switch hierboven is bepaald dat gekeken moet worden, welke toets is ingedrukt. Hier gaat het om de Tab-toets. Als de Tab-toets is ingedrukt, wordt de code hieronder uitgevoerd, tot de volgende case of default wordt ontmoet. Dan wordt de rest van de code bij een eventuele andere case of default overgeslagen. (Dat is te zeggen: als je het sleutelwoord break gebruikt, zoals hier gebeurt.)
Als gebruikers van de Tab-toets per ongeluk de Tab-toets indrukken, omdat ze gewend zijn snel door een menu of zoiets heen te tabben, is dat geen ramp. Zodra het menu voor de Tab-toets is geopend, kan het gesloten worden door Escape in te drukken.
Dit deel van het script wordt alleen uitgevoerd, als aan deze eerder gestelde voorwaarde is voldaan:
- De Tab-toets is ingedrukt, terwijl a#voor-tabfocus heeft.
e: is het bij function openVoorTab (e) { aan de code in de functie doorgegeven object, waarin onder andere zit, welke toetsen zijn ingedrukt.
preventDefault(): dit is een van de ingebouwde methoden, die JavaScript gratis en voor niets toevoegt aan het hierboven genoemde object e.
Als de Tab-toets wordt ingedrukt, geeft de browser automatisch de focus aan het eerstvolgende element dat daarvoor in aanmerking komt. Dat is standaard ingebakken in de browser en staat helemaal los van dit script.
De functie hier opent het menu voor gebruikers van de Tab-toets en geeft de focus aan het eerste item in het menu. Maar daarna voert de browser alsnog de standaardhandeling bij de Tab-toets uit en geeft de focus aan het volgende element. Het script zet dus de focus op het eerste item uit het menu, waarna de browser de focus nog 'n item verder plaatst. Hierdoor krijgt niet het eerste item uit het menu de focus, maar het tweede.
preventDefault() voorkomt dat de browser de standaardhandeling bij de Tab-toets uitvoert. Hierdoor blijft de focus netjes op het eerste item uit het menu staan.
Dit deel van het script wordt alleen uitgevoerd, als aan deze eerder gestelde voorwaarde is voldaan:
- De Tab-toets is ingedrukt, terwijl a#voor-tabfocus heeft.
openMenu: bij openMenu = document.getElementById("checkbox-open-menu"), is het element input#checkbox-open-menu in de vorm van een object opgeslagen in variabele openMenu. Hierdoor is allerlei informatie uit input#checkbox-open-menu toegankelijk voor het script, en kan het script allerlei dingen met input#checkbox-open-menu doen.
checked: in de eigenschap checked van het hierboven genoemde object is opgeslagen, of input#checkbox-open-menu is aangevinkt of niet.
=: 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. In dit geval staat voor het isgelijkteken geen variabele, maar een eigenschap van het in openMenu opgeslagen object, dat bij input#checkbox-open-menu hoort. In dit geval wordt die eigenschap veranderd in wat achter het isgelijkteken staat: de <input> wordt aan- of uitgevinkt.
true: omdat dit achter het isgelijkteken staat, wordt dit de nieuwe waarde van de eigenschap checked van het object in openMenu, dat bij #checkbox-open-menu hoort. Het resultaat hiervan is dat input#checkbox-open-menu wordt aangevinkt.
Omdat in de css selectors met #checkbox-open-menu:checked nu werken, opent het menu voor gebruikers van de Tab-toets. Dat is precies de bedoeling, want als de Tab-toets wordt ingedrukt, terwijl a#voor-tabfocus heeft, moet het menu volledig worden geopend.
Dit deel van het script wordt alleen uitgevoerd, als aan deze eerder gestelde voorwaarde is voldaan:
- De Tab-toets is ingedrukt, terwijl a#voor-tabfocus heeft.
eersteItem: bij eersteItem = document.querySelector("nav ul li"), is het eerste item uit het uitklapmenu in de vorm van een object opgeslagen in variabele eersteItem. Hierdoor is allerlei informatie uit dit eerste item toegankelijk voor het script, en kan het script allerlei dingen met het eerste item doen.
focus(): dit is een van de ingebouwde methoden, die JavaScript gratis en voor niets toevoegt aan het hierboven genoemde object in variabele eersteItem. Hiermee wordt de focus gegeven aan het bij dat object horende eerste item. (In het voorbeeld is dat de <li> met 'Kop 1': de eerste <li> uit de buitenste <ul>.)
Als de bezoeker de Tab-toets indrukt, terwijl a#voor-tab focus heeft, wordt de focus aan het eerste item in het menu gegeven.
;: de puntkomma geeft weer het einde van de regel aan.
Dit deel van het script wordt alleen uitgevoerd, als aan deze eerder gestelde voorwaarde is voldaan:
- De Tab-toets is ingedrukt, terwijl a#voor-tabfocus heeft.
Deze regel is precies hetzelfde als die gelijk hierboven bij eersteItem.focus();, alleen wordt hier een andere methode gebruikt: scrollIntoView();. Deze methode plaatst het element - dat is hier het eerste item uit het menu - binnen het zichtbare deel van het venster van de browser. Meestal is dat aan de bovenkant van het venster, maar dat hoeft niet.
Dit is ook de reden dat de menubalk bovenaan het browservenster wordt gesloten, als het menu voor de Tab-toets is geopend. Als het eerste item helemaal bovenaan het venster wordt gezet, zou anders het bovenste stukje anders wegvallen onder de menubalk.
Dit deel van het script wordt alleen uitgevoerd, als aan deze eerder gestelde voorwaarde is voldaan:
- De Tab-toets is ingedrukt, terwijl a#voor-tabfocus heeft.
De code binnen case "Tab" : is afgehandeld. Maar vervolgens wordt vrolijk verdergegaan met default : gelijk hieronder. Dat kan soms handig zijn, want je kunt dan met één switch-lus meerdere dingen combineren. Maar hier is dat niet de bedoeling.
break zorgt ervoor dat de rest van de code binnen de switch-lus niet wordt uitgevoerd.
Dit deel van het script wordt alleen uitgevoerd, als aan deze eerder gestelde voorwaarde is voldaan:
- Er is een andere toets dan de Tab-toets of Enter ingedrukt, terwijl a#voor-tabfocus heeft.
De bedoeling is dat de gebruiker van de Tab-toets een keuze maakt tussen het menu volledig openen, of het menu in één keer passeren. Dit gebeurt door het indrukken van de Tab-toets of Enter als a#voor-tab focus heeft.
Als een andere toets wordt ingedrukt, is dit niet de eerder bij case "Enter" : of case "Tab" : gevraagde toets en wordt de code achter default uitgevoerd: #a-voor-tab houdt de focus.
Dit deel van het script wordt alleen uitgevoerd, als aan deze eerder gestelde voorwaarde is voldaan:
- Er is een andere toets dan de Tab-toets of Enter ingedrukt, terwijl a#voor-tab focus heeft.
voorTab: bij voorTab = document.getElementById("voor-tab"), is het element a#voor-tab in de vorm van een object opgeslagen in variabele voorTab. Hierdoor is allerlei informatie uit a#voor-tab toegankelijk voor het script, en kan het script allerlei dingen met a#voor-tab doen.
focus(): dit is een van de ingebouwde methoden, die JavaScript gratis en voor niets toevoegt aan het hierboven genoemde object in variabele voorTab. Hiermee wordt de focus gegeven aan het bij dat object horende element a#voor-tab.
Met behulp van css is geregeld dat a#voor-tab zichtbaar wordt, als dit element de focus krijgt.
Omdat deze code wordt uitgevoerd, zolang de bezoeker niet de Tab-toets of Enter heeft ingedrukt, blijft a#voor-tab zichtbaar.
function navigatieVoorTab (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 een toets wordt ingedrukt, terwijl het menu voor gebruikers van de Tab-toets is geopend. Het luisteren naar de toets wordt geregeld bij uitklapMenu.addEventListener("keydown", navigatieVoorTab);.
Voor gebruikers van de Tab-toets wordt het menu in z'n geheel geopend (als ze daarvoor kiezen): 33 koppen en 127 links. Als je met de Tab-toets de laatste link in het menu wilt bereiken, moet je 160 keer de Tab-toets indrukken. Mensen zijn van minder aan de alcohol geraakt.
Daarom kan een aantal pijltjestoetsen en dergelijke worden gebruikt, waarmee sneller door het menu kan worden gelopen. Deze functie handelt die toetsen af.
function: het sleutelwoord waarmee het begin van een functie wordt aangegeven.
navigatieVoorTab: 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): in e zit een zogenaamd object, waarin onder andere informatie zit over welke toets is ingedrukt. Omdat het tussen de haakjes achter function () , 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 }.
Hier worden twee variabelen aangemaakt. In een variabele kan een bepaalde waarde worden opgeslagen, die eventueel kan veranderen. Een uitgebreidere beschrijving van wat een variabele is, staat bij var body = document.getElementsByTagName("body").[0],,
Omdat na de tweede regel een komma staat, weet het script dat het sleutelwoord var eigenlijk ook voor krijgtFocus staat.
Omdat deze variabelen worden aangemaakt binnen function navigatieVoorTab (e), kunnen ze alleen door de code binnen deze functie worden gebruikt. Het zijn zogenaamde 'lokale variabelen'.
var: met het sleutelwoord var wordt aangegeven dat elk van de erop volgende woorden de naam van een 'variabele' is.
=: 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.
e: in e zit een zogenaamd object, waarin allerlei informatie zit. Bij function navigatieVoorTab (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.
Omdat function navigatieVoorTab (e) luistert naar een toetsaanslag ergens binnen nav#uitklapmenu, bevat het object in e informatie over het héle menu, ook over alle nakomelingen van de <nav>.
(Een uitgebreidere, algemenere omschrijving over dit soort objecten is te vinden bij (e).)
target: dit is een onderdeel van het hierboven genoemde object e. In target zit, in de vorm van een object, informatie over het exacte item in het menu dat focus had op het moment dat de toets werd ingedrukt. Ook dat is weer heel veel informatie: het soort element, eventuele tekst erin, nakomelingen, wat de ouder is, noem maar op.
Al deze informatie wordt hier in variabele geklikt opgeslagen. (Je zou ook overal in deze functie e.target kunnen gebruikt, maar geklikt is iets mensvriendelijker.)
,: de komma aan het eind geeft aan, dat de zin op de volgende regel doorgaat. Het sleutelwoord var aan het begin geldt daardoor ook voor de tweede regel met krijgtFocus.
krijgtFocus: de tweede variabele. Hier gebeurt nog niets mee, deze variabele wordt hier alleen aangemaakt. Later in de functie kan de variabele dan gewoon worden gebruikt om iets in op te bergen. In dit geval wordt het element dat focus moet krijgen erin opgeborgen. Welk element dat is, is afhankelijk van de ingedrukte toets.
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.
body: bij body = document.getElementsByTagName("body")[0], is het element <body> in de vorm van een object opgeslagen in variabele body. Hierdoor is allerlei informatie uit <body> toegankelijk voor het script, en kan het script allerlei dingen met <body> doen.
classList: dit is zo'n stukje informatie uit body. In classList zitten alle classes die bij het element <body> aanwezig zijn.
contains("xschermlezerx"): met methode contains() kan worden gekeken, of een bepaalde class aanwezig is in classList. De class waarnaar wordt gezocht, staat tussen aanhalingstekens: 'xschermlezerx'.
Als de bezoeker heeft gekozen om het menu toegankelijk te maken voor schermlezers, heeft het script deze class bij <body> toegevoegd. Deze class is niet te zien in de gewone code, maar alleen in de Gegenereerde code.
Als je de naam van class 'xschermlezerx' wilt veranderen, moet je die in het script en in de css veranderen. Dit is één van de plaatsen in het script, waar die naam gewijzigd moet worden.
{: 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: als bij <body> class 'xschermlezerx' aanwezig is, voer dan de code tussen de {} achter de if uit.
Omdat hier verder niet wordt gekeken, welke toets wordt ingedrukt, wordt deze code áltijd uitgevoerd, ongeacht welke toets is ingedrukt.
Dit deel van het script wordt alleen uitgevoerd, als aan deze eerder gestelde voorwaarde is voldaan:
- class 'xschermlezerx' is aanwezig bij <body>.
Schermlezers hebben een heel scala aan eigen toetsen en toetscombinaties, waar gebruikers aan gewend zijn. Met behulp van die toetscombinaties kan een schermlezer snel door het menu lopen door bijvoorbeeld van kopregel naar kopregel te gaan.
Gebruikers van schermlezers worden daarom niet blij, als die toetsen plotseling heel anders gaan werken. Daarom wordt met het sleutelwoord return deze functie afgebroken: de rest van de functie wordt gewoon niet uitgevoerd. Er worden dus geen toetsen in gebruik genomen, die mogelijk al door de schermlezer worden gebruikt.
Als de Escape-toets wordt ingedrukt, wordt het menu gesloten en in één keer gepasseerd.
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 navigatieVoorTab (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.
||: twee verticale strepen betekenen in JavaScript 'of'. Omdat dit binnen een if staat, moet óf wat voor de || staat waar zijn, óf wat achter de || staat.
e.key === "Esc": het verhaal hiervoor is precies hetzelfde als hierboven bij e.key === "Escape" voor de ||. Dit is nog een restant van de ellende uit het verleden, waarin vrijwel elke browser zo'n beetje alles op z'n eigen manier deed.
Tegenwoordig is de naam van de Escape-toets gestandaardiseerd als 'Escape'. Internet Explorer en Edge gebruiken nog een oudere, niet-standaard naam: 'Esc'.
{: 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: als de Escape-toets is ingedrukt, voer dan de code tussen de {} achter de if uit.
Dit deel van het script wordt alleen uitgevoerd, als aan deze eerder gestelde voorwaarde is voldaan:
- De Escape-toets is ingedrukt, terwijl het menu voor de Tab-toets is geopend.
Deze vier regels zijn precies hetzelfde als die in function openVoorTab (e) onder case "Enter" :. (Alleen staat daar nog een vijfde regel break;. Die break ontbreekt hier, want die is alleen bij een case nodig.)
Deze vier regels betekenen:
* voorkom de standaardwerking van de Escape-toets;
* haal het vinkje weg bij input#checkbox-open-menu;
else if: iets hierboven bij if (e.key === "Escape" || e.key === "Esc") { staat een if met als voorwaarde dat de Escape-toets moet zijn ingedrukt. Hier staat een andere if, die als voorwaarde heeft dat Pijltje omhoog of Pijltje omlaag is ingedrukt.
Zo'n tweede (of latere) if geef je aan met else if: 'anders als'. Het is gebruikelijk het sleutelwoord else achter de afsluitende } van de voorgaande if of else if te zetten, op dezelfde regel.
Net als bij de if staat ook hier de voorwaarde weer tussen haakjes.
e: in e zit een zogenaamd object, waarin allerlei informatie zit. Bij function navigatieVoorTab (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.
"ArrowUp": dit is in JavaScript de naam van het Pijltje omhoog. De inhoud van key, oftewel de naam van de ingedrukte toets moet 'ArrowUp' zijn: het Pijltje omhoog moet zijn ingedrukt.
||: twee verticale strepen betekenen in JavaScript 'of'. Omdat dit binnen een if staat, moet óf wat voor de || staat waar zijn, óf wat achter de || staat.
e.key === "Up": het verhaal hiervoor is precies hetzelfde als hierboven bij e.key === "ArrowUp" voor de ||. Dit is nog een restant van de ellende uit het verleden, waarin vrijwel elke browser zo'n beetje alles op z'n eigen manier deed.
Tegenwoordig is de naam van de Escape-toets gestandaardiseerd als 'ArrowUp'. Internet Explorer 11 gebruikt nog een oudere, niet-standaard naam: 'Up'.
|| e.key === "ArrowDown" || e.key === "Down"): precies hetzelfde verhaal als gelijk hierboven, maar nu voor het Pijltje omlaag. Waarbij 'Down' weer voor Internet Explorer 11 is.
{: de code die wordt uitgevoerd, als aan deze else if-voorwaarde wordt voldaan, wordt tussen accolades gezet. Het script weet dan, wat bij deze else if hoort. Aan het eind van de code bij deze else if staat de afsluitende }.
De hele regel: als Pijltje omhoog of Pijltje omlaag is ingedrukt, voer dan de code tussen de {} achter de if uit.
Omdat een deel van de code voor Pijltje omhoog en Pijltje omlaag hetzelfde is, wordt dat gecombineerd. Wat niet hetzelfde is, wordt later gesplitst met weer 'n if.
Dit deel van het script wordt alleen uitgevoerd, als aan deze eerder gestelde voorwaarde is voldaan:
- Pijltje omhoog of Pijltje omlaag is ingedrukt, terwijl het menu voor de Tab-toets is geopend.
e: is het bij function navigatieVoorTab (e) { aan de code in de functie doorgegeven object, waarin onder andere zit, welke toetsen zijn ingedrukt.
preventDefault(): dit is een van de ingebouwde methoden, die JavaScript gratis en voor niets toevoegt aan het hierboven genoemde object e.
Pijltje omhoog en Pijltje omlaag worden voor van alles en nog wat gebruikt. Hiermee wordt de normale werking van deze toetsen geblokkeerd. Hierdoor wordt een mogelijke botsing tussen de normale werking van deze toetsen en de werking van deze toetsen in deze functie voorkomen.
geklikt = (geklikt.tagName === "A" ? geklikt.parentNode : geklikt);
Dit deel van het script wordt alleen uitgevoerd, als aan deze eerder gestelde voorwaarde is voldaan:
- Pijltje omhoog of Pijltje omlaag is ingedrukt, terwijl het menu voor de Tab-toets is geopend.
In deze regel wordt gekeken, of het element dat focus had op het moment dat de toets werd ingedrukt, een link <a> is. Als dat zo is, wordt de <a> vervangen door de <li>, waar de <a> in zit.
geklikt: bij var geklikt = e.target, is in variabele geklikt de inhoud van e.target opgeslagen. Hierin zit, in de vorm van een object, het precieze element binnen het menu dat focus had op het moment dat Pijltje omhoog of Pijltje omlaag werd ingedrukt. Dat kan een <a> zijn. Maar het kan ook een <li> met een kop zijn, omdat daar in de html het attribuut tabindex="0" aan is toegevoegd, waardoor ook deze <li>'s focus kunnen krijgen.
=: 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.
(geklikt.tagName === "A" ? geklikt.parentNode : geklikt): in dit geval staat achter het isgelijkteken iets tussen haakjes. Als je wat daar staat even door letters vervangt, wordt het wat overzichtelijker:
(a === "A" ? b : c)
Dit is een verkorte manier om een if te beschrijven. Dit kan worden gebruikt bij heel eenvoudige if's. De voorwaarde staat voor het vraagteken: a === "A".
Als die voorwaarde waar is, wordt de waarde in variabele geklikt vervangen door de waarde van wat tussen vraagteken en dubbele punt staat: b. Als de voorwaarde niet waar is, wordt geklikt vervangen door wat achter de dubbele punt staat: c.
De nieuwe waarde van geklikt hangt dus af van het resultaat van de if tussen de haakjes.
De echte if ziet er iets minder simpel uit dan die hierboven met letters, maar werkt precies hetzelfde: (geklikt.tagName === "A": geklikt.parentNode : geklikt)
geklikt.tagName === "A": de voorwaarde. In variabele geklikt zit in de vorm van een object het precieze element uit het menu dat de focus had op het moment dat geluisterd werd, welke toets werd ingedrukt.
In de eigenschap tagName van dat object zit de naam van de tag. Bij een <a> is dat 'A', bij een <li> 'LI', enzovoort. Altijd in hoofdletters en zonder de teksthaken, maar wel tussen aanhalingstekens, omdat het om een letterlijke naam gaat.
===: betekent weer dat wat voor de drie isgelijktekens staat helemaal hetzelfde moet zijn als wat erachter staat.
Achter de drie isgelijktekens staat "A". Wat voor de isgelijktekens staat moet dus hetzelfde zijn als 'A'. Oftewel: het element dat focus had op het moment dat naar de toets werd geluisterd, moet een <a> zijn. Als dat zo is, is de voorwaarde waar.
?: het vraagteken scheidt de voorwaarde van de if van wat er moet gebeuren als de voorwaarde wel of niet waar is.
geklikt.parentNode: dit staat voor de dubbele punt, daarom moet dit gebeuren, als de voorwaarde waar is. Als het element een <a> is, wordt dit de nieuwe waarde van variabele geklikt.
In geklikt is de <a> in de vorm van een object met allerlei informatie over de <a> opgeslagen. Eén van de eigenschappen van het object is, wat de vader van de <a> is: parentNode.
Als het element dat focus had op het moment dat naar de toets werd geluisterd een <a> is, vervang de inhoud van variabele geklikt dan door de ouder van die <a>. Dat is in dit voorbeeld altijd een <li>.
:: de dubbele punt scheidt wat er moet gebeuren, als de voorwaarde van de if aan het begin waar is, van wat er moet gebeuren, als dat niet zo is.
geklikt: dit staat achter de dubbele punt, daarom moet dit gebeuren, als de voorwaarde niet waar is. Als het element geen <a> is, blijft geklikt gewoon geklikt: er verandert niets aan de inhoud van variabele geklikt.
De hele regel: in geklikt zit het object dat hoort bij het element dat focus had op het moment dat er naar een toetsaanslag werd geluisterd. Als dat object bij een <a> hoort, vervang het dan door de <li> die de ouder van die <a> is. Als het geen <a> is, doe dan niets.
Dit maakt de rest van de code iets eenvoudiger, omdat nu alleen rekening gehouden hoeft te worden met <li>'s en niet met <a>'s. Er kan nu op dezelfde manier een aantal bewerkingen worden uitgevoerd, of het element dat focus had nu een <a> was of een <li>.
Later in de code wordt, als dat nodig is, weer gekeken, of het om een <a> of om een <li> gaat en zijn enkele regels code weer verschillend voor een <a> of <li>.
Bij deze if wordt iets verder gefilterd: de code tussen de {} achter deze if wordt alleen uitgevoerd, als Pijltje omhoog is ingedrukt. Deze if is grotendeels hetzelfde als de eerdere, alleen is deze iets korter, omdat het maar om één toets gaat. Een uitgebreidere uitleg over hoe dit werkt, is te vinden bij de eerdere if, waarvan de link ernaartoe iets hierboven staat.
if (geklikt === geklikt.parentNode.firstElementChild) {
Dit deel van het script wordt alleen uitgevoerd, als aan deze eerder gestelde voorwaarde is voldaan:
- Pijltje omhoog is ingedrukt, terwijl het menu voor de Tab-toets is 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.
geklikt: bij var geklikt = e.target, is in variabele geklikt de inhoud van e.target opgeslagen. Hierin zit, in de vorm van een object, het precieze element binnen het menu dat focus had op het moment dat Pijltje omhoog werd ingedrukt. Als dat een link, een <a> was, is dat eerder bij geklikt = (geklikt.tagName === "A" ? geklikt.parentNode : geklikt); vervangen door de <li> die ouder is van die <a>.
In alle gevallen hoort het object in variabele geklikt dus bij een <li> uit het menu.
===: 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.
geklikt.parentNode.firstElementChild: dit is een wat eigenaardige rondreis naar boven en terug naar beneden.
geklikt: de variabele met het object, dat iets hierboven al is beschreven.
parentNode: de ouder van het element dat bij het object uit geklikt hoort. Omdat dat element een <li> is, is die ouder per definitie een <ul>.
firstElementChild: het eerste kind uit de gelijk hierboven beschreven parentNode. Oftewel: de eerste <li> in die <ul>.
Je gaat dus van de <li> in geklikt naar de ouder van die <li> (een <ul>), en dan weer naar het eerste kind van die <ul>. Wat ook een <li> moet zijn, want er mogen nou eenmaal alleen <li>'s kinderen van een <ul> zijn.
{: de code die wordt uitgevoerd, als aan deze if 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: als het element dat bij het object in geklikt hoort hetzelfde is als het eerste kind van de ouder van dat element. Een wat omslachtige manier om vast te stellen, dat deze <li> het eerste kind van de <ul> is.
Als je dit naar mensen zou vertalen: je vraagt aan een kind, wie de ouder is. En aan die ouder vraag je vervolgens, welk kind de eerstgeborene is. Als die eerstgeborene dan dezelfde blijkt te zijn als het kind, waaraan je naar de ouders hebt gevraagd, is dat kind dús de eerstgeborene.
Bij mensen zou je natuurlijk gewoon aan het kind vragen, of het de oudste is. Maar een computer is daar te dom en te rechtlijnig voor.
Dit deel van het script wordt alleen uitgevoerd, als aan deze eerder gestelde voorwaarden is voldaan:
- Pijltje omhoog is ingedrukt, terwijl het menu voor de Tab-toets is geopend.
- Het element dat bij het object in geklikt hoort, is de eerste <li> in de <ul>.
krijgtFocus: aan het begin van function navigatieVoorTab () is bij krijgtFocus; deze variabele al aangemaakt, maar verder is er nog niets mee gedaan.
In krijgtFocus wordt het object opgeslagen dat hoort bij het element uit het menu dat focus moet krijgen.
=: 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.
geklikt.parentNode.lastElementChild: dit is een wat eigenaardige rondreis naar boven en terug naar beneden.
geklikt: bij var geklikt = e.target, is in variabele geklikt de inhoud van e.target opgeslagen. Hierin zit, in de vorm van een object, het precieze element binnen het menu dat focus had op het moment dat Pijltje omhoog werd ingedrukt. Als dat een link, een <a> was, is dat eerder bij geklikt = (geklikt.tagName === "A" ? geklikt.parentNode : geklikt); vervangen door de <li> die ouder is van die <a>.
In alle gevallen hoort het object in variabele geklikt dus bij een <li> uit het menu.
parentNode: de ouder van het element dat bij het object uit geklikt hoort. Omdat dat element een <li> is, is die ouder per definitie een <ul>.
lastElementChild: het laatste kind uit de gelijk hierboven beschreven parentNode. Oftewel: de laatste <li> in die <ul>.
Je gaat dus van de <li> in geklikt naar de ouder van die <li> (een <ul>), en dan weer naar het laatste kind van die <ul>. Wat ook een <li> moet zijn, want er mogen nou eenmaal alleen <li>'s kinderen van een <ul> zijn.
{: de code die wordt uitgevoerd, als aan deze if 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: stop in krijgtFocus de laatste <li> van de ouder (de <ul>) van het element dat bij het object in geklikt hoort. Oftewel: de laatste <li> uit dezelfde <ul> als waar de <li> uit geklikt in zit.
Als pijltje omhoog wordt ingedrukt op het eerste item in een submenu, moet de focus binnen het submenu blijven. Daarom gaat de focus van het eerste item in het submenu naar het laatste item in het submenu.
Dat zou je ook hier al helemaal kunnen regelen. Maar dan zou dat alleen gelden voor het eerste item in een submenu als Pijltje omhoog is ingedrukt. Een deel van de code is voor het laatste item, of als Pijltje omlaag is ingedrukt, hetzelfde. Daarom wordt het door deze regel gevonden laatste item in het submenu eerst opgeborgen in variabele krijgtFocus. Dat gebeurt ook voor het item dat bij Pijltje omlaag en dergelijke wordt gevonden.
Voor alle elementen kan dan later dezelfde code worden uitgevoerd door niet het element zelf, maar variabele krijgtFocus te gebruiken. De code wordt dan uitgevoerd met het bij het object in krijgtFocus horende element, ongeacht welk element dat is.
}: dit is de afsluitende accolade van de code die bij de hierboven genoemde if hoort.
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. Bij if (e.key === "ArrowUp" || e.key === "Up") { was de voorwaarde dat Pijltje omhoog is ingedrukt. Als dat bij de eerste <li> uit een <ul> gebeurde, is dat hier gelijk boven al afgehandeld. Alle andere <li>'s worden hier afgehandeld, of het nou de tweede, derde of zoveelste <li> binnen de <ul> is.
{: de code die wordt uitgevoerd, als aan deze else wordt voldaan, wordt tussen code tussen accolades gezet. Het script weet dan, wat bij deze else hoort. Aan het eind van de code bij deze else staat de afsluitende }.
Dit deel van het script wordt alleen uitgevoerd, als aan deze eerder gestelde voorwaarden is voldaan:
- Pijltje omhoog is ingedrukt, terwijl het menu voor de Tab-toets is geopend.
- Het element dat bij het object in geklikt hoort, is niet de eerste <li> in de <ul>.
krijgtFocus: aan het begin van function navigatieVoorTab () is bij krijgtFocus; deze variabele al aangemaakt, maar verder is er nog niets mee gedaan. In krijgtFocus wordt het object opgeslagen dat hoort bij het element uit het menu dat focus moet krijgen.
=: 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.
geklikt.previousElementSibling:
geklikt: bij var geklikt = e.target, is in variabele geklikt de inhoud van e.target opgeslagen. Hierin zit, in de vorm van een object, het precieze element binnen het menu dat focus had op het moment dat Pijltje omhoog werd ingedrukt. Als dat een link, een <a> was, is dat eerder bij geklikt = (geklikt.tagName === "A" ? geklikt.parentNode : geklikt); vervangen door de <li> die ouder is van die <a>.
In alle gevallen hoort het object in variabele geklikt dus bij een <li> uit het menu.
previousElementSibling: het eerste element in de html voor het element dat bij de variabele in het object in geklikt hoort. Beide elementen moeten dezelfde ouder hebben. Omdat het element bij het object uit geklikt een <li> is, is het eerste element ervoor met dezelfde ouder per definitie ook een <li>, want alleen een <li> kan een kind van een <ul> zijn.
De hele regel: stop in krijgtFocus de <li> die in de html voor de <li> zit, die bij het object in geklikt hoort. ('previous' betekent vorige, 'sibling' betekent broer of zus.)
Als er nog 'n <li> in het submenu zit voor deze <li>, dan krijgt die <li> de focus, als Pijltje omhoog is ingedrukt.
}: dit is de afsluitende accolade van de code die bij de hierboven genoemde if hoort.
else: als aan de voorwaarde bij die if niet wordt voldaan. Oftewel: als Pijltje omhoog niet is ingedrukt. Aangezien Pijltje omhoog al is afgehandeld bij de if, blijft hier bij de else alleen Pijltje omlaag over.
{: de code die wordt uitgevoerd, als aan deze else wordt voldaan, wordt tussen code tussen accolades gezet. Het script weet dan, wat bij deze else hoort. Aan het eind van de code bij deze else staat de afsluitende }.
if (geklikt === geklikt.parentNode.lastElementChild) {
Als Pijltje omlaag is ingedrukt, terwijl het laatste item in een submenu focus heeft, wordt teruggegaan naar het eerste item in het submenu. Als het niet het laatste item was, krijgt het volgende item de focus.
lastElementChild (laatste kind) en firstElementChild (eerste kind) zijn hier omgedraaid, en in plaats van previousElementSibling (voorgaand element) wordt nextElementSibling (volgende element) gebruikt.
krijgtFocus = (krijgtFocus.firstElementChild.tagName === "A" ? krijgtFocus.firstElementChild : krijgtFocus);
Dit deel van het script wordt alleen uitgevoerd, als aan deze eerder gestelde voorwaarde is voldaan:
- Pijltje omhoog of Pijltje omlaag is ingedrukt, terwijl het menu voor de Tab-toets is geopend.
De aparte code voor Pijltje omhoog en Pijltje omlaag is afgehandeld, deze code geldt weer voor beide toetsen.
krijgtFocus: in deze variabele is het object opgeborgen dat hoort bij het element, dat na indrukken van Pijltje omhoog of Pijltje omlaag de focus moet krijgen. Omdat dit object in een variabele zit, kan de code voor elk gevonden object met bijbehorend element worden gebruikt. Hoewel krijgtFocus aan het begin van de regel staat, wordt in werkelijkheid de code uitgevoerd op de inhoud van krijgtFocus.
=: 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.
(krijgtFocus.firstElementChild.tagName === "A" ? krijgtFocus.firstElementChild : krijgtFocus): in dit geval staat achter het isgelijkteken iets tussen haakjes. Als je wat daar staat even door letters vervangt, wordt het wat overzichtelijker:
(a === "A" ? b : c)
Dit is een verkorte manier om een if te beschrijven, die kan worden gebruikt bij heel eenvoudige if's. De voorwaarde staat voor het vraagteken: a === "A".
Als dat zo is, wordt de waarde in variabele krijgtFocus vervangen door de waarde van wat tussen vraagteken en dubbele punt staat: b. Als de voorwaarde niet waar is, wordt krijgtFocus vervangen door wat achter de dubbele punt staat: c.
De nieuwe waarde van krijgtFocus hangt dus af van het resultaat van de if tussen de haakjes.
De echte if ziet er iets minder simpel uit dan die hierboven met letters, maar werkt precies hetzelfde: krijgtFocus.firstElementChild.tagName === "A" ? krijgtFocus.firstElementChild : krijgtFocus)
krijgtFocus.firstElementChild.tagName === "A": de voorwaarde.
In variabele krijgtFocus zit in de vorm van een object het element dat focus moet krijgen.
firstElementChild is het eerste kind van dat element. Dat eerste kind is ook weer opgeslagen in de vorm van een object, waardoor ook weer allerlei informatie over het eerste kind wordt opgeslagen.
In de eigenschap tagName van het object dat bij het eerste kind hoort, zit de naam van de tag van het eerste kind. Bij een <a> is dat 'A', bij een <li> 'LI', enzovoort. Altijd in hoofdletters en zonder de teksthaken, maar wel tussen aanhalingstekens, omdat het om een letterlijke naam gaat.
=== betekent weer dat wat voor de drie isgelijktekens staat helemaal hetzelfde moet zijn als wat erachter staat.
Achter de drie isgelijktekens staat "A". Wat voor de isgelijktekens staat moet dus hetzelfde zijn als 'A'. Oftewel: het eerste kind van het element dat focus moet krijgen, moet een <a> zijn.
Bij geklikt = (geklikt.tagName === "A" ? geklikt.parentNode : geklikt); is een <a> die focus had omgezet naar de ouder van die <a>. Dat is altijd een <li>. Hierdoor hoefde de code geen rekening te houden met <a>'s, maar alleen met <li>'s, waardoor de code een stuk simpeler werd.
Maar nu met het geven van de focus aan een element is er weer wel een verschil. Als een <li> een kop boven een submenu bevat, mag die <li> de focus krijgen. Maar als in een <li> een <a> zit, moet die <a> de focus krijgen. Oftewel: hier moeten koppen en links weer uit elkaar worden gehaald.
Dat gebeurt hier.
Een <li> waarin een kop boven een submenu zit, heeft niet als eerste kind een <a>. Een <li> met een link moet echter als eerste kind een <a> hebben. Deze voorwaarde is dus alleen waar, als de <li> een <a> bevat.
?: het vraagteken scheidt de voorwaarde van de if van wat er moet gebeuren, als de voorwaarde wel of niet waar is.
krijgtFocus.firstElementChild: dit staat voor de dubbele punt, daarom moet dit gebeuren, als de voorwaarde waar is. Als het eerste kind een <a> is, is dit het element dat de focus moet krijgen: het object in krijgtFocus (dat bij een <li> hoort) wordt vervangen door het object dat bij het eerste kind van die <li> hoort. En dat eerste kind is dus een <a>.
Nu gaat niet langer de <li> de focus krijgen, maar de in die <li> zittende <a>.
:: de dubbele punt scheidt wat er moet gebeuren, als de voorwaarde van de if aan het begin waar is, van wat er moet gebeuren, als dat niet zo is.
krijgtFocus: dit staat achter de dubbele punt, daarom moet dit gebeuren, als de voorwaarde niet waar is. Als het element geen <a> is, blijft krijgtFocus gewoon krijgtFocus: er verandert niets aan de inhoud van variabele krijgtFocus.
De hele regel: in krijgtFocus zit het object dat hoort bij het element dat de focus moet krijgen (nadat op Pijltje omhoog of Pijltje omlaag is gedrukt). Als van dat element het eerste kind een <a> is, vervang het object dan door het object dat bij die <a> hoort. Als het geen <a> is, doe dan niets.
Dit deel van het script wordt alleen uitgevoerd, als aan deze eerder gestelde voorwaarde is voldaan:
- Pijltje omhoog of Pijltje omlaag is ingedrukt, terwijl het menu voor de Tab-toets is geopend.
krijgtFocus: in deze variabele zit het eerder gevonden object dat hoort bij het element dat focus moet krijgen.
focus(): dit is een van de ingebouwde methoden, die JavaScript gratis en voor niets toevoegt aan het hierboven genoemde object in variabele krijgtFocus. Hiermee wordt de focus gegeven aan het bij dat object horende element.
Dit deel van het script wordt alleen uitgevoerd, als aan deze eerder gestelde voorwaarde is voldaan:
- Pijltje omhoog of Pijltje omlaag is ingedrukt, terwijl het menu voor de Tab-toets is geopend.
Deze regel is precies hetzelfde als die gelijk hierboven bij krijgtFocus.focus();, alleen wordt hier een andere methode gebruikt: scrollIntoView();. Deze methode plaatst het element dat bij het object in krijgtFocus hoort binnen het zichtbare deel van het venster van de browser. Meestal is dat aan de bovenkant van het venster, maar dat hoeft niet.
Dit is ook de reden dat de menubalk bovenaan het browservenster wordt gesloten, als het menu voor de Tab-toets is geopend. Als <main> helemaal bovenaan het venster wordt gezet, zou anders het bovenste stukje wegvallen onder de menubalk.
De } aan het begin is weer de afsluitende } bij bovenstaande else if. De naam van Pijltje links is 'ArrowLeft', maar Internet Explorer noemt deze toets 'Left'.
Dit deel van het script wordt alleen uitgevoerd, als aan deze eerder gestelde voorwaarde is voldaan:
- Pijltje links is ingedrukt, terwijl het menu voor de Tab-toets is geopend.
e: is het bij function navigatieVoorTab (e) { aan de code in de functie doorgegeven object, waarin onder andere zit, welke toetsen zijn ingedrukt.
preventDefault(): dit is een van de ingebouwde methoden, die JavaScript gratis en voor niets toevoegt aan het hierboven genoemde object e.
Pijltje links wordt voor van alles en nog wat gebruikt. Hiermee wordt de normale werking van deze toets geblokkeerd. Hierdoor wordt een mogelijke botsing tussen de normale werking van deze toets en de werking van deze toets in deze functie voorkomen.
if (geklikt.parentNode.parentNode.tagName === "NAV") {
Dit deel van het script wordt alleen uitgevoerd, als aan deze eerder gestelde voorwaarde is voldaan:
- Pijltje links is ingedrukt, terwijl het menu voor de Tab-toets is 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.
geklikt: bij var geklikt = e.target, is in variabele geklikt de inhoud van e.target opgeslagen. Hierin zit, in de vorm van een object, het precieze element binnen het menu dat focus had op het moment dat Pijltje links werd ingedrukt.
parentNode: de ouder van het element dat bij het object uit geklikt hoort. Als dat element een <a> (een link) was, is de ouder een <li>, want elke <a> zit in een <li>. Als dat element een <li> (met een kop) was, is de ouder een <ul>, want elke <li> zit (verplicht) in een <ul>.
parentNode: nog een keer parentNode. Nu gaat het om de ouder van het element dat bij de parentNode hierboven is gevonden. Bij mensen zou dat 'n grootouder zijn. (Anders dan bij mensen heb je maar één grootouder bij html, waardoor je ook geen echtelijke twisten hebt. Helaas wel bergen andere ellende die vaak met erfelijke eigenschappen te maken hebben...)
Als het bij de eerste parentNode gevonden element een <li> was, is de grootouder een <ul> (want elke <li> zit altijd in een <ul>). Als het bij de eerste parentNode gevonden element een <ul> was, is de grootouder een <li> (want elke <ul> zit in dit voorbeeld in een <li>).
Met één uitzondering (en daar gaat het hier om): de grootouder van de <li>'s uit de buitenste <ul> (de items met 'Kop 1', 'Kop 2', enzovoort) is geen <ul>, maar een <nav>: nav#uitklapmenu.
tagName: de naam van het hierboven gevonden grootouder-element. Dit kan geen andere naam zijn dan 'UL', 'LI' of 'NAV' (in JavaScript schrijf je tags altijd in hoofdletters, zonder teksthaken en tussen aanhalingstekens).
===: 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.
"NAV": achter de drie isgelijktekens staat "NAV". Wat voor de isgelijktekens staat moet dus hetzelfde zijn als 'NAV'. Oftewel: de gevonden grootouder moet een <nav> zijn. De enige items uit het menu die een <nav> (nav#uitklapmenu, waarin het hele menu zit) als grootouder hebben, zijn de zes <li>'s uit de buitenste <ul>. De <li>'s met 'Kop 1', 'Kop 2', enzovoort.
De hele regel: als de grootouder van het element dat focus had toen Pijltje links werd ingedrukt een <nav> is, voer dan de code tussen de {} achter de if uit.
Dit deel van het script wordt alleen uitgevoerd, als aan deze eerder gestelde voorwaarden is voldaan:
- Pijltje links is ingedrukt, terwijl het menu voor de Tab-toets is geopend.
- Pijltje links is ingedrukt, terwijl één van de <li>'s uit de buitenste <ul> (met 'Kop 1', 'Kop 2', enzovoort) focus had.
Bij if (geklikt.parentNode.parentNode.tagName === "NAV") { hier iets boven is vastgesteld dat een van de <li>'s uit de buitenste <ul> focus had, op het moment dat Pijltje links werd ingedrukt. Met Pijltje links moet naar de <li> met de kop boven het submenu worden gegaan.
De buitenste <ul> is geen submenu en heeft dan ook geen kop. Daarom moet bij de <li>'s in de buitenste <ul> deze toets worden genegeerd.
Met return wordt function navigatieVoorTab (), waar deze regel onderdeel van is, verlaten. De rest van de code uit de functie wordt niet uitgevoerd.
else if: bij de hierboven genoemde if is de voorwaarde dat het om een <li> uit de buitenste <nav> gaat (de grootouder moet een <nav><nav> zijn). Hier staat een andere if, die als voorwaarde heeft dat een <a> focus had op het ogenblik dat Pijltje links werd ingedrukt.
Zo'n tweede (of latere) if geef je aan met else if: 'anders als'. Het is gebruikelijk het sleutelwoord else achter de afsluitende } van de voorgaande if of else if te zetten, op dezelfde regel.
Net als bij de if staat ook hier de voorwaarde weer tussen haakjes.
geklikt: bij var geklikt = e.target, is in variabele geklikt de inhoud van e.target opgeslagen. Hierin zit, in de vorm van een object, het precieze element binnen het menu dat focus had op het moment dat Pijltje links werd ingedrukt.
tagName: de tag-naam van het element dat hoort bij het in geklikt opgeslagen object.
===: 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.
"A": achter de drie isgelijktekens staat "A". Wat voor de isgelijktekens staat moet dus hetzelfde zijn als 'A'. Oftewel: het element dat focus had op het moment dat naar de toets werd geluisterd, moet een <a> zijn.
{: de code die wordt uitgevoerd, als aan deze else if- wordt voldaan, wordt tussen accolades gezet. Het script weet dan, wat bij deze else if hoort. Aan het eind van de code bij deze else if staat de afsluitende }.
De hele regel: als een <a> focus had op het moment dat Pijltje links werd ingedrukt, voer dan de code tussen de {} achter de else if uit.
Dit deel van het script wordt alleen uitgevoerd, als aan deze eerder gestelde voorwaarden is voldaan:
- Pijltje links is ingedrukt, terwijl het menu voor de Tab-toets is geopend.
- Pijltje links is ingedrukt, terwijl een <a> focus had.
krijgtFocus: aan het begin van function navigatieVoorTab () is bij krijgtFocus; deze variabele al aangemaakt, maar verder is er nog niets mee gedaan. In krijgtFocus wordt het object opgeslagen dat hoort bij het element uit het menu dat focus moet krijgen.
=: 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.
geklikt.parentNode.parentNode.parentNode:
geklikt: bij var geklikt = e.target, is in variabele geklikt de inhoud van e.target opgeslagen. Hierin zit, in de vorm van een object, het precieze element binnen het menu dat focus had op het moment dat Pijltje links werd ingedrukt.
parentNode: de ouder van het element dat bij het object uit geklikt hoort. Aangezien dit een <a> is, is die ouder een <li>, want elke <a> binnen het menu zit in een <li>.
parentNode: de ouder van de hierboven gevonden <li>. Dat is een <ul>, want een <li> moet als ouder een <ul> hebben.
parentNode: de ouder van de hierboven gevonden <ul>. Dit moet een <li> zijn, want elke <ul> zit in dit voorbeeld in een <li>. (Behalve de buitenste <ul>, die zit in nav#uitklapmenu, maar die is er eerder bij if (geklikt.parentNode.parentNode.tagName === "NAV") { al uitgefilterd.)
geklikt.parentNode.parentNode.parentNode: dit hele deel samen:
elke <ul> met een submenu zit in een <li>, waarin de kop boven dat submenu staat:
<li>Kop ...</li>
<ul>
<li><a>Link in submenu</a></li>
<li><a>Link in submenu</a></li>
<li><a>Link in submenu</a></li>
Als Pijltje links is ingedrukt, terwijl een link uit de code hierboven focus had, zit in geklikt het object dat bij die <a> hoort. De eerste parentNode daarvan is de <li>, waar de <a> in zit. De parentNode van die <li> is de <ul>. En de parentNode daarvan is de <li>, waar de kop van het submenu in zit.
De hele regel: sla in variabele krijgtFocus het object op dat hoort bij de overgrootouder van de link die focus had, op het moment dat Pijltje links werd ingedrukt. Daarin zit de kop boven het submenu met de link die focus had. Die kop moet focus krijgen, als Pijltje links is ingedrukt.
Dit deel van het script wordt alleen uitgevoerd, als aan deze eerder gestelde voorwaarde is voldaan:
- Pijltje links is ingedrukt, terwijl het menu voor de Tab-toets is geopend.
Deze regel is precies hetzelfde als die iets hoger bij } else if (geklikt.tagName === "A") {, alleen moet nu geen <a>, maar een <li> focus hebben gehad, op het moment dat Pijltje links werd ingedrukt.
(Een <li> kan normaal genomen geen focus krijgen, maar door in de html het attribuut tabindex="0" aan de <li> toe te voegen, kan dit wel. Elke <li> met een kop heeft dat attribuut gekregen, dus alleen <li>'s met een kop kunnen focus krijgen.)
Dit deel van het script wordt alleen uitgevoerd, als aan deze eerder gestelde voorwaarden is voldaan:
- Pijltje links is ingedrukt, terwijl het menu voor de Tab-toets is geopend.
- Pijltje links is ingedrukt, terwijl een <li> met een kop focus had.
Ook hier is het verhaal weer vrijwel hetzelfde als hierboven bij krijgtFocus = geklikt.parentNode.parentNode.parentNode;. Die regel is voor als een <a> focus had op het moment dat Pijltje links werd ingedrukt. Deze regel is voor als een <li> focus heeft.
Daarom staat er één keer minder parentNode er hoeft niet eerst van de <a> naar ouder <li> gegaan te worden, want het object dat bij die <li> hoort zit al in krijgtFocus.
Dit deel van het script wordt alleen uitgevoerd, als aan deze eerder gestelde voorwaarde is voldaan:
- Pijltje links is ingedrukt, terwijl het menu voor de Tab-toets is geopend.
De eerste regel geeft de focus aan de <li> met de kop die hierboven is gevonden. De tweede regel zet die <li> binnen het zichtbare deel van het venster van de browser.
Deze twee regels zijn precies hetzelfde als die bij krijgtFocus.focus(); en daaronder, waar een iets uitgebreidere beschrijving is te vinden. Die regels zijn voor Pijltje omhoog of Pijltje omlaag, deze regel is voor Pijltje links. Maar het geven van focus en op de juiste plaats neerzetten gebeurt op precies dezelfde manier.
Pijltje rechts werkt alleen als de toets wordt ingedrukt, als een <li> met een kop focus heeft. De focus gaat dan naar het eerste item in het submenu dat onder de kop staat.
Dit deel van het script wordt alleen uitgevoerd, als aan deze eerder gestelde voorwaarde is voldaan:
- Pijltje rechts is ingedrukt, terwijl het menu voor de Tab-toets is geopend.
e: is het bij function navigatieVoorTab (e) { aan de code in de functie doorgegeven object, waarin onder andere zit, welke toetsen zijn ingedrukt.
preventDefault(): dit is een van de ingebouwde methoden, die JavaScript gratis en voor niets toevoegt aan het hierboven genoemde object e.
Pijltje rechts wordt voor van alles en nog wat gebruikt. Hiermee wordt de normale werking van deze toets geblokkeerd. Hierdoor wordt een mogelijke botsing tussen de normale werking van deze toets en de werking van deze toets in deze functie voorkomen.
Dit deel van het script wordt alleen uitgevoerd, als aan deze eerder gestelde voorwaarde is voldaan:
- Pijltje rechts is ingedrukt, terwijl het menu voor de Tab-toets is 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.
geklikt: bij var geklikt = e.target, is in variabele geklikt de inhoud van e.target opgeslagen. Hierin zit, in de vorm van een object, het precieze element binnen het menu dat focus had, op het moment dat Pijltje rechts werd ingedrukt.
===: 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.
"LI": achter de drie isgelijktekens staat "LI". Wat voor de isgelijktekens staat moet dus hetzelfde zijn als 'LI'. Oftewel: het element dat focus had op het moment dat naar de toets werd geluisterd, moet een <li> zijn.
Een <li> kan normaal genomen geen focus krijgen, maar aan de <li>'s met een kop is in de html het attribuut tabindex="0" toegevoegd, waardoor deze <li>'s wel focus kunnen krijgen.
{: de code die wordt uitgevoerd, als aan deze else if wordt voldaan, wordt tussen accolades gezet. Het script weet dan, wat bij deze else if hoort. Aan het eind van de code bij deze else if staat de afsluitende }.
De hele regel: als het element dat bij het object in geklikt hoort een <li> is. Omdat alleen <li>'s met een kop focus kunnen krijgen, moet dit dus een <li> met een kop zijn, een <li> boven een submenu.
Dit deel van het script wordt alleen uitgevoerd, als aan deze eerder gestelde voorwaarden is voldaan:
- Pijltje rechts is ingedrukt, terwijl het menu voor de Tab-toets is geopend.
- Pijltje rechts is ingedrukt, terwijl een <li> met een kop focus had.
krijgtFocus: aan het begin van function navigatieVoorTab () is bij krijgtFocus; deze variabele al aangemaakt, maar verder is er nog niets mee gedaan. In krijgtFocus wordt het object opgeslagen dat hoort bij het element uit het menu dat focus moet krijgen.
=: 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.
geklikt: bij var geklikt = e.target, is in variabele geklikt de inhoud van e.target opgeslagen. Hierin zit, in de vorm van een object, het precieze element binnen het menu dat focus had op het moment dat Pijltje rechts werd ingedrukt.
querySelector("ul li"): querySelector() is een zogenaamde 'functie'. Een functie is een stukje in de browser ingebakken code, waarmee je iets kunt doen. Omdat voor deze functie geklikt staat, is de werking van deze functie beperkt tot het element dat bij het object in geklikt hoort: de <li> met de kop die focus had, op het moment dat Pijltje rechts werd ingedrukt.
(querySelector() is eigenlijk geen functie, maar een 'methode'. Dat is een functie die bij een object hoort. Bij een object werkt een functie een klein beetje anders, daarom heet een functie uit een object een 'methode'. In dit geval hoort querySelector() bij het object dat in geklikt zit.)
Met de methode querySelector() wordt het eerste element gevonden dat bij een bepaalde selector hoort. Het element waarnaar wordt gezocht, staat tussen haakjes:
querySelector("ul li")
Er wordt gezocht naar een <li> binnen een <ul>. Het selectiecriterium tussen de haakjes schrijf je op precies dezelfde manier als een css-selector, alleen komt het tussen aanhalingstekens te staan. (Anders weet JavaScript niet dat het om een letterlijk stukje tekst gaat.) Zou je willen zoeken naar het eerste element met class="marietje", dan zou je querySelector(".marietje") gebruiken.
Omdat querySelector() achter geklikt staat, wordt ook het element dat bij het object in geklikt hoort er nog voor gezet. Dat was de <li> met de kop die focus had op het moment dat Pijltje rechts werd ingedrukt.
De volledige selector wordt daarmee: li ul li.
Het eerste element dat aan de selector li ul li voldoet is per definitie de eerste <li> in het submenu onder de <li> met de kop.
De hele regel: sla in krijgtFocus het object op dat hoort bij het eerste item in het submenu dat hoort bij de <li> met de kop die focus had op het moment dat Pijltje rechts werd ingedrukt.
krijgtFocus = (krijgtFocus.firstElementChild.tagName === "A" ? krijgtFocus.firstElementChild : krijgtFocus);
Dit deel van het script wordt alleen uitgevoerd, als aan deze eerder gestelde voorwaarden is voldaan:
- Pijltje rechts is ingedrukt, terwijl het menu voor de Tab-toets is geopend.
- Pijltje rechts is ingedrukt, terwijl een <li> met een kop focus had.
Door het indrukken van Pijltje rechts op een <li> met een kop die focus heeft, wordt het submenu onder die kop binnengegaan. Hierboven bij krijgtFocus = geklikt.querySelector("ul li"); is de eerste <li> in dat submenu gevonden en in de vorm van een object opgeborgen in variabele krijgtFocus.
Nu zijn er twee mogelijkheden: in deze <li> zit ook weer een kop, die bij een onder die <li> vallend submenu hoort, zoals bijvoorbeeld bij de <li> met 'Kop 1 - 1' het geval is. In dat geval moet die <li> de focus krijgen.
Maar als in die <li> een <a> zit, moet niet de <li>, maar de <a> in de <li> de focus krijgen. Anders wordt de link niet gevolgd bij indrukken van Enter, en dit menu is nu juist voor gebruikers van het toetsenbord.
Deze regel kijkt of het eerste kind van de gevonden <li> een <a> is. Als dat zo is, wordt het object in krijgtFocus vervangen door het object dat bij die <a> hoort. Als dat niet zo is, gebeurt er niets en blijft krijgtFocus gewoon hetzelfde.
Dit deel van het script wordt alleen uitgevoerd, als aan deze eerder gestelde voorwaarden is voldaan:
- Pijltje rechts is ingedrukt, terwijl het menu voor de Tab-toets is geopend.
- Pijltje rechts is ingedrukt, terwijl een <li> met een kop focus had.
De eerste regel geeft de focus aan de <li> met de kop, of aan de <a>, die hierboven is gevonden. De tweede regel zet die <li> of <a> binnen het zichtbare deel van het venster van de browser.
Deze twee regels zijn precies hetzelfde als die bij krijgtFocus.focus(); en daaronder, waar een iets uitgebreidere beschrijving is te vinden. Die regels zijn voor Pijltje omhoog of Pijltje omlaag, deze regel is voor Pijltje rechts. Maar het geven van focus en op de juiste plaats neerzetten gebeurt op precies dezelfde manier.
Als je op Shift+Tab drukt, gaat de focus naar het vorige element dat focus kan krijgen. Als het eerste item in het menu focus heeft en er wordt op Shift+Tab gedrukt, kom je buiten het menu terecht (want anders zou het niet het eerste item in het menu zijn...). Dat is niet echt handig, want vaak zal een vorig element een knop van de browser of zoiets zijn. Daarom wordt in dat geval de focus van het allereerste item naar het allerlaatste item in het menu verplaatst.
else if: redelijk aan het begin van function navigatieVoorTab () staat bij if (e.key === "Escape" || e.key === "Esc") { een if met als voorwaarde dat de Escape-toets moet zijn ingedrukt. Vervolgens zijn met else if de Pijltjes-toetsen afgehandeld.
Hier staat weer een andere else if , die als voorwaarde heeft dat het eerste item uit het menu focus heeft én dat Shift+Tab is ingedrukt.
Net als bij de if staat ook hier de voorwaarde weer tussen haakjes.
(geklikt === eersteItem && e.shiftKey && e.key === "Tab"): de voorwaarde waaraan moet worden voldaan, om de code tussen de {} uit te voeren.
geklikt: bij var geklikt = e.target, is in variabele geklikt de inhoud van e.target opgeslagen. Hierin zit, in de vorm van een object, het precieze element binnen het menu dat focus had op het moment dat Pijltje rechts werd ingedrukt.
===: 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.
eersteItem: bij eersteItem = document.querySelector("nav ul li"), is in variabele eersteItem het object dat bij het eerste item uit het menu hoort opgeslagen. In het voorbeeld is dat de <li> met 'Kop 1'.
&&: dit is de manier, waarop je in JavaScript 'en' aangeeft: wat voor de && staat moet waar zijn, én wat achter de && staat moet waar zijn.
e.shiftKey: e is het bij function navigatieVoorTab (e) {, de functie waar deze regel een onderdeel van is, aan de functie doorgegeven object. Als de Shift-toets is ingedrukt, is eigenschap shiftKey van dat object waar.
Omdat de Shift-toets een apart soort toets heeft, is dit één van de toetsen die een eigen naam heeft.
Hier staat dus: als Shift is ingedrukt.
&&: weer het én-teken van JavaScript.
e.key === "Tab": e is het bij function navigatieVoorTab (e) {, de functie waar deze regel een onderdeel van is, aan de functie doorgegeven object. In eigenschap key daarvan zit de naam van de toets die is ingedrukt. En o wonder van wonderschone en loepzuivere logica: 'Tab' is de naam van de Tab-toets.
{: de code die wordt uitgevoerd, als aan deze else if wordt voldaan, wordt tussen accolades gezet. Het script weet dan, wat bij deze else if hoort. Aan het eind van de code bij deze else if staat de afsluitende }.
De hele regel: als het eerste item uit het menu focus had, terwijl Shift én Tab beide werden ingedrukt.
Dit deel van het script wordt alleen uitgevoerd, als aan deze eerder gestelde voorwaarde is voldaan:
- Het eerste item in het menu voor de Tab-toets had focus, terwijl Shift+Tab is ingedrukt.
e: is het bij function navigatieVoorTab (e) { aan de code in de functie doorgegeven object, waarin onder andere zit, welke toetsen zijn ingedrukt.
preventDefault(): dit is een van de ingebouwde methoden, die JavaScript gratis en voor niets toevoegt aan het hierboven genoemde object e.
Als Shift+Tab wordt ingedrukt, geeft de browser automatisch de focus aan het vorige element dat daarvoor in aanmerking komt. Helemaal los van dit script.
Deze functie zorgt ervoor dat de focus naar het laatste item in het menu gaat, als Shift+Tab op het eerste item wordt ingedrukt. Maar daarna voert de browser alsnog de standaardhandeling bij Shift+Tab uit en geeft ook nog 'ns de focus aan het vorige element. Hierdoor komt de focus niet op het laatste item in het menu, maar op het voorlaatste te staan.
preventDefault() voorkomt dat de browser de standaardhandeling bij de Shift+Tab uitvoert. Hierdoor blijft de focus netjes op het laatste item uit het menu staan.
Dit deel van het script wordt alleen uitgevoerd, als aan deze eerder gestelde voorwaarde is voldaan:
- Het eerste item in het menu voor de Tab-toets had focus, terwijl Shift+Tab is ingedrukt.
laatsteItem: in variabele laatsteItem is bij laatsteItem = "nav ul li:last-of-type"; en de daarop volgende regels het bij de laatste <li> uit het menu horende object opgeslagen.
firstElementChild: in deze eigenschap van het hierboven genoemde object zit het eerste kind van de <li>, die bij het object hoort. (Feitelijk zit weer niet dat element zelf erin, maar het object dat bij dat element hoort. Hierdoor is weer allerlei informatie over het element beschikbaar.)
In het menu zitten maar twee soorten <li>'s: die waar een kop (met bijbehorend submenu) en die waar een link in zit. Omdat dit de laatste <li> is, kan er geen submenu onder zitten. Er kan dus alleen een link <a> in de <li> zitten.
Eén van de voorwaarden voor de html is dat de <a> in een <li> het eerste kind van die <li> is. Het eerste kind van de laatste <li> uit het menu moet dus een <a> zijn.
focus(): dit is een van de ingebouwde methoden, die JavaScript gratis en voor niets toevoegt aan het hierboven genoemde object in variabele firstElementChild. Hiermee wordt de focus gegeven aan het bij dat object horende element. Wat in dit geval de <a> in de laatste <li> is.
De hele regel: geef de focus aan de <a> in de laatste <li> van het menu.
Dit deel van het script wordt alleen uitgevoerd, als aan deze eerder gestelde voorwaarde is voldaan:
- Het eerste item in het menu voor de Tab-toets had focus, terwijl Shift+Tab is ingedrukt.
Deze regel is precies hetzelfde als die gelijk hierboven bij laatsteItem.firstElementChild.focus();, alleen wordt hier een andere methode gebruikt: scrollIntoView();. Deze methode plaatst het element - dat is hier de <a> in de laatste <li> van het menu - binnen het zichtbare deel van het venster van de browser. Meestal is dat aan de bovenkant van het venster, maar dat hoeft niet.
Dit is ook de reden dat de menubalk bovenaan het browservenster wordt gesloten, als het menu voor de Tab-toets is geopend. Als <main> helemaal bovenaan het venster wordt gezet, zou anders het bovenste stukje wegvallen onder de menubalk.
Als je op de Tab-toets drukt, gaat de focus naar het volgende element dat focus kan krijgen. Als het laatste item in het menu focus heeft en er wordt op de Tab-toets gedrukt, is dat een element buiten het menu, want anders zou het niet het laatste item zijn. Dat is niet echt handig, want het is maar raden wat voor element dat toevallig is. Daarom wordt in dat geval de focus van het allerlaatste item naar het allereerste item in het menu verplaatst.
else if: redelijk aan het begin van function navigatieVoorTab () staat bij if (e.key === "Escape" || e.key === "Esc") { een if met als voorwaarde dat de Escape-toets moet zijn ingedrukt. Vervolgens zijn met else if de Pijltjes-toetsen en Shift+Tab afgehandeld.
Hier staat weer een andere else if , die als voorwaarde heeft dat het laatste item uit het menu focus heeft én dat de Tab-toets, maar niet de Shift-toets is ingedrukt.
Net als bij de if staat ook hier de voorwaarde weer tussen haakjes.
(geklikt === laatsteItem.firstElementChild && ! e.shiftKey && e.key === "Tab"): de voorwaarde waaraan moet worden voldaan, om de code tussen de {} uit te voeren.
geklikt: bij var geklikt = e.target, is in variabele geklikt de inhoud van e.target opgeslagen. Hierin zit, in de vorm van een object, het precieze element binnen het menu dat focus had op het moment dat de Tab-toets werd ingedrukt.
===: 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.
laatsteItem: in variabele laatsteItem is bij laatsteItem = "nav ul li:last-of-type"; en de daarop volgende regels het bij de laatste <li> uit het menu horende object opgeslagen.
firstElementChild: in deze eigenschap van het hierboven genoemde object zit het eerste kind van de <li>, die bij het object hoort. (Feitelijk zit weer niet dat element zelf erin, maar het object dat bij dat element hoort. Hierdoor is weer allerlei informatie over het element beschikbaar.)
In het menu zitten maar twee soorten <li>'s: die waar een kop (met bijbehorend submenu) en die waar een link in zit. Omdat dit de laatste <li> is, kan er geen submenu onder zitten. Er zit dus een <a> in de <li>.
Eén van de voorwaarden voor de html is dat de <a> in een <li> het eerste kind van die <li> is. Het eerste kind van de laatste <li> uit het menu moet dus een <a> zijn.
&&: dit is de manier, waarop je in JavaScript 'en' aangeeft: wat voor de && staat moet waar zijn, én wat achter de && staat moet waar zijn.
!e.shiftKey: e is het bij function navigatieVoorTab (e) {, de functie waar deze regel een onderdeel van is, aan de functie doorgegeven object. Als de Shift-toets is ingedrukt, is eigenschap shiftKey van dat object waar.
Omdat de Shift-toets een apart soort toets heeft, is dit één van de toetsen die een eigen naam heeft.
Voor de e staat echter nog een !. In JavaScript betekent een uitroepen 'niet'. !e.shiftKey betekent dus: de Shift-toets mag niet zijn ingedrukt.
&&: weer het én-teken van JavaScript.
e.key === "Tab": e is het bij function navigatieVoorTab (e) {, de functie waar deze regel een onderdeel van is, aan de functie doorgegeven object. In eigenschap key daarvan zit de naam van de toets die is ingedrukt. 'Tab' is de naam van de Tab-toets.
{: de code die wordt uitgevoerd, als aan deze else if wordt voldaan, wordt tussen accolades gezet. Het script weet dan, wat bij deze else if hoort. Aan het eind van de code bij deze else if staat de afsluitende }.
De hele regel: als het laatste item uit het menu focus had, terwijl de Tab-toets, maar niet de Shift-toets, werd ingedrukt.
Met Shift+Tab ga je een item omhoog in het menu. Daarom moet expliciet worden opgegeven dat de Shift-toets niet mag zijn ingedrukt. Als je dat niet doet, werkt deze else if ook als Shift+Tab is ingedrukt en zou je ook dan van het laatste item in het menu naar het eerste item teruggaan. Terwijl je met Shift+Tab gewoon naar het voorlaatste item moet gaan.
Dit deel van het script wordt alleen uitgevoerd, als aan deze eerder gestelde voorwaarde is voldaan:
- Het laatste item in het menu voor de Tab-toets had focus, terwijl de Tab-toets, maar niet de Shift-toets, is ingedrukt.
Deze drie regels zijn precies hetzelfde als die bij e.preventDefault(); en daaronder. Daar zijn de regels voor als Shift+Tab wordt ingedrukt terwijl het eerste item in het menu de focus heeft. Hier zijn de regels voor als op Tab-toets wordt gedrukt, terwijl het laatste item de focus heeft.
Deze drie regels betekenen:
* voorkom de standaardwerking van de Tab-toets (anders gaat ook de browser 'n item verder en komt de focus niet op het eerste, maar op het tweede item in het menu te staan);
* zet de focus op het eerste item uit het menu;
* zet het eerste item uit het menu binnen het zichtbare deel van het browservenster.
Samengevat: als het laatste item uit het menu de focus heeft en de Tab-toets wordt ingedrukt, ga dan terug naar het eerste item in het menu.
Een veel uitgebreidere beschrijving van deze drie regels is te vinden bij e.preventDefault(); en de twee daarop volgende regels.
function voorSchermlezers1 () {
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 input#checkbox-voor-schermlezers-1 wordt aan- of uitgevinkt. Het luisteren naar dat aan- of uitvinken wordt geregeld bij checkboxSchermlezers1.addEventListener("change", voorSchermlezers1);.
De code in deze functie regelt, wat er gebeurt bij uit- en aanvinken. Als input#checkbox-voor-schermlezers-1 wordt uitgevinkt, wordt het menu voor schermlezers gesloten. Tevens wordt input#checkbox-voor-schermlezers-2, de tweede <input> voor schermlezers, uitgevinkt en verborgen.
Als deze <input> wordt aangevinkt, wordt de tweede <input> voor schermlezers op het scherm gezet (en worden nog wat andere dingen geregeld, waarover hieronder meer).
function: het sleutelwoord waarmee het begin van een functie wordt aangegeven.
voorSchermlezers1: 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.)
(): 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. Dat gebeurt hier niet.
{: geeft het begin van de code binnen de functie aan. Aan het eind van de functie staat een bijbehorende }.
openMenu: bij openMenu = document.getElementById("checkbox-open-menu"), is in variabele openMenu het element <input id="checkbox-open-menu"> opgeslagen in de vorm van een object. Hierdoor is voor JavaScript allerlei informatie uit die <input> beschikbaar, en kunnen er ook dingen worden veranderd.
checked: in de eigenschap checked van het hierboven genoemde object is opgeslagen, of de <input> is aangevinkt of niet.
=: 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. In dit geval staat voor het isgelijkteken geen variabele, maar een eigenschap van het in openMenu opgeslagen object, dat bij input#checkbox-open-menu hoort. In dit geval wordt die eigenschap veranderd in wat achter het isgelijkteken staat: de <input> wordt aan- of uitgevinkt.
false: omdat dit achter het isgelijkteken staat, wordt dit de nieuwe waarde van de eigenschap checked van het object in openMenu, dat bij input#checkbox-open-menu hoort. Het resultaat hiervan is dat input#checkbox-open-menu wordt uitgevinkt.
Omdat in de css selectors met #checkbox-open-menu:checked nu niet meer werken, sluit het menu voor de schermlezer voor browservensters minimaal 760 px breed, als dat eventueel al geopend was.
De hele regel: vink input#checkbox-open-menu uit, waardoor het menu voor schermlezers in browservensters minimaal 760 px breed sluit, als dat eventueel al was geopend.
De hele regel: vink input#checkbox-voor-smal uit, waardoor het menu voor schermlezers in browservensters smaller dan 760 px sluit, als dat eventueel al was geopend.
checkboxSchermlezers2: bij checkboxSchermlezers2 = document.getElementById("checkbox-voor-schermlezers-2"), is in variabele checkboxSchermlezers2 het element <input id="checkbox-voor-schermlezers-2"> opgeslagen in de vorm van een object. Hierdoor is voor JavaScript allerlei informatie uit die <input> beschikbaar, en kunnen er ook dingen worden veranderd.
checked: in de eigenschap checked van het hierboven genoemde object is opgeslagen, of de <input> is aangevinkt of niet.
=: 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. In dit geval staat voor het isgelijkteken geen variabele, maar een eigenschap van het in checkboxSchermlezers2 opgeslagen object, dat bij input#checkbox-voor-schermlezers-2 hoort. In dit geval wordt die eigenschap veranderd in wat achter het isgelijkteken staat: de <input> wordt aan- of uitgevinkt.
false: omdat dit achter het isgelijkteken staat, wordt dit de nieuwe waarde van de eigenschap checked van het object in checkboxSchermlezers2, dat bij input#checkbox-voor-schermlezers-2 hoort. Het resultaat hiervan is dat input#checkbox-voor-schermlezers-2 wordt uitgevinkt.
De hele regel: vink input#checkbox-voor-schermlezers-2 uit. Als deze <input> niet was aangevinkt, gebeurt er gewoon niets.
Voor TalkBack op Android moet minimaal een plekje van 1 x 1 px van een aankruisvakje (<input type="checkbox"> binnen het venster staan, niet verborgen onder een ander element, anders kan het aankruisvakje niet worden uit- of aangevinkt. (Meer hierover is te vinden bij Probleem: in TalkBack op Android moet een link en dergelijke binnen het venster staan.) Alle andere schermlezers kunnen een <input> ook aan- of uitvinken, als die buiten het scherm staat.
Het probleem hiervan: als iemand per ongeluk het aankruisvakje aanraakt, opent het menu voor schermlezers. Daarom zijn er twee aankruisvakjes. Het tweede aankruisvakje wordt pas op het scherm gezet, als het eerste is aangevinkt. De kans dat deze tweede aankruisvakjes per ongeluk - en ook nog in de juiste volgorde - worden aangeraakt, is nihil.
Als het menu voor schermlezers al is geopend en het eerste aankruisvakje wordt uitgevinkt, sluit het menu. Maar het tweede aankruisvakje is dan nog steeds aangevinkt. Het menu opent nu al, als alleen het eerste aankruisvakje wordt aangevinkt. En dus ook, als alleen dat eerste aankruisvakje per ongeluk wordt aangeraakt.
Daarom wordt hier ook het tweede aankruisvakje uitgevinkt. Nu moet het scherm weer twee keer worden aangeraakt, voordat het menu onbedoeld openklapt. Dit is wel vervelend voor gebruikers van een schermlezer, maar zolang TalkBack niet fatsoenlijk werkt op dit gebied, is hier weinig aan te doen.
body: bij body = document.getElementsByTagName("body")[0], is het element <body> in de vorm van een object opgeslagen in variabele body. Hierdoor is allerlei informatie uit <body> toegankelijk voor het script, en kan het script allerlei dingen met <body> doen.
classList: dit is zo'n stukje informatie uit body. In classList zitten alle classes die bij het element <body> aanwezig zijn.
remove("xschermlezerx"): met methode remove() kan een class worden verwijderd uit classList. Deze class wordt ook daadwerkelijk verwijderd uit de html. De te verwijderen class staat tussen aanhalingstekens en haakjes.
Als het menu voor schermlezers is geopend, voegt het script class 'xschermlezerx' toe aan het element <body>. Als het menu weer wordt gesloten, verwijdert het menu deze class weer.
Door het toevoegen en verwijderen van een class aan <body>, kan in de css in selectors .xschermlezerx worden gebruikt.
Deze class is niet te zien in de gewone code, maar alleen in de Gegenereerde code.
De naam 'xschermlezerx' is tamelijk vreemd, maar dat maakt de kans dat deze naam al in gebruik is kleiner. Als je de naam van class 'xschermlezerx' wilt veranderen, moet je die in het script en in de css veranderen. Dit is één van de plaatsen in het script, waar die naam gewijzigd moet worden.
len: bij len, is deze variabele al aangemaakt, maar er is nog niets mee gedaan. Hier gaat er iets in worden opgeborgen.
=: 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.
length: variabele inputsVoorSmal heeft zelf ook wat informatie, onder andere het aantal items dat erin zit opgeslagen. In de eigenschap length zit dat aantal. Omdat er 32 objecten - één voor elke <input> in het menu - in zitten, is de lengte van inputsVoorSmal 32.
De hele regel: sla in len het aantal objecten in inputsVoorSmal, oftewel het aantal <input>'s in het menu, op. Omdat dat aantal eerder door het script is vastgesteld, wordt dit aantal automatisch aangepast, als het aantal <input>'s in het menu verandert door het toevoegen of verwijderen van submenu's in de html.
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. (In de echte code staat de laatste } lager, achter wat er waarmee moet gebeuren.)
Hier eerst het deel dat ervoor zorgt alle 32 <input>'s in het menu worden behandeld.
(: met dit haakje opent het deel dat ervoor zorgt, dat alle 32 <input>'s aan de beurt komen. Aan het eind van de regel staat de bijbehorende ), waardoor het script weet dat dit het einde van dit deel van de code is.
i = 0;: 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. In dit geval is dat resultaat heel simpel: het is het getal 0.
De variabele i is al eerder aangemaakt bij i, en kan daarom hier nu gewoon worden gebruikt.
Omdat i een variabele is, kan de waarde ervan veranderen, variëren. Wat later in de regel ook gaat gebeuren: elke keer als een <input> in het menu 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' z'n beginwaarde krijgt, hier eindigt.
i++: eerst het laatste deel, het middelste deel komt gelijk hieronder.
Elke keer als een <input> is afgehandeld, 1 optellen bij 'i'. Omdat programmeurs liederlijk lui zijn, wordt 'er 1 bij optellen' afgekort 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 31 is, betekent i++: i wordt 31 + 1 ('32' dus). En meer dan 32 wordt het niet, vanwege redenen die gelijk hieronder staan.
i < len;: het middelste deel.
i bevat een getal. Als er nog geen enkele <input> is verwerkt, is dat getal 0, want dat is hierboven opgegeven.
Het teken < betekent: kleiner dan.
In len is iets hoger bij len = inputsVoorSmal.length; het aantal <input>'s in het uitklapmenu opgeslagen. In het voorbeeld is dat 32. Eigenlijk staat hier dus: i < 32;
De ; aan het einde geeft aan dat dit stukje code, de voorwaarde waaraan moet worden voldaan, hier eindigt.
In gewone mensentaal staat hier: zolang teller i kleiner is dan 32.
Als niet meer aan deze voorwaarde wordt voldaan, als teller i niet meer kleiner is dan de 32 in len, stop dan met het uitvoeren van de code die tussen de {} staat. En omdat 32 even groot is als het aantal <input>'s in het menu dat moet worden bekeken, geeft dit een mogelijkheid om elke <input> éé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 niveau in het menu 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 niveau van het menu íé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 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 <input> in het menu 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 31e ronde heeft i de waarde 32. Waarmee i niet meer kleiner is dan de 32 in len, het aantal <input>'s in het menu.
Omdat niet meer aan de voorwaarde i < len wordt voldaan, wordt gestopt met het uitvoeren van de code tussen de {}. En dat komt goed uit, want alle <input>'s in het menu zijn precies allemaal één keer aan de beurt gekomen. Niet meer, niet minder. Bij allemaal is de tussen de {} staande code één keer toegepast.
(Wat mogelijk wat verwarrend is: het script begint te tellen met 0. De eerste <input> heeft dus volgnummer 0, en de 32e <input> heeft volgnummer 31. Ook al stopt de for-lus bij 31, er worden dus toch 32 <input>'s bekeken.)
De code tussen de haakjes in gewone taal: zet teller i op 0 als de for de eerste keer wordt uitgevoerd. Herhaal de for zolang teller i lager is dan de 32 in len. Verhoog teller i elke keer als de for wordt uitgevoerd met 1.
[i]: in inputsVoorSmal zitten 32 objecten, één voor elke <input>. Deze objecten kun je benaderen met een volgnummer. Het eerste object is nummer 0, het tweede nummer 1, en het laatste nummer 31 (daar is weer de irritante gewoonte van de computer om tellen met 0 te beginnen...).
Dat volgnummer staat tussen twee teksthaken [], dat hoort gewoon zo.
In de for-lus wordt geregeld dat de inhoud van variabele i van 0 naar 31 loopt: elke keer als de code binnen de for-lus is uitgevoerd, wordt i met 1 verhoogd, tot alle 32 <input>'s zijn afgehandeld.
checked: elke <input> is in de vorm van een object opgeborgen in inputsVoorSmal. In de eigenschap checked van dat object is opgeslagen, of de <input> is aangevinkt of niet.
=: 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. In dit geval staat voor het isgelijkteken geen variabele, maar een eigenschap van het in inputsVoorSmal opgeslagen object, dat aan de beurt is. In dit geval wordt die eigenschap veranderd in wat achter het isgelijkteken staat: de <input> wordt aan- of uitgevinkt.
false: omdat dit achter het isgelijkteken staat, wordt dit de nieuwe waarde van de eigenschap checked van het object in inputsVoorSmal, dat aan de beurt is. Het resultaat hiervan is dat de bij dat object horende <input> wordt uitgevinkt.
Omdat alle <input>'s in het menu aan de beurt zijn gekomen, zijn alle <input>'s uitgevinkt.
function voorSchermlezers2 () {
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 input#checkbox-voor-schermlezers-2 wordt aan- of uitgevinkt. Het luisteren naar dat aan- of uitvinken wordt geregeld bij checkboxSchermlezers2.addEventListener("change", voorSchermlezers2);
De code in deze functie regelt, wat er gebeurt bij uit- en aanvinken. Als input#checkbox-voor-schermlezers-2 wordt uitgevinkt, wordt het menu voor schermlezers gesloten. Als de <input> wordt aangevinkt, wordt het menu voor schermlezers geopend (en worden nog wat andere dingen geregeld, waarover hieronder meer).
Deze <input> kan alleen worden aangevinkt, als eerst input#checkbox-voor-schermlezers-1 al is aangevinkt.
function: het sleutelwoord waarmee het begin van een functie wordt aangegeven.
voorSchermlezers2: 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.)
(): 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. Dat gebeurt hier niet.
{: geeft het begin van de code binnen de functie aan. Aan het eind van de functie staat een bijbehorende }.
openMenu: bij openMenu = document.getElementById("checkbox-open-menu"), is in variabele openMenu het element <input id="checkbox-open-menu"> opgeslagen in de vorm van een object. Hierdoor is voor JavaScript allerlei informatie uit die <input> beschikbaar, en kunnen er ook dingen worden veranderd.
checked: in de eigenschap checked van het hierboven genoemde object is opgeslagen, of de <input> is aangevinkt of niet.
=: 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. In dit geval staat voor het isgelijkteken geen variabele, maar een eigenschap van het in openMenu opgeslagen object, dat bij input#checkbox-open-menu hoort. In dit geval wordt die eigenschap veranderd in wat achter het isgelijkteken staat: de <input> wordt aan- of uitgevinkt.
(: dit haakje is hier niet echt nodig, maar het maakt de code iets duidelijker: alles tussen de () moet eerst worden uitgewerkt, en het resultaat daarvan wordt dan in openMenu.checked gestopt. Als het resultaat waar is, wordt input#checkbox-open-menu aangevinkt, als het resultaat niet waar is, wordt de <input> uitgevinkt.
checkboxSchermlezers1.checked: in variabele checkboxSchermlezers1 is bij checkboxSchermlezers1 = document.getElementById("checkbox-voor-schermlezers-1"), het element <input id="checkbox-voor-schermlezers-1"> in de vorm van een object opgeslagen. Hierdoor is allerlei informatie uit die <input> beschikbaar voor JavaScript.
In eigenschap checked van het hierboven genoemde object is opgeslagen, of de <input> is aangevinkt of niet. Als de <input> is aangevinkt, is checked waar, anders is het niet waar.
&&: dit is de manier, waarop je in JavaScript 'en' aangeeft: wat voor de && staat moet waar zijn, én wat achter de && staat moet waar zijn.
checkboxSchermlezers2.checked: in variabele checkboxSchermlezers2 is bij checkboxSchermlezers2 = document.getElementById("checkbox-voor-schermlezers-2"), het element <input id="checkbox-voor-schermlezers-2"> in de vorm van een object opgeslagen. Hierdoor is allerlei informatie uit die <input> beschikbaar voor JavaScript.
In eigenschap checked van het hierboven genoemde object is opgeslagen, of de <input> is aangevinkt of niet. Als de <input> is aangevinkt, is checked waar, anders is het niet waar.
): het afsluitende haakje bij het openingshaakje (.
;: met de puntkomma wordt in JavaScript een regel afgesloten. Het is te vergelijken met een gewone punt in een tekst.
De hele regel: als input#checkbox-voor-schermlezers-1 én input#checkbox-voor-schermlezers-2 zijn aangevinkt, is wat achter het isgelijkteken staat waar en wordt input#checkbox-open-menu aangevinkt.
Als input#checkbox-voor-schermlezers-1 óf input#checkbox-voor-schermlezers-2 niet is aangevinkt, is wat achter het isgelijkteken staat niet waar en wordt input#checkbox-open-menu uitgevinkt.
Als input#checkbox-open-menu is aangevinkt, wordt via css geregeld dat het menu voor schermlezers in browservensters minimaal 760 px breed wordt geopend. Als de <input> niet is aangevinkt, wordt het menu niet geopend. Als het al open was, wordt het gesloten.
Deze regel is precies hetzelfde als die hierboven bij openMenu.checked = (checkboxSchermlezers1.checked && checkboxSchermlezers2.checked);, alleen staat nu in variabele checkboxVoorSmal voor het isgelijkteken het object dat bij input#checkbox-voor-smal hoort. Als die <input> is aangevinkt, wordt via css geregeld dat het menu voor schermlezers in browservensters smaller dan 760 px wordt geopend.
In variabele len wordt het aantal <input>'s binnen het menu opgeslagen. In het voorbeeld zijn dit er 32. Een uitgebreidere beschrijving van deze regel is iets hierboven te vinden bij len = inputsVoorSmal.length;.
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.
openMenu: bij openMenu = document.getElementById("checkbox-open-menu"), is in variabele openMenu het element <input id="checkbox-open-menu"> opgeslagen in de vorm van een object. Hierdoor is voor JavaScript allerlei informatie uit die <input> beschikbaar, en kunnen er ook dingen worden veranderd.
checked: in de eigenschap checked van het hierboven genoemde object is opgeslagen, of de <input> is aangevinkt of niet.
{: de code die wordt uitgevoerd, als aan deze if 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 voorwaarde tussen de haakjes is dus alleen waar, als bij input#checkbox-voor-schermlezers-1 is gekozen om het menu voor schermlezers te openen, en als deze keuze bij input#checkbox-voor-schermlezers-2 is bevestigd.
Dit deel van het script wordt alleen uitgevoerd, als aan deze eerder gestelde voorwaarde is voldaan:
- input#checkbox-open-menu moet zijn aangevinkt.
body: bij body = document.getElementsByTagName("body")[0], is het element <body> in de vorm van een object opgeslagen in variabele body. Hierdoor is allerlei informatie uit <body> toegankelijk voor het script, en kan het script allerlei dingen met <body> doen.
classList: dit is zo'n stukje informatie uit body. In classList zitten alle classes die bij het element <body> aanwezig zijn.
add("xschermlezerx"): met methode add() kan een class worden toegevoegd aan classList. Deze class wordt ook daadwerkelijk toegevoegd in de html, waardoor deze in de css in selectors kan worden gebruikt: .xschermlezerx. De toe te voegen class staat tussen aanhalingstekens en haakjes.
Omdat input#checkbox-open-menu is aangevinkt (een voorwaarde van de if, waar deze regel bij hoort), is het menu voor schermlezers geopend. Feitelijk is dat echter hetzelfde menu als dat wat in browservensters smaller dan 760 px wordt geopend.
Door het toevoegen van class 'xschermlezerx' aan <body> kunnen met behulp van css nog wat aanpassingen speciaal voor schermlezers worden gedaan.
Deze class is niet te zien in de gewone code, maar alleen in de Gegenereerde code.
De naam 'xschermlezerx' is tamelijk vreemd, maar dat maakt de kans dat deze naam al in gebruik is kleiner. Als je de naam van class 'xschermlezerx' wilt veranderen, moet je die in het script en in de css veranderen. Dit is één van de plaatsen in het script, waar die naam gewijzigd moet worden.
Dit deel van het script wordt alleen uitgevoerd, als aan deze eerder gestelde voorwaarde is voldaan:
- input#checkbox-open-menu moet zijn aangevinkt.
Deze regel zorgt ervoor dat de code tussen de {} achter de for-lus voor elke <input> in het menu precies één keer wordt uitgevoerd. Een uitgebreide beschrijving van de werking ervan staat bij for (i = 0; i < len; i++) {, een regel die bij function voorSchermlezers1 () hoort.
Deze regel zit binnen een for-lus wordt voor elke <input> in het menu één keer uitgevoerd.
Dit deel van het script wordt alleen uitgevoerd, als aan deze eerder gestelde voorwaarde is voldaan:
- input#checkbox-open-menu moet zijn aangevinkt.
Ook deze regel werkt weer hetzelfde als die in function voorSchermlezers1() bij inputsVoorSmal[i].checked = false;, waar een uitgebreidere beschrijving is te vinden. Elk bij een <input> in het menu horend object in variabele inputsVoorSmal wordt één keer bekeken. Het enige verschil met de eerdere regel: daar worden de <input>'s uitgevinkt, hier worden ze aangevinkt.
Het menu voor schermlezers is eigenlijk een aangepaste versie van het menu in browservensters smaller dan 760 px. In zo'n menu voor smalle vensters kan de gebruiker een submenu openen, door een <input> aan te raken of aan te klikken. Door die aanraking of klik wordt de <input> aangevinkt en opent het bijbehorende submenu. Op dezelfde manier kan het submenu weer worden gesloten.
Dat aanvinken van de (in het voorbeeld 32) <input>'s wordt hier in één keer voor allemaal door het script gedaan. Als de schermlezer het menu voorleest, is hierdoor op het scherm te zien, wat er wordt voorgelezen.
Bovendien vangt dit het probleem van schermlezer TalkBack op Android op: voor TalkBack op Android moet minimaal een plekje van 1 x 1 px van elke link, <input>, en dergelijke binnen het browservenster staan, niet verborgen onder een ander element. Anders werken ze stomweg niet. (Meer hierover is te vinden bij Probleem: in TalkBack op Android moet een link en dergelijke binnen het venster staan.)
Door alle submenu's te openen, staat de hele handel binnen het browservenster. Als de schermlezer het menu voorleest, scrolt dat automatisch naar beneden (of naar boven), dus elke link die wordt voorgelezen staat binnen het venster. Waarmee het probleem voor TalkBack is opgelost.
}: dit is de afsluitende accolade van de code die bij de hierboven genoemde if hoort.
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 code tussen accolades gezet. Het script weet dan, wat bij deze else hoort. Aan het eind van de code bij deze else staat de afsluitende }.
Dit deel van het script wordt alleen uitgevoerd, als aan deze eerder gestelde voorwaarde is voldaan:
- input#checkbox-open-menu mag niet zijn aangevinkt.
body: bij body = document.getElementsByTagName("body")[0], is het element <body> in de vorm van een object opgeslagen in variabele body. Hierdoor is allerlei informatie uit <body> toegankelijk voor het script, en kan het script allerlei dingen met <body> doen.
classList: dit is zo'n stukje informatie uit body. In classList zitten alle classes die bij het element <body> aanwezig zijn.
remove("xschermlezerx"): met methode remove() kan een class worden verwijderd uit classList. Deze class wordt ook daadwerkelijk verwijderd uit de html. De te verwijderen class staat tussen aanhalingstekens en haakjes.
Als het menu voor schermlezers is geopend, voegt het script class 'xschermlezerx' toe aan het element <body>. Deze regel wordt alleen uitgevoerd, als het menu voor schermlezers is (of wordt) gesloten. Door het verwijderen van de class, zullen de selectors in de css die die class gebruiken, nu niet meer werken. Wat precies de bedoeling is.
Deze class is niet te zien in de gewone code, maar alleen in de Gegenereerde code.
De naam 'xschermlezerx' is tamelijk vreemd, maar dat maakt de kans dat deze naam al in gebruik is kleiner. Als je de naam van class 'xschermlezerx' wilt veranderen, moet je die in het script en in de css veranderen. Dit is één van de plaatsen in het script, waar die naam gewijzigd moet worden.
Dit deel van het script wordt alleen uitgevoerd, als aan deze eerder gestelde voorwaarde is voldaan:
- input#checkbox-open-menu mag niet zijn aangevinkt.
Deze twee regels zijn precies hetzelfde als die iets hoger in function voorSchermlezers2 (), alleen worden de vinkjes bij de 32 <input>'s in het menu nu weggehaald.
Als het menu voor schermlezers is geopend, heeft het script alle 32 <input>'s aangevinkt, waardoor het menu volledig is geopend. Nu het menu is gesloten, of gesloten wordt, moeten die vinkjes weer worden weggehaald.
De bovenste van deze twee regels zorgt dat elk van de 32 <input>'s in het menu precies één keer aan de beurt komt.
De tweede regel binnen de for-lus verwijdert het vinkje bij de <input>.