Skip links en inhoudsopgave

Uitleg knoppenbalk met bij elke knop een te openen paneel

Laatst aangepast: .

Aangeklikte knop met geopend bijbehorend paneel.

Korte omschrijving

Als een knop in een knoppenbalk wordt geselecteerd, wordt een bijbehorend paneel met tekst (of iets anders) getoond.

BELANGRIJK

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

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

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

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

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

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

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

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

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

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

Opmerkingen

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

Dit voorbeeld is gemaakt op een systeem met Linux (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

Bovenaan de pagina staat een rij gewone radioknoppen met bijbehorende <label>'s. De radioknoppen zelf zijn verborgen, omdat ze nou niet echt heel erg mooi zijn. Wat je ziet, zijn de bij de radioknoppen horende opgeleukte <label>'s.

De radioknoppen zijn niet volledig afwezig, je ziet ze alleen niet. Als ze volledig afwezig zouden zijn, werkt het niet in schermlezer TalkBack op Android in oudere versies dan 8.1. Ze zijn slechts 1 x 1 px groot en staan op de bijbehorende <label>'s, waar ze onzichtbaar lelijk mogen staan te wezen.

Omdat het om een gewone serie radioknoppen gaat (ze zien er alleen wat anders uit), zijn de knoppen relatief makkelijk te bedienen met toetsenbord of schermlezer.

Onder elke knop met bijbehorend <label> staat een <div> met tekst. Deze <div> is normaal genomen met display: none; verborgen, maar zodra een radioknop wordt geselecteerd, wordt de daaronder zittende <div> met het paneel getoond. Ook wordt dan de <label> bij die knop van kleur veranderd, zodat duidelijk is, bij welke knop heet paneel hoort.

Onder de laatste radioknop zit geen <div>: als deze wordt geselecteerd, wordt geen paneel getoond, maar worden ze juist allemaal gesloten. Dat is niet onbelangrijk, want de <div>'s met de panelen komen gewoon over andere tekst en dergelijke heen te staan.

Als een radioknop wordt geselecteerd, leest niet elke schermlezer de tekst uit de bijbehorende <div> automatisch voor. Bij sommige schermlezers moet daarvoor eerst de Tab-toets worden ingedrukt. Gebruikers van de Tab-toets moeten, als ze tekst willen kopiëren of zo, de <div> met tekst binnen kunnen gaan.

Normaal genomen bezoekt de Tab-toets alleen links, knoppen, tekstvelden, en dergelijke. Een gewone <div> wordt genegeerd. Door de <div> een tabindex="0" te geven, werkt de Tab-toets toch.

Mensen die een muis of een touchscreen gebruiken, zien duidelijk op de laatste knop 'Paneel sluiten' staan, en kunnen dus makkelijk een paneel sluiten, als ze de rest van de pagina willen zien. Voor schermlezers en gebruikers van de Tab-toets ligt dat wat lastiger.

Als een <div> is geopend en de Tab-toets wordt ingedrukt, wordt de <div> binnengegaan. Als nogmaals de Tab-toets wordt ingedrukt, gaan sommige browsers naar de volgende radioknop. Maar andere verlaten de radioknoppen en gaan gelijk naar het volgende interactieve punt, als dat er is. In dat geval wordt de geopende <div> niet verborgen en verbergt een deel van de rest van de pagina.

Schermlezers kunnen met behulp van sneltoetsen en dergelijke delen van een pagina overslaan. Een veel gebruikte manier van navigeren is het alleen bezoeken van de kopregels, de <h>'s. Als een <div> is geopend en de schermlezer gaat naar de volgende <h>, blijft het paneel geopend en verbergt een deel van de pagina. Schermlezers kunnen ook gelijk heel specifiek naar een <h1> gaan, of naar bepaalde 'landmarks', zoals <main>. Ook in dat geval blijft mogelijk een <div> geopend. Onder de <div> met het paneel zittende tekst wordt gewoon voorgelezen, maar het kan heel verwarrend zijn, als je dit tekst niet ziet. Niet elke gebruiker van een schermlezer is volledig blind, en je kunt ook met meer mensen een site bekijken, die niet allemaal een schermlezer gebruiken.

Daarom wordt wat JavaScript gebruikt om dit op te lossen. Zonder JavaScript werkt de hele constructie ook nog steeds, maar dan kan een geopende <div> alleen worden gesloten, door de laatste radioknop te selecteren.

Zodra een <div> wordt getoond, wordt met behulp van JavaScript een extra <h1> bovenaan de pagina zichtbaar gemaakt. Althans: je ziet die nauwelijks, maar voor schermlezers is dat voldoende. Binnen die <h1> zit een gewone <button> met als tekst de melding dat er nog een paneel is geopend. Als op die <button> wordt gedrukt, worden alle panelen gesloten. Deze <button> is dus alleen aanwezig, als een <div> is geopend.

Omdat de <h1> gelijk onder <main> staat, wordt deze altijd gevonden (en daarmee de erin zittende <button>, waarom het eigenlijk gaat.) Als een schermlezer gelijk naar <main> gaat, staat daar gelijk onder de <h1> met de <button>. Als een schermlezer de <h>'s afloopt, of gelijk naar de <h1> gaat, geldt hetzelfde.

Zodra de <button> is ingedrukt, wordt de <h1> met de <button> met display: none; weer verborgen en staat er weer - zoals het eigenlijk hoort - één <h1>: de echte kopregel.

Een <button> wordt gewoon door de Tab-toets bezocht, dus ook als een gebruiker van de Tab-toets de radioknoppen verlaat, terwijl een paneel is geopend, zal de <button> worden bezocht: de <button> krijgt de focus. In dat geval wordt de <button> met de melding dat er nog een paneel is geopend wel leesbaar op het scherm gezet. Door het indrukken van Enter of de Spatiebalk kan het paneel worden gesloten. (En ook door een gewone klik of aanraking van de <button>.)

Daarnaast wordt elke <div> nog verborgen, als op Escape wordt gedrukt.

Als je de muis niet kunt (of wilt) gebruiken, of als je de tekst niet goed kunt lezen, zal mogelijk niet gelijk duidelijk zijn, hoe deze constructie werkt. Daarom staat aan het begin van de constructie een korte uitleg. Die uitleg staat normaal genomen buiten het scherm en is daardoor onzichtbaar, maar schermlezers lezen hem gewoon voor. De uitleg staat in een <div> met tabindex="0", waardoor de <div> de focus kan krijgen, als de Tab-toets wordt ingedrukt. Omdat de <div> buiten het scherm staat, kan dat alleen met behulp van de Tab-toets, dus de lay-out wordt verder niet verstoord. Zodra de <div> met de uitleg de focus heeft, wordt deze binnen het scherm gezet, waardoor de uitleg zichtbaar wordt. Als de Tab-toets nogmaals wordt ingedrukt, heeft de <div> de focus niet meer. De uitleg wordt dan weer buiten het scherm gezet en is daardoor weer onzichtbaar.

Als je ziet, hoeveel extra voorzieningen nodig zijn om deze constructie enigszins toegankelijk te maken, dan begrijp je, waarom veel mensen afraden dit soort constructies te gebruiken. Mogelijk zijn er andere, simpeler oplossingen, waarbij minder aanpassingen nodig zijn.

Voorwaarden van het JavaScript waaraan html en css moeten voldoen

Voor dit voorbeeld wordt gebruik gemaakt van JavaScript. Zonder JavaScript werkt het ook, maar is dan minder toegankelijk voor gebruikers van schermlezers en Tab-toets.

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

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 Edge, 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 en Edge gebruiken Blink.)

Internet Explorer: -ms-, naar de maker: Microsoft.

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. Als bijvoorbeeld Safari -webkit-appearance herkent, zal dit worden overruled door appearance, zodra Safari dit gaat herkennen, omdat het er later in staat. Dat ze er beide in staan, is dus geen enkel probleem.

In dit voorbeeld wordt appearance gebruikt.

appearance

Op dit moment moet je nog het volgende schrijven:

{-webkit-appearance: ...; appearance: ...;}

In de toekomst kun je volstaan met:

{appearance: ...;}

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 niet meer gebruikt. Nieuwe, experimentele css-eigenschappen zitten inmiddels 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.

Semantische elementen en WAI-ARIA

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

Semantische elementen

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

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

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

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

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

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

<header>

Een <header> gedraagt zich als een gewone <div>, maar dan een <div> met een semantische betekenis: de header van de pagina of een onderdeel daarvan.

Een <header> mag vaker op één pagina worden gebruikt. De <header> hoort altijd bij de eerste voorouder, die een <body>, <main>, <article>, <aside>, <nav>, <section>, <blockquote>, <details>, <dialog>, <fieldset>, <figure> of <td> 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 flauwekultekst).

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

WAI-ARIA-codes

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

Er worden in dit voorbeeld drie WAI-ARIA-codes gebruikt: aria-label, aria-hidden en role="radiogroup".

aria-label

div#container, waarin de hele constructie met de panelen zit, is met behulp van role="radiogroup" (zoals iets hieronder beschreven) veranderd in een overkoepelend element voor een groep bij elkaar horende radioknoppen. Zo'n element moet een naam krijgen, zodat die door schermlezers voorgelezen kan worden:

<div id="container" role="radiogroup" aria-label="Knoppenbalk met panelen">

Op het scherm zie je hier niets van, maar schermlezers kunnen dit in principe voorlezen. In principe, want geen van de geteste schermlezers blijkt dit in de praktijk voor te lezen. Kwaad kan het niet, en ooit zullen ze dit mogelijk wel gaan doen.

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 de <label>'s staat een driehoekje ▼ om aan te geven dat er iets geopend kan worden:

<label for="radio-1">Paneel 1 <span aria-hidden=<h1 id="kop" tabindex="-1">Knoppenbalk met bij elke knop een te openen paneel</h1>"true">▼</span></label>

Dit driehoekje is gewoon een utf-8-code, net zoals een gewone letter. Daarom zou het ook gelijk achter 'Paneel 1' kunnen staan, niet in een aparte <span>:

<label for="radio-1">Paneel 1 ▼</label>

Schermlezers zouden dit dan echter voorlezen met de officiële naam 'Black down-pointing triangle'. Een weinig zinvolle toevoeging, die alleen maar tot verwarring zou leiden. Daarom wordt het driehoekje in een <span> gezet, die met aria-hidden wordt verborgen voor schermlezers.

role="radiogroup"

Als geen <fieldset> of zoiets wordt gebruikt voor een groep radioknoppen, maar, zoals hier, een gewone <div>, geef je hiermee aan dat er binnen het element een groep bij elkaar horende radioknoppen zit:

<div id="container" role="radiogroup" aria-label="Knoppenbalk met panelen">

Afhankelijk van schermlezer en instelling daarvan wordt dit dan door de schermlezer aangekondigd. Op het scherm is hier verder niets van te zien.

Tabindex en 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.

Dat is ook, waar het in dit voorbeeld voor wordt gebruikt:

<h1 id="kop" tabindex="-1">Knoppenbalk met bij elke knop een te openen paneel</h1>

Als één van de radioknoppen wordt geselecteerd en het bijbehorende paneel wordt getoond, zouden gebruikers van de Tab-toets of een schermlezer de groep radioknoppen al kunnen verlaten, terwijl nog een paneel is geopend. Hierdoor zou een deel van de pagina worden verborgen.

Daarom wordt in dat geval gelijk aan het begin van <main< een extra sluitknop getoond. Dit wordt geregeld met behulp van JavaScript. Zodra deze knop wordt ingedrukt, sluit het geopende paneel. Sommige schermlezers gaan dan echter terug naar de radioknoppen, in plaats van dat ze bij <main> verder gaan met voorlezen. Daarom geeft het script de focus aan de <h1> bovenin <main>.

Normaal genomen zou dat niet kunnen, maar door het gebruik van tabindex="‑1" kan het wel.

tabindex="0"

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

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

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

In het voorbeeld wordt dit gebruikt bij de korte uitleg:

<div id="uitleg" tabindex="0">

div#uitleg staat normaal genomen onzichtbaar buiten het scherm. Maar zodra door het indrukken van de Tab-toets div#uitleg de focus krijgt, wordt de <div> binnen het scherm gezet en daardoor zichtbaar. Na het nogmaals indrukken van de Tab-toets verliest de <div> de focus weer, waardoor deze weer buiten het scherm komt te staan en weer onzichtbaar wordt.

De <h2> bovenaan elke <div> met tekst, bovenaan elk paneel, heeft ook een tabindex:

<h2 tabindex="0">Paneel 1</h2>

Bij sommige schermlezers wordt de inhoud van het paneel pas voorgelezen, als de Tab-toets is ingedrukt. Als de <h2> geen tabindex heeft, wordt de Tab-toets genegeerd.

Als gebruikers van de Tab-toets tekst uit het paneel willen kopiëren of zo, moeten ze het paneel binnengaan om de tekst te kunnen selecteren. Dat kan met behulp van de Tab-toets, maar ook weer alleen als de <h2> een tabindex heeft.

tabindex="..."

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

Een tabindex met een positief nummer wordt 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 dat ook voor een touchpad.

:hover

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

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

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

Bij gebruik van een muis is er een verschil tussen hoveren en klikken, maar op een touchscreen is dat verschil er niet: je raakt een touchscreen aan of niet. Dat levert vooral soms problemen op, als bij een element :hover én klikken worden gebruikt, zoals bij een link die bij hoveren erover verkleurt. Soms brengt dit grote problemen met zich mee, hier gaat het maar om een kleinigheid: de onderstreping blijft staan, als de laatste knop met 'Paneel sluiten' wordt aangeraakt. Dit is simpel op te lossen. De oplossing vind je bij #container input:last-of-type:checked + label.

:focus

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

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

Er is echter 'n tweede manier om naar links, invoervelden, en dergelijke te gaan: met behulp van de Tab-toets kun je naar links, invoervelden, en dergelijke 'springen'. (Over het gebruik van de Tab-toets staat meer bij 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 worden de kleuren van de <label> met focus van zwart op wit veranderd in wit op zwart. Omdat daardoor al duidelijk genoeg is, welk <label> de focus heeft, kan het (foeilelijke) kadertje veilig worden verwijderd.

: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 fluitend of met bloed, zweet en tranen zitten intypen.

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

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

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

Als je met behulp van JavaScript elementen invoegt (zoals in dit voorbeeld gebeurt), zie je die alleen in deze gegenereerde code.

De code aanpassen aan je eigen ontwerp

Toegankelijkheid en zoekmachines

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

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

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

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

Enkele tips die helpen bij toegankelijkheid:

Getest in

Laatst gecontroleerd op 22 mei 2021.

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

Dit voorbeeld is getest op de volgende systemen:

Desktopcomputers

Windows 7 (1280 x 1024 px, resolution 96 ppi):
Firefox, Google Chrome, Internet Explorer 11 en Edge, in grotere en kleinere browservensters.

OS X 10.14.6 ('Mojave') (1680 x 1050 px, resolution: 96 ppi, device-pixel-ratio: 1):
Firefox, Safari, Google Chrome en Microsoft Edge, in grotere en kleinere browservensters.

Linux (Kubuntu 20.04 LTS, 'Focal Fossa') (2560 x 1080 px, resolution: 96 ppi):
Firefox en Google Chrome, in grotere en kleinere browservensters.

Laptops

Windows 8.1 (1366 x 768 px, resolution: 135 ppi):
Bureaublad-versie: Firefox, Google Chrome, Internet Explorer 11 en Edge, in grotere en kleinere browservensters.
Startscherm-versie: Internet Explorer 11.

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

Tablets

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

iPad met iOS 13.3 (gesimuleerd in Xcode):
Safari (portret en landschap).

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

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

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

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

Smartphones

iPhone 7 met iOS 12.4 (gesimuleerd in Xcode):
Safari (portret en landschap).

iPhone 8 met iOS 13.3 (gesimuleerd in Xcode):
Safari (portret en landschap).

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

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

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

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

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

Er is getest met behulp van muis en toetsenbord, behalve op iOS, iPadOs en Android, waar een touchscreen is gebruikt. Op Windows 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, iPadOS en Android 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 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.

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 6.0, 7.0, 8.1, 9 en 11

VoiceOver is een in iOS en OS X ingebouwde schermlezer. Er is getest in combinatie met Safari op iOS 12.5.3 en 14.5.1, iPadOS 14.5.1 en OS X 10.14.6.

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.

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

Nieuwe browsers worden pas getest, als ze uit het bèta-stadium zijn. Anders is er 'n redelijke kans dat je tegen 'n bug zit te vechten, die voor de uiteindelijke versie nog gerepareerd wordt. Dit voorbeeld is alleen getest in de hierboven met name genoemde browsers. Vragen over niet-geteste browsers kunnen niet worden beantwoord, en het melden van fouten in niet-geteste browsers heeft ook geen enkel nut. (Melden van fouten, problemen, enzovoort in wel geteste browsers: graag! Dat kan op het forum.)

Bekende problemen (en oplossingen)

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

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

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

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

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

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

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

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

Zonder JavaScript

De panelen kunnen niet met Escape worden gesloten.

Door het indrukken van de Escape-toets wordt een eventueel geopend paneel gesloten. Zonder JavaScript werkt Escape niet en kan een paneel alleen worden gesloten door de laatste radioknop ('Paneel sluiten') te selecteren.

Alles werkt dus nog wel, maar het is 'n stuk onhandiger voor mensen die alleen het toetsenbord gebruiken.

De extra sluitmogelijkheid voor gebruikers van een schermlezer of de Tab-toets ontbreekt.

Zodra een radioknop wordt geselecteerd en een paneel wordt getoond, wordt een extra sluitmogelijkheid aangemaakt voor gebruikers van een schermlezer of de Tab-toets. Als die de groep radioknoppen verlaten, terwijl nog een paneel is geopend, zou een deel van de pagina niet te zien zijn.

Daarom wordt in dat geval bovenaan <main> een extra <button> getoond, waarmee het paneel alsnog kan worden gesloten. Zonder JavaScript is die extra <button> er niet: een paneel kan alleen worden gesloten door de laatste radioknop ('Paneel sluiten') te selecteren.

Alles werkt dus nog wel, maar het is 'n stuk onhandiger.

Zonder css

Geen problemen.

Omdat de tekst in de panelen met behulp van css wordt verborgen, is nu gewoon alles zichtbaar. Er verdwijnt dus geen tekst of zo. Uiteraard is de hele lay-out wel om zeep geholpen.

Zonder afbeeldingen

Geen problemen.

Afbeeldingen worden niet gebruikt. Het driehoekje in de <label>'s is een gewoon karakter.

Gebruikers Tab-toets

De werking van de radioknoppen is mogelijk zonder uitleg niet duidelijk.

Bovenaan de pagina staat in div#uitleg een korte uitleg voor gebruikers van een schermlezer of de Tab-toets. Deze <div> staat normaal genomen onzichtbaar buiten het scherm. Zodra deze <div> door het indrukken van de Tab-toets de focus krijgt, wordt deze binnen het scherm gezet en daardoor zichtbaar. Om dit te laten werken, heeft deze <div> tabindex="0" gekregen.

Als een paneel wordt geopend, kan dit niet met het toetsenbord worden binnengegaan om bijvoorbeeld tekst te kopiëren.

Zodra een radioknop wordt geselecteerd, wordt het bijbehorende paneel geopend. Om bijvoorbeeld tekst te kunnen selecteren en kopiëren uit zo'n paneel, moet het paneel worden binnengegaan. Om te zorgen dat dit kan, staat bovenin elke div.tekst met een paneel een <h2 tabindex="0">. Door de tabindex reageert de <h2> op de Tab-toets en kan het paneel worden binnengegaan.

Zonder JavaScript kan makkelijk een paneel open blijven staan.

Als de Tab-toets wordt ingedrukt, terwijl een paneel is geopend, wordt de groep radioknoppen verlaten. (Hoe vaak je de Tab-toets moet indrukken, is afhankelijk van de browser.) Hierdoor zou een deel van de pagina niet te zien zijn, omdat dit wordt verborgen onder een paneel.

In dat geval wordt bovenaan de pagina een extra <button> getoond, waarmee het paneel kan worden gesloten. Dit werkt alleen als JavaScript aanstaat. Zonder JavaScript is die extra <button> er niet en kan een paneel alleen worden gesloten door de laatste radioknop ('Paneel sluiten') te selecteren.

Als een paneel is geopend en de pagina wordt herladen, verschijnt geen extra <button>, waarmee het paneel alsnog gesloten kan worden.

Gelijk hierboven staat een oplossing voor als een paneel nog is geopend, terwijl al naar de rest van de pagina wordt gegaan. Die oplossing werkt echter alleen, als een radioknop wordt geselecteerd. Als de pagina wordt herladen, terwijl al een radioknop is geselecteerd en er dus al een paneel is geopend, werkt dat niet meer.

Bij geforceerd herladen van de pagina speelt dit niet, want dan wordt een geselecteerde radioknop weer ongeselecteerd.

Dit zou redelijk simpel op te lossen zijn door bij laden van de pagina alle radioknoppen te deselecteren. Dit is hier niet gedaan, omdat deze situatie zich niet of nauwelijks voor zal doen.

Tekstbrowsers

Geen problemen.

Lynx toon alle knoppen, <label>'s, de uitleg, alle panelen, enzovoort.

WebbIE toont de korte uitleg, de radioknoppen en de <label>'s. De panelen zijn verborgen, maar kunnen met behulp van de radioknoppen worden getoond en gesloten.

Schermlezers

De werking van de radioknoppen is mogelijk zonder uitleg niet duidelijk.

Bovenaan de pagina staat in <div id="uitleg" tabindex="0"> een korte uitleg voor gebruikers van een schermlezer of de Tab-toets. Deze <div> staat onzichtbaar buiten het scherm, maar wordt door een schermlezer gewoon voorgelezen.

Zonder JavaScript kan makkelijk een paneel open blijven staan.

Als de groep radioknoppen wordt verlaten, terwijl een paneel is geopend, blijft dit paneel geopend. Hierdoor zou een deel van de pagina niet te zien zijn.

In dat geval wordt bovenaan de pagina een extra <button> ingevoegd,s waarmee het paneel kan worden gesloten. Dit werkt alleen als JavaScript aanstaat. Zonder JavaScript is die extra <button> er niet en kan een paneel alleen worden gesloten door de laatste radioknop ('Paneel sluiten') te selecteren.

Als een paneel is geopend en de pagina wordt herladen, verschijnt geen extra <button>, waarmee het paneel alsnog gesloten kan worden.

Gelijk hierboven staat een oplossing voor als een paneel nog is geopend, terwijl al naar de rest van de pagina wordt gegaan. Die oplossing werkt echter alleen, als een radioknop wordt geselecteerd. Als de pagina wordt herladen, terwijl al een radioknop is geselecteerd en er dus al een paneel is geopend, werkt dat niet meer.

Bij geforceerd herladen van de pagina speelt dit niet, want dan wordt een geselecteerde radioknop weer ongeselecteerd.

Dit zou redelijk simpel op te lossen zijn door bij laden van de pagina alle radioknoppen te deselecteren. Dit is hier niet gedaan, omdat deze situatie zich niet of nauwelijks voor zal doen.

Zoomen en andere lettergrootte

In Samsung Internet op Android staan bij een grotere letter de driehoekjes over de tekst.

Als in Samsung Internet op Android de letters worden vergroot, horen de <label>'s breder te worden. Als ze niet meer naast elkaar passen, moeten ze onder elkaar komen te staan. In Samsung Internet op Android worden de <label>'s echter niet breder, waardoor de driehoekjes in de knoppen over de tekst erin komt te staan.

Dit gebeurt alleen in deze browser en is kennelijk een bug. Het is verder geen groot probleem, want alles werkt nog gewoon. Het ziet het er alleen lelijk uit.

Wijzigingen

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

:

Nieuw opgenomen.

(Onder nummer 48 stond eerst een ander voorbeeld. Dat was echter niet werkend te krijgen op touchscreens en ook niet bijzonder genoeg om toch te bewaren. Vandaar dat het is vervangen door een volledig nieuw iets.)

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.

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

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

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

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

048-css-dl:

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

048-js:

menu-048.js: kort script om de toegankelijkheid voor gebruikers van een schermlezer of Tab-toets te verbeteren.

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.

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

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

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

<html lang="nl">

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 (&auml; en dergelijke) te gebruiken, maar kun je bijvoorbeeld gewoon ä gebruiken.

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

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

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

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

Mobiele browsers gokken erop dat een pagina een bepaalde breedte heeft. Safari voor mobiel bijvoorbeeld gaat ervan uit dat een pagina 980 px breed is. De pagina wordt vervolgens zoveel versmald dat 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 worden in smallere vensters de knoppen onder elkaar gezet, en de panelen worden smaller weergegeven.

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.

<div id="container" role="radiogroup" aria-label="Knoppenbalk met panelen">

Binnen deze <div> staan de radioknoppen <input type="radio"> met de bijbehorende <label>'s, en bij elke radioknop een <div> met tekst.

role="radiogroup" geeft voor schermlezers aan dat er een groep bij elkaar horende radioknoppen binnen dit element zit. Meer hierover is te vinden bij WAI-ARIA-codes.

aria-label="Knoppenbalk met panelen" is ook bedoeld voor schermlezers. Die kunnen dit eventueel voorlezen. Meer hierover is te vinden bij WAI-ARIA-codes.

<div id="uitleg" tabindex="0">

Binnen div#uitleg staat een korte uitleg voor gebruikers van schermlezer of Tab-toets, Deze <div> staat normaal genomen buiten het scherm, waardoor deze onzichtbaar is. Schermlezers lezen hem gewoon voor, maar gebruikers van de Tab-toets zien de uitleg niet. Daarom wordt deze bij #uitleg:focus binnen het scherm gezet, zodra div#uitleg door het indrukken van de Tab-toets de focus heeft gekregen. Omdat die focus alleen kan worden gekregen door gebruik van de Tab-toets, wordt de uitleg alleen zichtbaar voor gebruikers van de Tab-toets. Die uitleg is ook alleen voor hun (en voor schermlezers) nodig, want anderen zullen de muis of een aanraking gebruiken, en dat heeft geen uitleg nodig.

<label for="radio-1">Paneel 1 <span aria-hidden="true">▼</span></label>

Het for-attribuut koppelt de <label> aan de bijbehorende radioknop.

De tekst 'Paneel 1▼' zou gewoon in z'n geheel binnen <label> kunnen staan, zonder extra <span>, want de ▼ is een gewoon karakter, net zoals een letter of cijfer. Zo wordt die ▼ dan ook door schermlezers behandeld: ze lezen het driehoekje voor met de officiële naam: 'Black down-pointing triangle'. Wat natuurlijk een volstrekt overbodige mededeling is. Daarom wordt het driehoekje in een aparte <span> gezet, die met aria-hidden="true" voor schermlezers wordt verborgen. Meer hierover is te vinden bij WAI-ARIA-codes.

<h2 tabindex="0">Paneel 1</h2>

De panelen zitten elk in een div.tekst. Bovenaan elk van die <div>'s staat zo'n bovenstaande <h2>, als eerste element.

Gebruikers van de Tab-toets kunnen normaal genomen met behulp van het toetsenbord tekst selecteren om bijvoorbeeld te kopiëren. Maar als zo'n paneel wordt geopend, kan dat niet. Door aan het eerste element binnen div.tekst (in het voorbeeld zijn dat dus de <h2>'s) een tabindex="0" te geven, wordt het paneel binnengegaan als de Tab-toets wordt ingedrukt. Nu kan desgewenst wel tekst worden geselecteerd en dergelijke.

<h1 id="extra"><button id="sluit">Er staat nog een paneel open. Druk op deze knop om dat te sluiten.</button></h1>

<h1 id="kop" tabindex="-1">Knoppenbalk met bij elke knop een te openen paneel</h1>

Twee of meer <h1>'s binnen één pagina is vanuit het oogpunt van toegankelijkheid een slecht idee. Schermlezers kunnen met behulp van een sneltoets in één keer naar de <h1> gaan. Als het goed is, staat daarin de belangrijkste kop van de pagina. Als er meer dan één <h1> is, kan dat uiterst verwarrend zijn.

Voor je aan jezelf gaat twijfelen: je hebt niet te veel gedronken en ook een nieuwe bril is niet nodig: er staan inderdaad twee <h1>'s in de code. Maar normaal genomen wordt de eerste <h1> volledig verborgen.

Een geopend paneel bedekt een deel van de pagina.

Met behulp van de laatste radioknop 'Paneel sluiten' kunnen alle panelen weer worden gesloten. Voor gebruikers van een muis of een touchscreen is dat duidelijk genoeg.

Maar gebruikers van een schermlezer of van de Tab-toets kunnen de groep radioknoppen verlaten, terwijl nog een paneel is geopend. In dat geval wordt een deel van de pagina door het paneel verborgen, zoals op de afbeelding is te zien.

De eerste h1#extra hierboven is normaal genomen met display: none; verborgen, ook voor schermlezers. Normaal genomen staat er dus maar één <h1> op de pagina, zoals het eigenlijk hoort.

Zodra een radioknop is geselecteerd en een paneel wordt geopend, wordt bij h1#extra met behulp van JavaScript style="display: block;" ingevoegd:

<h1 id="extra" style="display: block;">

(Als je die ingevoegde code wilt bekijken, moet je niet in de gewone code, maar in de Gegenereerde code kijken.)

In de <h1> zit een <button>, waarmee alle panelen kunnen worden gesloten. Alleen als er dus nog een paneel is geopend, wordt deze eerste <h1> zichtbaar. Normaal genomen is h1#extra met display: none; verborgen. h1#extra is het eerste element binnen <main>. Dat is niet toevallig, want schermlezers kunnen in één keer naar <main> gaan. Ze kunnen ook in één keer naar een <h1> gaan, en ze kunnen ook alle <h>'s aflopen.

In alle gevallen zal deze <h1> worden gevonden. Althans: alleen als er een paneel is geopend. Als dat niet zo is, is deze <h1> verborgen, en wordt de 'echte' h1#kop gevonden. Zodra met de <button> in h1#extra het paneel is gesloten, wordt h1#extra met behulp van JavaScript weer verborgen, en is er dus weer één <h1> aanwezig: de 'echte' <h1>.

Voor gebruikers van de Tab-toets werkt het in principe hetzelfde, alleen wordt de tekst nu binnen het scherm gezet, zodat deze is te lezen.

Binnen h1#extra zit een <button>. Als die wordt ingedrukt, worden met behulp van JavaScript alle radioknoppen gedeselecteerd, waardoor alle panelen worden gesloten. Je zou ook alleen een <button> kunnen gebruiken, maar dan kunnen gebruikers van een schermlezer die <button> missen, als ze gelijk naar de <h1> gaan of alleen <h>'s aflopen. Nu vinden ze de <button> altijd.

De tekst binnen de <h1> moet volledig binnen de <button> staan, omdat sommige schermlezers anders de tekst wel netjes voorlezen, maar de <button> werkt niet.

h1#kop, de 'echte' belangrijkste kopregel, heeft een tabindex="-1". Hierdoor kan JavaScript de focus op deze <h1> zetten, wat normaal genomen niet kan. Als dat niet gebeurt, gaan sommige schermlezers weer naar de eerste radioknop terug, als de <button> in h1#extra wordt ingedrukt. Nu gaan ze allemaal netjes bij h1#kop verder met voorlezen.

Kort samengevat: er zijn alleen twee <h1>'s aanwezig, zolang een paneel is geopend. Zodra dat weer is gesloten, is er maar één <h1> aanwezig.

<p lang="la">

De <p>'s 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.)

<script src="048-js/menu-048.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:

<script src="pad-naar-script/script.js" async></script>

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:

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

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

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

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

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

Dat comprimeren kun je met de hand doen, maar er bestaan ook hulpmiddelen voor. Op de pagina met links kun je onder het kopje Gereedschap → Snelheid, testen, gzip, 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-048-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;

Slim om te doen vanwege verschillen tussen browsers.

header

Alle <header>'s. Dat is er hier maar één. De hele constructie met knoppen, panelen en dergelijke zit hierin. Een uitgebreidere beschrijving van de semantische betekenis van <header> is te vinden bij <header>.

width: 1000px;

Breedte.

max-width: 90%;

Hier gelijk boven is een breedte van 1000 px opgegeven. Als een browservenster smaller is dan die 1000 px, moet daardoor horizontaal worden gescrold om alles te kunnen zien. Deze maximumbreedte voorkomt dat.

Een breedte in procenten is normaal genomen ten opzichte van de ouder van het element. Die ouder is hier <body>, een blok-element. Een blok-element wordt normaal genomen even breed als z'n ouder. De ouder van <body> is <html>. Omdat <html> het buitenste element is, wordt dit normaal genomen even breed als het venster van de browser. Uiteindelijk wordt <header> hierdoor nooit breder dan 90% van de breedte van het venster.

margin: 10px auto 0;

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

Aan de bovenkant een kleine marge tot de bovenkant van het browservenster. Links en rechts auto, wat hier hetzelfde betekent als evenveel. Hierdoor staat <header>, en daarmee alles wat erin zit, altijd horizontaal gecentreerd binnen z'n ouder <body>. Zoals gelijk hierboven bij max-width beschreven is <body> even breed als het venster. Uiteindelijk staat <header> hierdoor altijd horizontaal gecentreerd binnen het venster, ongeacht de breedte van het venster.

border: black solid 1px;

Zwart randje.

position: relative;

Om nakomelingen van <header> te kunnen positioneren ten opzichte van <header>, moet <header> zelf een andere positie dan statisch hebben. Een relatieve positie heeft verder geen invloed op <header> zelf, omdat niets voor top en dergelijke wordt opgegeven.

#container

Het element met id="container".

Een <div>, waarin de hele constructie met knoppen, panelen, en dergelijke zit.

Dat stond ook al bij header gelijk hierboven, dus dit is eigenlijk wat dubbelop. Schermlezers kunnen snel naar elementen als een <header> navigeren, vandaar dat <header> is gebruikt. (Meer over dat soort elementen staat bij Semantische elementen.)

Je zou deze <div> weg kunnen laten, maar het is duidelijker om de 'uiterlijkheden' bij <header> neer te zetten (border, centreren) en de 'innerlijkheden' bij deze <div>. Dat is overigens volledig 'n kwestie van voorkeur. Als iemand gelukkig wordt van het combineren van <header> en div#container, moet zij of hij daardoor niet langer in het vagevuur blijven logeren.

display: flex;

De waarde flex is onderdeel van een hele reeks eigenschappen en waarden, die bij elkaar 'flexbox' worden genoemd. Met display: flex; wordt div#container in een zogenaamde 'flex container' veranderd. Dit maakt het veel makkelijker om de directe kinderen van dit element, de 'flex items', op een bepaalde plaats neer te zetten.

De directe kinderen van div#container zijn hier div#uitleg, zeven <input type="radio">'s, zeven <label>'s en zes <div class="tekst">'s met de panelen.

div#uitleg wordt verderop bij #uitleg absoluut gepositioneerd, en absoluut gepositioneerde elementen zijn geen flex items meer.

De radioknoppen zijn onzichtbaar en daarom voor het neerzetten niet van belang, zoals wordt beschreven bij #container input.

De <div>'s met de panelen worden verderop bij .tekst ook absoluut gepositioneerd, en doen dus ook niet meer mee.

Uiteindelijk zijn alleen de zeven <label>'s wat betreft flexbox van belang. Dat komt goed uit, want die zeven <label>'s moeten netjes worden neergezet. De andere elementen zie je niet, of ze worden later met position: absolute; op de juiste plaats neergezet.

In dit geval komen de zeven <label>'s naast elkaar te staan, omdat dat de standaardinstelling van flex-direction is. Zou je hier flex-direction: column; gebruiken, dan zouden flex items onder elkaar komen te staan.

De waarde van flex-direction is van belang voor een aantal andere eigenschappen van flexbox, zoals het verderop bij #container label gebruikte flex: 1 0 7em;.

flex-wrap: wrap;
Knoppen zijn veel te smal.

De standaardwaarde bij flex-wrap is no‑wrap: zet alle flex items, hier de zeven <label>'s binnen div#container, naast elkaar op één regel. Als het niet meer op één regel past, maak ze dan smaller. Dat is wat op de afbeelding gebeurt: de zeven <label>'s zijn zo smal gemaakt dat ze naast elkaar op één regel passen. Tja, passen doet het wel, maar dan ongeveer zoals de waarheid bij Mark Rutte past: het wringt aan alle kanten.

Knoppenbalk te breed voor venster.

Daarom wordt later bij #container label een minimumbreedte aan de <label>'s gegeven. Dat ziet er veel netter uit, zoals op de tweede afbeelding is te zien. Maar nu dient zich een andere ellendepukkel aan: bijna de helft van de knoppenbalk staat buiten het venster van de browser. Je moet horizontaal scrollen om alle knoppen te kunnen zien.

Tot niet zolang geleden moest je de meest idiote capriolen uithalen om dit soort dingen op te lossen.

De flex container div#container heeft maximaal 90% van de breedte van het venster van de browser. De zeven flex items, de zeven <label>'s krijgen bij #container label een minimumbreedte van 7 em en zijn dus samen 49 em breed.

Met flex-wrap: wrap; worden de niet meer op één regel binnen div#container passende knoppen gewoon op één of meer volgende regels gezet. Waarbij je desgewenst ook nog gedetailleerd kunt aangeven, of ze de hele regel moeten vullen, links of rechts moeten staan, en noem maar op.

#uitleg

Het element met id="uitleg". Een <div> met een korte uitleg voor gebruikers van een schermlezer of van de Tab-toets, omdat voor die mensen de werking van de knoppenbalk mogelijk niet gelijk duidelijk is.

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: 30em;

Breedte.

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

max-width: 90%;

Maximumbreedte.

Hier gelijk boven is een breedte van 30 em opgegeven. In smallere browservensters kan dat te breed zijn voor het venster, zeker als de lettergrootte ook is verhoogd. Daarom wordt hier een maximumbreedte opgegeven.

Een breedte in procenten is normaal genomen ten opzichte van de ouder van het element. Die ouder is hier div#container, een blok-element. Een blok-element wordt normaal genomen even breed als z'n ouder, hier <header>. Dat is ook weer een blok-element en wordt even breed als z'n ouder <body>. Het wordt eentonig: ook het blok-element <body> wordt weer 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.

Uiteindelijk wordt hierdoor div#uitleg nooit breder dan 90% van de breedte van het venster van de browser, ongeacht de breedte daarvan.

border: black solid 1px;

Zwart randje.

padding: 10px;

Kleine afstand tussen buitenkant van en tekst in de <div>.

position: absolute;

Absoluut positioneren om div#uitleg op de juiste plaats neer te kunnen zetten.

Er wordt gepositioneerd ten opzichte van het 'containing block'. Dat is de eerste voorouder die zelf een bepaalde eigenschap heeft, zoals een andere positie dan statisch. Dat is hier <header>, die bij header relatief is gepositioneerd.

top: 2rem;

Kleine afstand tot de bovenkant van <header>.

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

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

left: -20000px;

Onzichtbaar links buiten het scherm parkeren.

Schermlezers lezen de tekst gewoon voor. Voor gebruikers van de Tab-toets wordt de <div> later bij #uitleg:focus binnen het scherm gezet en daarmee zichtbaar.

z-index: 10;

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

Een z-index werkt alleen in bepaalde omstandigheden. Eén van die omstandigheden is een absolute positie. Die is iets hierboven aan div#uitleg gegeven, dus dat is geregeld.

#container input

Alle <input>'s binnen een element met id="container". De radioknoppen waarmee een paneel kan worden geopend of (alleen met de laatste knop) gesloten.

Deze selector is iets langer dan strikt nodig is, want op de pagina staan alle <input>'s binnen div#container. Dus je zou kunnen volstaan met input. Maar heel vaak zullen er meer <input>'s op een pagina staan, en dan zou alleen input als selector ook voor die <input>'s gelden. Hierdoor kunnen makkelijk problemen ontstaan, zeker als later 'n <input> ergens wordt toegevoegd.

Radioknoppen in een knoppenbalk ziet er nou niet echt heel fraai uit, om het parlementair uit te drukken. Daarom wordt, voor het uiterlijk, de bij de knop horende <label> gebruikt, zodat je de radioknop zelf niet ziet.

Maar de radioknop helemaal verbergen kan niet, want dan werken ze niet meer in schermlezer TalkBack op Android in versies ouder dan 8.1. De knoppen moeten daarvoor minimaal 1 x 1 px groot zijn, moeten binnen het browservenster staan, en ze mogen niet onder een ander element staan. Daarom worden ze hieronder verkleind tot 1 x 1 px en bovenop het bij de knop horende <label> geplaatst. Of je nou de radioknop of de <label> aanraakt, in beide gevallen wordt gewoon de radioknop geselecteerd. En je ziet niets meer van 't kreng. Oeps. En je ziet niets meer van de door moeder natuur iets minder fraai bedeelde radioknop.

-webkit-appearance: none; appearance: none;

Hier staat in feite twee keer hetzelfde: appearance: none;. Waarom dat zo is, staat bij De voorvoegsels -moz-, -ms- en -webkit-.

Elke browser en/of besturingssysteem heeft een eigen standaardweergave van een radioknop. Die wordt hiermee uitgeschakeld.

width: 1px; height: 1px;

De radioknoppen zo klein mogelijk maken.

margin: 0 0 0 -1px; Kieren tussen de labels door de marges bij de inputs.

Ondanks dat hierboven bij appearance de standaardweergave is weggehaald, geven sommige browsers toch een marge aan de <input>'s. Zoals op de afbeelding is te zien, ontstaan daardoor kieren tussen de <label>'s, want die marge wordt bij de gelijk hierboven opgegeven breedte opgeteld.

Links staat een negatieve marge van 1 px. De breedte van de <input>'s is maar 1 px, maar toch zie je dat. In de html staat gelijk na elke <input> een bijbehorende <label>. Door de <input> 1 px naar links te zetten, komt ook de <label> 1 px naar links te staan, en sluit de hele handel netjes aan.

border: none;

Ondanks het het hierboven bij appearance weghalen van de standaardweergave, menen sommige browsers toch een border te moeten tonen. Die wordt hier ook weggehaald.

position: relative;

Hier gelijk onder wordt een z-index gebruikt. Die werkt alleen in bepaalde omstandigheden, zoals wanneer een element een relatieve positie heeft. Omdat verder niets voor top of zo wordt opgegeven, heeft dit verder geen enkele invloed.

z-index: 10;

Normaal genomen worden elementen in de volgorde van de html op het scherm gezet. Wat later in de html staat, wordt over eerdere elementen gezet. In dit geval zou dat betekenen dat de achter de <input> staande <label> over de <input> wordt neergezet. Waardoor deze niet meer werkt in TalkBack op versies ouder dan 8.1. Daarom wordt de <button> hiermee bovenop de <label> gezet.

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

#container label

Alle <label>'s binnen een element met id="container". De bij de radioknoppen horende <label>'s, die het uiterlijk van de radioknoppen bepalen.

Deze selector is iets langer dan strikt nodig is, want op de pagina staan alle <label>'s binnen div#container. Dus je zou kunnen volstaan met label. Maar heel vaak zullen er meer <label>'s op een pagina staan, en dan zou alleen label als selector ook voor die <label>'s gelden. Hierdoor kunnen makkelijk problemen ontstaan, zeker als later 'n <label> ergens wordt toegevoegd.

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.

cursor: pointer;

Bij hoveren over een <label> verandert de cursor in het symbool dat bij een link hoort. Hierdoor is - voor gebruikers van een muis - duidelijker dat er iets gebeurt, als je op 'n knop klikt. Op touchscreens heeft dit (uiteraard) geen enkel effect.

flex: 1 0 7em;

flex is een zogenaamde 'shorthand': een eigenschap die eigenlijk uit meerdere eigenschappen is samengesteld. In dit geval zijn dat flex-grow, flex-shrink en flex-basis. De eerste waarde achter flex hoort bij flex-grow, de tweede bij flex-shrink en de derde bij flex-basis.

Er wordt aangeraden hier altijd de shorthand te gebruiken, omdat deze drie eigenschappen elkaar sterk beïnvloeden. Als je er eentje vergeet, valt die terug op z'n standaardwaarde en dat kan lastig te vinden problemen opleveren. Als je ze alle drie gebruikt, zie je altijd wat de waarde bij elk van de drie eigenschappen is.

De richting waarin flex werkt (horizontaal of verticaal) is afhankelijk van de bij de flex container opgegeven flex-direction. Die flex container is hier div#container. Omdat daar niets is opgegeven voor flex-direction, werkt dat in de standaardrichting: horizontaal.

Als voor flex niets wordt opgegeven, worden de standaardwaarden gebruikt. Dat ziet eruit als op de afbeelding hieronder:

Knoppen te smal om knoppenbalk te vullen.

De witte blokjes zijn de <label>'s. De buitenste zwarte rand is de border bij <header>, want dat is een blok-element en daarom gewoon even breed als z'n ouder <body>. Met behulp van flex kan deze weergave worden verbeterd.

flex-grow: de eerste waarde 1 hoort hierbij. In principe wordt een flex item niet breder dan nodig is om de inhoud ervan weer te geven. Dat is wat je op de afbeelding ziet gebeuren: de tekst past keurig binnen de <label>'s. (De driehoekjes staan over de tekst heen, maar die zijn absoluut gepositioneerd, vandaar. Dat heeft hier verder niets mee te maken.)

De standaardwaarde bij flex-grow is 0: het flex item (hier de <label>) mag niet groter worden dan nodig is om de inhoud ervan weer te geven, ook al is er nog ruimte over in flex container div#container. Door de waarde bij flex-grow te veranderen in 1, mag het flex item (hier de <label>) groeien en gebruikt eventuele overblijvende ruimte in de flex container. Daardoor wordt de volledige ruimte binnen de div#container gebruikt.

flex-shrink: de tweede waarde 0 hoort hierbij. Dit is de tegenhanger van flex-grow. Deze eigenschap bepaalt of het flex-item eventueel mag krimpen. De standaardwaarde is hier 1 krimpen mag. Hier is dat niet de bedoeling, dus dit wordt veranderd in 0 krimpen mag niet. (Overigens maakt dat hier weinig uit. Bij #container is flex-wrap: wrap; opgegeven. Als de <label>'s te breed worden, komen ze hierdoor gewoon op een nieuwe regel te staan.)

flex-basis: dit is de 'ideale' breedte. Deze breedte krijgt het flex item, voordat er wordt gegroeid of gekrompen. Omdat er niet mag worden gekrompen, is dit hier gelijk de minimumbreedte. Het is niet de maximumbreedte, want groeien mag wel.

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

De standaardwaarde van flex-basis is auto: de grootte van de inhoud bepaalt de breedte. Hierdoor zou je in dit geval knoppen van ongelijke breedte krijgen, wat er niet netjes uitziet.

line-height: 2.5em;

Regelhoogte. Omdat geen gewone hoogte is opgegeven, is dit gelijk ook de hoogte van de hele <label>. Tekst wordt automatisch halverwege de regelhoogte neergezet, dus de tekst is gelijk verticaal gecentreerd.

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

text-align: center;

Tekst horizontaal centreren.

outline: black solid 1px;

Randje rondom elke <label>.

outline werkt hier beter dan border. Als twee naast elkaar staan de <label>'s beide een border hebben, komen die tegen elkaar aan te staan. Tussen twee <label>'s staat dan een border van 2 x 1 px = 2 px breed.

Een outline neemt zelf geen ruimte in, die komt gewoon over het element ernaast heen te staan. Twee outlines die elkaar raken, staan daardoor over elkaar heen, waardoor ze samen toch maar 1 px breed zijn.

position: relative;

Om nakomelingen van <label> te kunnen positioneren ten opzichte van <label>, moet <label> zelf een andere positie dan statisch hebben. Een relatieve positie heeft verder geen invloed op <label> zelf, omdat niets voor top en dergelijke wordt opgegeven.

#container label span

Alle <span>'s die binnen een <label> staan dat weer binnen een element met id="container" zit. De <span>'s waarin het driehoekje ▼zit. Deze zitten in een aparte <span>, zodat ze verborgen kunnen worden voor schermlezers.

color: black;

Voorgrondkleur zwart. Dit is onder andere de kleur van de tekst, dus ook van het driehoekje, want dat is een gewoon karakter.

Als geen voorgrondkleur wordt opgegeven, wordt de kleur van de ouder van het element geërfd, hier de <label> waar de <span> in zit. Die kleur is ook zwart. Maar zodra een radioknop is geselecteerd, wordt de voorgrondkleur van <label> bij #container input:checked:not(:last-of-type) + label veranderd in wit, en de achtergrondkleur in zwart. <span> zou die witte kleur dan ook krijgen. Hier wordt opgegeven, dat ook dan het driehoekje zwart blijft.

Het driehoekje geeft aan dat er iets te openen valt, Maar zodra het paneel is geopend, hoeft dat niet meer. Omdat het driehoekje zwart blijft en de achtergrond van ouder <label> zwart wordt, wordt het driehoekje onzichtbaar.

font-size: 1.4em;

Iets grotere lettergrootte dan normaal, omdat het driehoekje anders wel heel klein wordt.

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.

position: absolute;

Om de <span>, en daarmee het driehoekje, op de juiste plaats neer te kunnen zetten.

Er wordt gepositioneerd ten opzichte van het 'containing block'. Dat is de eerste voorouder die zelf een bepaalde eigenschap heeft, zoals een andere positie dan statisch. Dat is hier <label>, die bij #container label een relatieve positie heeft gekregen.

right: 4px;

4 px vanaf rechts neerzetten. Op die positie staat het driehoekje bij elke lettergrootte nog steeds rechts van de overige tekst in de <label>.

.tekst

Alle elementen met class="tekst". De zes <div>'s waarin de panelen zitten.

background: white;

Witte achtergrond.

color: black;

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

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

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

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

display: none;

Verbergen. Pas als een radioknop is geselecteerd, wordt de <div>, en daarmee het erin zittende paneel, getoond.

border: black solid 1px;

Zwart randje.

padding: 10px;

Kleine ruimte tussen buitenkant van en tekst in de <div>.

position: absolute;

Om de <div> op de juiste plaats neer te kunnen zetten.

Er wordt gepositioneerd ten opzichte van het 'containing block'. Dat is de eerste voorouder die zelf een bepaalde eigenschap heeft, zoals een andere positie dan statisch. Dat is hier <header>, die bij header een relatieve positie heeft gekregen.

right: -1px;

Er wordt gepositioneerd ten opzichte van <header>. Met right: 0; komt de rechterkant van div.tekst precies tegen de rechterkant van <header> te staan, precies gelijk met de rechterkant van de daarin zittende <label>'s.

Maar <header> heeft nog een border van 1 px breed, en die komt er nog bij. Daarom moet div.tekst 1 px meer naar rechts worden gezet om de rechterkant gelijk te krijgen met de rechterkant (eigenlijk de rechterborder) van <header>.

bottom: 0;

De <div> met het paneel moet onder de knoppen komen te staan, onder de <label>'s. De hoogte van die <label>'s is even hoog als de regelhoogte, en die is bij #container label met line-height: 2.5em; afhankelijk gemaakt van de lettergrootte. De hoogte van de knoppen kan dus variëren. Er zijn verschillende manieren om de <div>'s met de panelen toch precies onder de knoppen te krijgen, waarvan er hier één wordt gebruikt.

Paneel staat over de knoppen heen.

Er wordt gepositioneerd ten opzichte van <header>. Met bottom: 0; wordt de onderkant van div.tekst gelijk gezet met de onderkant van <header>. Dit is wat op de afbeelding is te zien: de eerste knop met 'Paneel 1' is weggevallen achter het eerste paneel, omdat beide aan de onderkant van <header> staan.

<header> is een blok-element en daarom wordt de hoogte ervan normaal genomen automatisch even hoog als de inhoud ervan. Die inhoud zit weer in div#container, dus div#container bepaalt de hoogte van <header>.

Ook div#container is een blok-element, dus ook deze <div> wordt precies hoog genoeg om de inhoud ervan weer te geven. Maar voor die hoogte tellen absoluut gepositioneerde elementen niet mee. Daardoor blijven voor het bepalen van de hoogte van div#container alleen de <label>'s en de <input>'s over.

De <input>'s zijn onzichtbaar, blijven alleen de <label>'s over, dus de hoogte van de <label>'s bepaalt de hoogte van div#container. Oftewel: hoe hoger de <label>'s, de knoppen, hoe hoger div#container, en daarmee ook <header>. Oftewel: de onderkant van de <label>'s is ook altijd de onderkant van <header>. Dus als de onderkant van div.tekst aan de onderkant van <header> wordt neergezet, staat die altijd op dezelfde hoogte als de onderkant van de <label>'s. Dat is precies, wat er op de afbeelding gebeurt.

Als de hoogte van de <label> verandert door een andere lettergrootte, verandert ook de hoogte van <header>, en verandert dus ook de positie van div.tekst. Maar div.tekst blijft wel altijd aan de onderkant van <header> staan.

Als je nou div.tekst precies z'n eigen hoogte naar omlaag zou kunnen verplaatsen, staat die altijd precies onder de <label>'s met de knoppen. Ongeacht de hoogte van <header>. En dat is precies wat iets hieronder bij transform gebeurt.

left: -1px;

Linkerkant van div.tekst gelijk zetten met de linkerkant van de <label>'s. De reden van de -1px in plaats van gewoon 0 staat iets hierboven bij right: -1px;.

transform: translateY(100%);

div.tekst naar omlaag verplaatsen. Bij translate() geldt een hoogte in procenten ten opzichte van het element zelf. 100% naar beneden betekent dus: div.tekst met precies z'n eigen hoogte omlaag verplaatsen. De browser berekent dit bij het weergeven van de pagina, dus dit werkt ook bij een ander lettergrootte.

Omdat iets hierboven bij bottom: 0; de onderkant van div.tekst gelijk is gezet met de onderkant van de <label>'s met de knoppen, staat nu de bovenkant van div.tekst precies onder de knoppen en sluit altijd netjes aan, ongeacht de lettergrootte.

h2

Alle <h2>'s.

font-size: 1.2em;

Een <h2> is van zichzelf vrij groot. Hier worden ze 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.

margin: 0;

Een <h2> heeft aan boven- en onderkant een marge. Die wordt hier weggehaald.

#uitleg: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.

#uitleg {background: white; color: black; width: 30em; max-width: 90%; border: black solid 1px; padding: 10px; position: absolute; top: 2rem; left: -20000px; z-index: 10;}

Het element met id="uitleg", maar alleen als dit de focus heeft. div#uitleg is een <div> met een korte uitleg voor gebruikers van schermlezer of Tab-toets.

Bij #uitleg is deze <div> links buiten het scherm geparkeerd, waardoor deze onzichtbaar is. Schermlezers lezen hem gewoon voor, maar daar hebben gebruikers van de Tab-toets niets aan. Daarom heeft deze <div> het attribuut tabindex="0" gekregen, waardoor deze <div> focus kan krijgen. Als de Tab-toets wordt ingedrukt, wordt de <div> binnen het scherm geplaatst en daardoor zichtbaar. Als de Tab-toets nogmaals wordt ingedrukt, verliest de <div> weer de focus en wordt weer onzichtbaar.

outline: none;

Als een element de focus heeft, geeft een kadertje dat aan. Dat is belangrijk voor gebruikers van de Tab-toets, want die weten dan bijvoorbeeld welke link er wordt gevolgd, als ze op Enter drukken.

Die outline is overduidelijk onderdeel van een wedstrijd 'Wie maakt het lelijkste kadertje'. Waarbij sommige browsers behoorlijk hoog weten te scoren. Maar goed, het ding is absoluut onmisbaar uit een oogpunt van toegankelijkheid.

Hier kan het echter veilig worden verwijderd, want de hele <div> verschijnt, als deze de focus heeft. Als je dat niet ziet, valt te vrezen dat een outline ook niet meer helpt.

left: 10px;

10 px vanaf links neerzetten.

Er wordt gepositioneerd ten opzichte van het 'containing block'. Dat is de eerste voorouder die zelf een bepaalde eigenschap heeft, zoals een andere positie dan statisch. Hier is dat <header>, die bij header relatief is gepositioneerd. Omdat <header> ook de breedte van de knoppenbalk bepaalt, staat div#uitleg 10 px rechts van de meest linkse knop.

#container input: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.

#container input {-webkit-appearance: none; appearance: none; width: 1px; height: 1px; margin: 0 0 0 -1px; border: none; position: relative; z-index: 10;}

Alle <input>'s binnen een element met id="container", maar alleen als een <input> de focus heeft. De radioknoppen waarmee een paneel kan worden geopend of (alleen met de zevende knop) gesloten.

Deze selector is iets langer dan strikt nodig is, want op de pagina staan alle <input>'s binnen div#container. Dus je zou kunnen volstaan met input:focus. Maar heel vaak zullen er meer <input>'s op een pagina staan, en dan zou alleen input:focus als selector ook voor die <input>'s gelden. Hierdoor kunnen makkelijk problemen ontstaan, zeker als later 'n <input> ergens wordt toegevoegd.

outline: none; Een outline maakt de input zichtbaar.

Bij #container input zijn de radioknoppen (vrijwel) onzichtbaar gemaakt. Maar het is ongelooflijk hoe hardnekkig sommige browsers deze lelijkerds toch zichtbaar proberen te maken.

Sommige browsers zetten, als een <input> de focus heeft, een outline om de <input>.

De afbeelding is gemaakt in Google Chrome op Linux. Het kleine blokje linksboven is de <input type="radio"> bij het eerste paneel, als deze de focus heeft en daardoor een outline krijgt. Kun je nog zo je best doen om de <input> te verstoppen, verpest zo'n outline het weer.

Deze regel verwijdert de outline en voorkomt daardoor het verpesten, met als bonus een verbetering van het humeur.

#container input:focus:not(:last-of-type) + label, #container input:hover + 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.

#container label {background: white; color: black; cursor: pointer; flex: 1 0 7em; line-height: 2.5em; text-align: center; outline: black solid 1px; position: relative;}

Dit is weer zo'n regel die je beter niet aan je toekomstige schoonfamilie kunt laten zien, als ze vragen wat je eigenlijk voor werk doet. Maar als je hem ontleedt (de selector, niet je schoonfamilie), wordt het 'n stuk overzichtelijker.

Er zijn hier twee selectors, gescheiden door een komma.

De eerste selector #container input:focus:not(:last-of-type) + label:

#container: het element met id="container". De <div> waar de hele constructie in zit.

input: elke <input> die in div#container zit. De radioknoppen die, als ze zijn geselecteerd, een bijbehorend paneel tonen (of, bij de laatste knop, de panelen verbergt).

input:focus:not:(:last-of-type): dit stukje ziet er wat ingewikkeld uit, maar ook dit kan weer in stukjes worden gehakt:

:focus: de <input> moet de focus hebben.

:last-of-type: 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. De selector :nth-last-of-type() werkt hetzelfde, maar telt van achter naar voren. Omdat het vaak voorkomt dat je het laatste element van 'n bepaald type wilt hebben, heeft dat een aparte selector: :last-of-type. Voor alle eerdere elementen gebruik je 'n soortgelijke selector, maar dan met een volgnummer: :nth-last-of-type(). Tussen de haakjes komt het volgnummer. :nth-last-of-type(1) is precies hetzelfde als :last-of-type, maar de laatste is wat mensvriendelijker.

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

:not(:last-of-type): maar dan omgekeerd. Omdat :last-of-type binnen :not() staat, worden juist alle <inputs>'s geselecteerd, behalve de laatste. De selector :not() geeft aan dat wat tussen de haakjes staat, juist níét geselecteerd moet worden.

:focus:not:(:last-of-type) bij elkaar betekent dus: als een <input> de focus heeft, behalve als dat de laatste <input> is. (Die laatste <input> dient als enige <input> alleen om panelen te sluiten, vandaar.)

Dat er in deze selector drie pseudo-classes achter elkaar staan, is geen enkel probleem. Dat mag gewoon.

De selector :last-of-type (of :nth-last-of-type()) kan onverwachte bijwerkingen hebben. In dit geval zit er maar één serie <input>'s in div#container. Maar als er bijvoorbeeld nog 'n geneste <div> in div#container zou zitten, waarin ook <input>'s zouden zitten, zou deze selector ook voor die <inputs>'s gelden. (Dit zou je in dit geval bijvoorbeeld kunnen oplossen door de <input>'s een class te geven. Dan geldt de selector alleen voor <input>'s met die class.)

+: 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 div#container.

label: de <label> die gelijk op de <input> voor de + volgt. Dat is de <label> die bij de voor de + staande <input> hoort.

De hele eerste selector in normale mensentaal: als een <input> binnen div#container de focus heeft, behalve als dat de laatste <input> is, doe dan iets met de gelijk daarop volgende <label>.

De tweede selector: #container input:hover + label:

#container: het element met id="container". De <div> waar de hele constructie in zit.

input: elke <input> die in div#container zit. De radioknoppen die, als ze zijn geselecteerd, een bijbehorend paneel tonen (of, bij de laatste knop, de panelen verbergt).

:hover: maar alleen als met de muis over de <input> wordt gehoverd. Omdat aan elke <input> met behulp van het for-attribuut een <label> is gekoppeld, werkt dit ook als over de bij de <input> horende <label> wordt gehoverd.

+: 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 div#container.

label: de <label> die gelijk op de <input> voor de + volgt. Dat is de <label> die bij de voor de + staande <input> hoort.

De hele tweede selector in normale mensentaal: als over een <input> (of de bijbehorende <label>) binnen div#container wordt gehoverd, doe dan iets met de gelijk op die <input> volgende <label>.

Voor beide selectors geldt, dat ze iets langer zijn, dan strikt nodig is, want op de pagina staan alle <input>'s binnen div#container. Dus je zou kunnen #container weg kunnen laten. Maar heel vaak zullen er meer <inputs>'s op een pagina staan, en dan zou alleen input als selector ook voor die <input>'s gelden. Hierdoor kunnen makkelijk problemen ontstaan, zeker als later 'n <input> ergens wordt toegevoegd.

text-decoration: underline;

Bij zo'n fantastische selector verwacht je natuurlijk geweldige effecten. Maar de weerzinwekkende neoliberale doctrine dat de rijkste selectors ook de rijkste effecten moeten hebben, laten we hier met liefde aan Rutte en trawanten over.

Er wordt alleen een streepje onder de tekst in de <label> gezet. De eerste selector regelt dat, als een paneel is geopend. Hierdoor is duidelijker, bij welke knop een paneel hoort. De tweede selector regelt dat, als over een knop wordt gehoverd. Hierdoor is duidelijker dat er iets gebeurt, als je op een knop klikt.

#container input:last-of-type:checked + label

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.

#container label {background: white; color: black; cursor: pointer; flex: 1 0 7em; line-height: 2.5em; text-align: center; outline: black solid 1px; position: relative;}

#container input:focus:not(:last-of-type) + label, #container input:hover + label {text-decoration: underline;}

Als een knop wordt aangeklikt, en dus de radioknop is geselecteerd, opent het bij die knop horende paneel. De geselecteerde knop heeft ook de focus. Om duidelijk te maken, bij welke knop het paneel hoort, wordt onder andere de tekst in de bij de knop horende <label> met behulp van :focus onderstreept. Behalve bij de laatste knop met 'Paneel sluiten', want die heeft geen paneel. Dit is redelijk gemakkelijk te regelen met behulp van de selector #container input:focus:not(:last-of-type) + label, zoals iets hierboven beschreven bij #container input:focus:not(:last-of-type) + label, #container input:hover + label {text-decoration: underline;}.

Om voor gebruikers van een muis duidelijk te maken dat er iets gebeurt, als je op een knop klikt, wordt de tekst in de <label>'s onderstreept, als je over een knop hovert. Dit wordt afgehandeld door het deel achter de komma in de selector die hierboven wordt genoemd: #container input:hover + label. Dat gebeurt (anders dan bij de :focus in de selector voor de komma) ook bij de laatste knop. Ook de tekst 'Paneel sluiten' wordt onderstreept, als erover wordt gehoverd.

Nu ontstaat er een klein probleem. Op een touchscreen wordt hoveren over een element meestal net zo behandeld als een aanraking. Dat wil zeggen dat de tekst 'Paneel sluiten' onderstreept blíjft, ook als die knop niet meer wordt aangeraakt. Als je niet meer met de muis over 'Paneel sluiten' hovert, verdwijnt de onderstreping, maar als je die tekst hebt aangeraakt, blijft de onderstreping staan. Terwijl het paneel al is gesloten. Op een touchscreen verdwijnt de 'hover' pas, als je over iets anders 'hovert'.

(Als je het touchscreen ergens buiten de knoppen aanraakt, verdwijnt de onderstreping ook bij de laatste knop. Behalve op iOS en iPadOS, daar blijft de onderstreping ook dan staan. Daar verdwijnt die pas, als je een ander element aanraakt dat de focus kan krijgen, zoals een <button> of een <textarea>.)

Geen wereldprobleem, maar gewoon niet zo netjes.

Als je de laatste radioknop met 'Paneel sluiten' aanraakt, wordt deze - net zoals alle andere radioknoppen - geselecteerd. Deze regel zorgt ervoor dat bij de laatste knop niet wordt onderstreept, als deze is geselecteerd. Wat prima is, want als die laatste knop is geselecteerd, zijn alle panelen verborgen.

Bij hoveren met de muis werkt de onderstreping nog wel, want bij alleen hoveren met de muis wordt de knop niet geselecteerd.

#container: het element met id="container". De <div> waar de hele constructie in zit.

input: elke <input> die in div#container zit. De radioknoppen die, als ze zijn geselecteerd, een bijbehorend paneel tonen (of, bij de laatste knop, de panelen verbergt).

input:last-of-type:checked: dit stukje ziet er wat ingewikkeld uit, maar ook dit kan weer in stukjes worden gehakt:

:last-of-type: 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. De selector :nth-last-of-type() werkt hetzelfde, maar telt van achter naar voren. Omdat het vaak voorkomt dat je het laatste element van 'n bepaald type wilt hebben, heeft dat een aparte selector: :last-of-type. Voor alle eerdere elementen gebruik je 'n soortgelijke selector, maar dan met een volgnummer: :nth-last-of-type(). Tussen de haakjes komt het volgnummer. :nth-last-of-type(1) is precies hetzelfde als :last-of-type, maar de laatste is wat mensvriendelijker.

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

:checked: maar alleen als de <input> is geselecteerd.

:last-of-type:checked bij elkaar betekent dus: de laatste <input>, maar alleen als die is geselecteerd.

Dat er in deze selector twee pseudo-classes achter elkaar staan, is geen enkel probleem. Dat mag gewoon.

De selector :last-of-type (of :nth-last-of-type()) kan onverwachte bijwerkingen hebben. In dit geval zit er maar één serie <input>'s in div#container. Maar als er bijvoorbeeld nog 'n geneste <div> in div#container zou zitten, waarin ook <input>'s zouden zitten, zou deze selector ook voor die <inputs>'s gelden. (Dit zou je in dit geval bijvoorbeeld kunnen oplossen door de <input>'s een class te geven. Dan geldt de selector alleen voor <input>'s met die class.)

+: 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 div#container.

label: de <label> die gelijk op de <input> voor de + volgt. Dat is de <label> die bij de voor de + staande <input> hoort.

De hele selector in normale mensentaal: als de laatste <input> binnen div#container is geselecteerd, doe dan iets met de gelijk daarop volgende <label>.

text-decoration: none;

Geen onderstreping.

#container input:checked:not(:last-of-type) + 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.

#container label {background: white; color: black; cursor: pointer; flex: 1 0 7em; line-height: 2.5em; text-align: center; outline: black solid 1px; position: relative;}

#container input:focus:not(:last-of-type) + label, #container input:hover + label {text-decoration: underline;} {text-decoration: underline;}

Deze selector is vrijwel hetzelfde als de eerste selector iets hierboven bij De eerste selector #container input:focus:not(:last-of-type) + label. Het enige verschil: hier moet de <input>, de radioknop, niet de focus hebben, maar zijn geselecteerd.

Als dat het geval is, wordt een bij de knop horend paneel getoond. Hier wordt ervoor gezorgd dat duidelijk is, bij welke knop het getoonde paneel hoort, door het bij de knop horende <label> aan te passen: de voorgrond- en achtergrondkleur worden omgewisseld.

background: black;

Zwarte achtergrondkleur.

color: white;

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

#container input:checked + label + .tekst

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.

.tekst {background: white; color: black; display: none; border: black solid 1px; padding: 10px; position: absolute; right: -1px; bottom: 0; left: -1px; transform: translateY(100%);}

#container: het element met id="container". De <div> waar de hele constructie in zit.

input: elke <input> die in div#container zit. De radioknoppen die, als ze zijn geselecteerd, een bijbehorend paneel tonen (of, bij de laatste knop, de panelen verbergt).

:checked: maar alleen als de <input> is geselecteerd.

+: 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 div#container.

label: de <label> die gelijk op de <input> voor de + volgt. Dat is de <label> die bij de voor de + staande <input> hoort.

+: het element achter de + moet in de html direct volgen op het element voor de +. In dit geval gaat het om een element met class="tekst" dat gelijk op een <label> volgt. Beide elementen moeten ook nog dezelfde ouder hebben. Dat is hier het geval, ze hebben beide als ouder div#container.

.tekst: het element met class="tekst" dat gelijk op de <label> voor de + volgt. In elke div.tekst zit een paneel met tekst, dat wordt getoond als de bijbehorende radioknop is geselecteerd.

De hele selector in normale mensentaal: doe iets met het element met class="tekst" dat gelijk volgt op een <label> dat weer gelijk volgt op een <input> binnen het element met id="container", maar alleen als die <input> is geselecteerd.

Deze selector is iets langer dan strikt nodig is, want op de pagina staan alle <inputs>'s binnen div#container. Dus je zou kunnen volstaan met input. Maar heel vaak zullen er meer <input>'s op een pagina staan, en dan zou alleen input als selector ook voor die <input>'s gelden. Hierdoor kunnen makkelijk problemen ontstaan, zeker als later 'n <input> ergens wordt toegevoegd.

display: block;

Bij .tekst is div#tekst met display: none; verborgen. Hier wordt de <div> zichtbaar gemaakt: het paneel wordt getoond.

main

Alle <main>'s. Dat is er maar één: de belangrijkste inhoud van de pagina staat erin. (Hier is dat vrijwel alleen Latijnse flauwekultekst.)

background: white;

Witte achtergrond.

color: black;

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

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

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

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

display: 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: 95%;

Breedte.

Een breedte in procenten is normaal genomen ten opzichte van de ouder van het element, dat is hier het blok-element <body>. Een blok-element wordt normaal genomen even breed als z'n ouder. Die ouder is <html>. Omdat <html> het buitenste element is, wordt dit normaal genomen even breed als het venster van de browser.

<main> wordt dus altijd even breed als 95% van de breedte van het venster, ongeacht de breedte van het venster.

max-width: 800px;

Hier gelijk boven is een breedte van 95% van het browservenster opgegeven. In brede vensters is dat veel te breed. Daarom wordt de maximumbreedte hier beperkt tot 800 px.

margin: 10px auto 0;

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

Aan de bovenkant een kleine afstand tot de knoppenbalk. Links en rechts auto, wat hier hetzelfde betekent als evenveel. Hierdoor staat <main> altijd horizontaal gecentreerd binnen z'n ouder <body>, ongeacht de breedte van <body>.

<body> is een blok-element en wordt daarom normaal genomen 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> altijd horizontaal gecentreerd binnen het venster, ongeacht de breedte van het venster.

border: black solid 1px;

Zwart randje.

padding: 10px;

Kleine ruimte tussen buitenkant van en tekst in <main>.

h1

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

Hier zijn dat er soms twee. De tweede <h1> is alleen bedoeld voor gebruikers van een schermlezer of van de Tab-toets. Als deze de knoppenbalk hebben verlaten, terwijl nog een paneel is geopend, wordt met behulp van JavaScript een tweede <h1> getoond. Met behulp van een <button> in deze <h1> kan het paneel dan alsnog worden gesloten. Daarna verdwijnt die tweede <h1> weer.

Meer over deze constructie is te vinden bij <h1 id="extra">.

font-size: 1.5em;

Een <h1> heeft een wel heel grote letter. Dat wordt hier wat getemperd.

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;

Een <h> is normaal genomen vet. Hier wordt dat veranderd in normaal.

text-align: center;

Tekst horizontaal centreren.

margin-top: 10px;

Een <h1> heeft standaard een marge aan boven- en onderkant. Hier wordt de marge aan de bovenkant iets verkleind.

#extra

Het element met id="extra". Dit is een tweede <h1>, die alleen is bedoeld voor gebruikers van een schermlezer of van de Tab-toets. Als deze de knoppenbalk hebben verlaten, terwijl nog een paneel is geopend, wordt met behulp van JavaScript een tweede <h1> getoond. Normaal genomen is deze <h1> verborgen. Met behulp van een <button> in deze <h1> kan het paneel dan alsnog worden gesloten. Daarna verdwijnt die tweede <h1> weer.

Meer over deze constructie is te vinden bij <h1 id="extra">.

display: none;

Verbergen. Alleen als dat nodig is, wordt de <h1> met de <button> met behulp van JavaScript getoond.

#sluit

Het element met id="sluit". Dit is een <button>, die is bedoeld voor gebruikers van een schermlezer of van de Tab-toets. Deze <button> zit in een extra <h1>, die normaal genomen is verborgen. Als gebruikers van schermlezer of Tab-toets de knoppenbalk hebben verlaten, terwijl nog een paneel is geopend, wordt met behulp van JavaScript de extra <h1>, en daarmee deze <button>, getoond. Met behulp van de <button> kan het paneel dan alsnog worden gesloten. Daarna verdwijnt die tweede <h1> met <button> weer.

Meer over deze constructie is te vinden bij <h1 id="extra">.

Zodra een radioknop is geselecteerd, wordt de <h1> met de <button> met behulp van JavaScript getoond. Maar dit is alleen bedoeld voor gebruikers van schermlezer of Tab-toets, want andere bezoekers kunnen panelen simpel sluiten door het aanraken of -klikken van de knop met 'Paneel sluiten'. Dus meestal moet de <h1> met de <button> onzichtbaar zijn. De <h1> is verder niet van belang, want daarin zit alleen de <button>. Dus als die <button> is verborgen, is de <h1> leeg en automatisch ook onzichtbaar.

Voor schermlezers kan de tekst in de knop met css worden verborgen, want die wordt dan toch gewoon voorgelezen. Dat gebeurt hier bij deze selector. Schermlezers lezen de tekst dan gewoon voor, terwijl anderen er niets van zien.

Het mooist zou het zijn, als je de <button> met de tekst volledig zou kunnen verbergen, door deze bijvoorbeeld links buiten het scherm te parkeren. Dan lezen schermlezers de tekst voor, maar de lay-out wordt niet verstoord. Dat kan echter niet, want dan werkt de <button> niet in schermlezer TalkBack op Android in oudere versies dan 8.1. Om daarin te werken moet een <button> minstens 1 x 1 px groot zijn, binnen het browservenster staan, en er mogen geen andere elementen overheen staan. Voor VoiceOver op iOS ouder dan versie 14 moet de <button> bovendien minstens 2 x 2 px groot zijn om te werken. Maar je kunt de <button> wel helemaal onzichtbaar maken.

Voor gebruikers van de Tab-toets moet de tekst wel worden getoond, dat gebeurt iets hieronder bij #sluit:focus, maar alleen als de Tab-toets wordt ingedrukt. Een deel van de opmaak daarvoor wordt hier al opgegeven. Dat is een kwestie van voorkeur, je zou dat ook bij #sluit:focus kunnen doen.

-webkit-appearance: none; appearance: none;

Hier staat in feite twee keer hetzelfde: appearance: none;. Waarom dat zo is, staat bij De voorvoegsels -moz-, -ms- en -webkit-.

Elke browser en/of besturingssysteem heeft een eigen standaardweergave van een radioknop. Die wordt hiermee uitgeschakeld.

background: white;

Achtergrondkleur wit.

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: 2px; height: 2px;

De <button> zo klein mogelijk maken.

overflow: hidden; De tekst in de extra sluitknop is gewoon zichtbaar.

Hier gelijk boven is die <button> dan wel 1 x 1 px klein gemaakt, maar et staat tekst in de <button>. Standaard staat overflow op visible. Hierdoor wordt inhoud van een element altijd weergegeven, ook als die inhoud te groot is voor het element, zoals hier het geval is en op de afbeelding is te zien.

De lay-out wordt dan misschien verpest, maar er verdwijnt in ieder geen tekst of zo. Hier is dat weergeven echter niet nodig. Daarom wordt met overflow: hidden; het teveel verborgen. Omdat de <button> slechts 1 x 1 px groot is, wordt feitelijk alles verborgen.

font-size: 2em;

Tamelijk grote letter, zodat de melding goed opvalt.

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.

outline: none;

Als een element de focus heeft, geeft een kadertje dat aan. Dat is belangrijk voor gebruikers van de Tab-toets, want die weten dan bijvoorbeeld welke link er wordt gevolgd, als ze op Enter drukken.

In alle andere gevallen maakt de outline de <button> zichtbaar, terwijl dat nou net niet de bedoeling is. Daarom wordt de outline hier verwijderd. Voor gebruikers van de Tab-toets wordt deze later bij #sluit:focus weer teruggezet.

border: none; margin: 0; padding: 0;

In het kader van het onzichtbaar maken van de <button> worden ook deze dingen verwijderd.

position: absolute;

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

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

top: 5px; left: 5px;

10 px vanaf de bovenkant en vanaf links neerzetten.

z-index: 50;

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

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

#sluit: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.

#sluit {-webkit-appearance: none; appearance: none; background: white; color: black; width: 2px; height: 2px; overflow: hidden; font-size: 2em; outline: none; margin: 0; padding: 0; position: absolute; top: 5px; left: 5px; z-index: 50;}

Het element met id="sluit", maar alleen als dit de focus heeft. Dit is een <button>, die is bedoeld voor gebruikers van een schermlezer of van de Tab-toets. Een uitgebreider verhaal over het hoe en waarom hiervan staat iets hierboven bij #sluit.

De meeste opmaak is al hierboven bij #sluit gegeven, maar daar is de <button> ook vrijwel verborgen. Dat maakt voor schermlezers niets uit, maar wel voor gebruikers van de Tab-toets. Onzichtbare tekst lees, als je niet toevallig als schermlezer bent gereïncarneerd, wat lastig.

width: 80%;

De eerder opgegeven breedte van 1 px wordt hier vergroot, waardoor de tekst in de <button> zichtbaar kan worden.

Een breedte in procenten is normaal genomen ten opzichte van de ouder van het element. Dat is hier het blok-element <main>. Een blok-element wordt normaal genomen even breed als z'n ouder. De ouder van <main> is <body>, ook weer een blok-element. <body> wordt daarom normaal genomen 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. Uiteindelijk krijgt button#sluit hierdoor een breedte van 80% van het venster, ongeacht de breedte van het venster.

max-width: 400px;

Hier gelijk boven is een breedte van 80% van het browservenster opgegeven. Dat is in brede vensters veel te breed. Daarom wordt hier een maximumbreedte opgegeven.

height: auto;

Bij #sluit is een hoogte van 1 px opgegeven om de <button> zoveel mogelijk te verbergen. Hier wordt de hoogte naar de standaardwaarde auto teruggezet. Daardoor wordt de <button> precies hoog genoeg, om de erin zittende tekst weer te kunnen geven.

outline: red solid 2px;

Rood kadertje, zodat de <button> beter opvalt.

padding: 5px;

Kleine afstand tussen buitenkant van en tekst in de <button>.

css voor vensters minimaal 760 px breed

@media screen and (min-width: 760px)

De css die hier tot nu toe staat, geldt voor alle browservensters. De css die binnen deze media query staat, geldt alleen voor vensters die minimaal 760 px breed zijn. In deze bredere vensters wordt, om te lange regels te voorkomen, de tekst binnen de panelen in twee kolommen weergegeven.

@media: geeft aan dat het om css gaat die alleen van toepassing is, als aan bepaalde voorwaarden wordt voldaan. Al langer bestond de mogelijkheid om met behulp van zo'n @media-regel css voor bijvoorbeeld printers op te geven. css3 heeft dat uitgebreid tot 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.

(min-width: 760px): het venster moet minimaal 760 px breed zijn. Is het venster smaller, 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 (min-width: 760px) { 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 }.

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 ppi ('pixels per inch'). (Vaak wordt foutief de eenheid dpi ('dots per inch') gebruikt. Die eenheid is voor printers.) Als dat mobieltje een resolutie van 192 ppi heeft, 192 pixels per inch, zijn de pixels ervan twee keer zo klein als op een origineel beeldscherm. Er zijn per inch twee keer zoveel schermpixels aanwezig.

Om nu te voorkomen dat alles op dat mobieltje twee keer zo klein wordt, geeft het mobieltje niet het echte aantal schermpixels (1024 x 768), maar een lager aantal css-pixels door bij een media query. De 192 ppi van het mobieltje is twee keer zo veel als de 96 ppi van een normaal beeldscherm. Het aantal css-pixels is dan het aantal schermpixels gedeeld door 2. 1024 x 768 gedeeld door 2 is 512 x 384 px. Het aantal css-pixels is 512 x 384 px en zit daarmee dus ruim onder de 760 px van deze media query. Je bent dus niet opgelicht, of in ieder geval niet wat betreft het aantal pixel.

Door deze truc is een lijn van 1 px breed op een normaal beeldscherm ook op het mobieltje nog steeds 1 px breed, alleen wordt die ene (css-)pixel opgebouwd uit twee schermpixels (feitelijk vier, want het verhaal geldt voor breedte én hoogte). De dikte van het lijntje is hetzelfde, maar het is veel fijner opgebouwd. Bij lijntjes is dat verschil bijvoorbeeld in bochten goed te zien.

Hetzelfde verhaal geldt voor hogere resoluties, Een tablet met een breedte van 4096 schermpixels en een ppi van 384 (vier keer de originele dichtheid) geeft 4096 gedeeld door 4 = 1024 css-pixel door. Het lijntje van 1 px breedte op de originele monitor is nog steeds 1 css-pixel breed op de tablet, maar die ene css-pixel is nu opgebouwd uit zestien schermpixel.

(Overigens kun je met behulp van media query's ook testen op de resolutie met gebruik van het sleutelwoord 'resolution'. Apple gebruikt het niet-standaard 'device-pixel-ratio', maar het idee is hetzelfde. Dit kan bijvoorbeeld handig zijn om te bepalen, hoe groot een foto moet zijn.)

Kort samengevat: omdat niet het aantal schermpixels (waarvoor je hebt betaald), maar het aantal css-pixels (de door de ontwerper bedoelde afmeting) wordt doorgegeven, wordt voorkomen dat een hogeresolutiescherm onleesbaar klein wordt.

.tekst

Deze selector werkt alleen in vensters minimaal 760 px breed. Voor andere vensters is de uitleg hieronder niet van belang.

Voor deze elementen is eerder css opgegeven. Deze wordt binnen dit blokje herhaald in de volgorde, waarin deze in de stylesheet staat, zodat alles hier overzichtelijk bij elkaar staat. (Alleen wat binnen deze media query geldig is, wordt binnen dit blokje herhaald.)

.tekst {background: white; color: black; display: none; border: black solid 1px; padding: 10px; position: absolute; right: -1px; bottom: 0; left: -1px; transform: translateY(100%);}

#container input:checked + label + .tekst {display: block;}

Alle elementen met class="tekst". De zes <div>'s waarin de panelen zitten.

columns: 2;

Tekst in twee kolommen weergeven om te lange en daardoor slecht leesbare regels te voorkomen.

Als de kolommen te hoog worden, moet vanaf de onderkant van 'n kolom terug naar boven worden gescrold om de volgende kolom te kunnen lezen. Dat is ongelooflijk onhandig. Daarom is deze eigenschap op dit moment alleen bruikbaar voor hele korte teksten. Er wordt wel aan gewerkt om dat op te lossen met een nieuw soort overflow, maar het zal nog wel even duren, voordat dat klaar is.

column-gap: 20px;

Ruimte tussen de twee kolommen.

padding: 15px;

Kleine ruimte tussen buitenkant van en tekst in div.tekst.

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.

Omdat het om een iets groter script gaat, is dit in een apart bestand gezet: menu-048.js in de map 048-js. Het script wordt met behulp van <script src="048-js/menu-048.js"></script> aan de pagina gekoppeld.

// menu-048.js

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

(function () {

function: het sleutelwoord waarmee het begin van een functie wordt aangegeven. Een functie is een stukje bij elkaar horende code. (Het haakje helemaal aan het begin wordt iets verderop beschreven.)

(): achter een functie moeten twee haakjes staan. Behalve dat het gewoon zo hoort, kun je hier ook van alles in stoppen om door te geven aan de code in het binnenste van de functie, maar bij deze functie gebeurt dat niet.

{: geeft het begin van de code binnen de functie aan. Aan het eind van de functie, dat is 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.

;: de puntkomma geeft het eind van de regel aan. In gewone tekst zou je hier een punt gebruiken.

We hebben hier een script, waarin alle code binnen een functie is geplaatst. Alle code staat tussen { en }, zodat de computer het begin en einde van de functie kan herkennen.

Een functie is een stukje bij elkaar horende code dat je, zo vaak als je wilt, kunt uitvoeren. Als je de tafel van twaalf op het scherm wilt zetten, hoef je niet twaalf vermenigvuldigingen in JavaScript te schrijven, maar schrijf je 'n functie voor één vermenigvuldiging, die je vervolgens tien keer (op steeds iets andere wijze) uitvoert.

Een functie heeft echter één probleem: de code in de functie wordt pas uitgevoerd, als de functie wordt aangeroepen. Dat aanroepen kan op allerlei manieren gebeuren, bijvoorbeeld als de gebruiker 'n toets indrukt, als de pagina is geladen, als het 13:13 is op vrijdag de dertiende, noem maar op. Maar zonder te worden aangeroepen doet een functie helemaal niets.

In dit script is dat ook zo. Er zit bijvoorbeeld een functie in die reageert op het indrukken van Escape. 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 'extra' en 'sluit'. 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.

De ; aan het eind geeft het eind van de regel aan. In gewone tekst zou je hier een punt gebruiken.

var inputs = document.querySelectorAll("#container input")

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 vijf variabelen worden aangemaakt: in elke regel één.

Pardon? Variwiewatwaar? Ha, leuk dat je het vraagt.

var: het begin van het eerste deel van de eerste 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 'inputs', 'len', 'extra', 'sluit', en 'i'. Elke variabele staat op een eigen regel. Dat is niet nodig voor de computer, maar het maakt het leesbaarder voor mensen.

In 'inputs', 'len', 'extra', 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 worden hieronder alle <input>'s opgezocht met document.querySelectorAll("container input") en opgeslagen in de variabele 'inputs'. Je zou ook elke keer al die <input>'s 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.

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.

Hieronder worden de aangemaakte variabelen één voor één doorgenomen.

inputs = document.querySelectorAll("#container input"),

Als je in de html een andere id dan 'container' hebt gebruikt bij div#container, moet je die id ook in bovenstaande regel aanpassen.

Het tweede deel van de eerste regel. Hier wordt de eerste variabele aangemaakt.

inputs: 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.

querySelectorAll("#container input"): het middelste stukje querySelectorAll is een zogenaamde 'functie'. Een functie is een stukje in de browser ingebakken code, waarmee je iets kunt doen. Deze functie is te vinden in het object document, vandaar dat document ervoor staat.

Een object is een bij elkaar horende verzameling van functies en andere code. Een van die objecten heeft de naam 'document'. Bij een object werkt een functie alleen een klein beetje anders dan een gewone functie, daarom heet een functie uit een object 'methode'.

JavaScript heeft een groot aantal ingebakken objecten, waar je gebruik van kunt maken. In document bijvoorbeeld zit heel veel informatie over de pagina, en er zitten heel veel methodes in om met die informatie te kunnen werken.

Met de methode querySelectorAll() uit document kan JavaScript alle elementen opzoeken, die aan een bepaalde voorwaarde voldoen, zoals allHieronder worden dee <div>'s, alle elementen met een class="hoera", en dergelijke. Het gegeven waarnaar wordt gezocht, staat tussen aanhalingstekens tussen de haakjes:

querySelectorAll("#container input")

Hierbij is de syntax van het deel tussen de aanhalingstekens precies hetzelfde als bij een selector in css. In bovenstaande regel wordt naar de alle <input>'s binnen het element met id="container", net zoals je in css in een selector #container input {...} zou gebruiken.

Zou je naar alle <span>'s zoeken die een eerstHieronder worden dee kind zijn, dan zou je de volgende regel gebruiken:

querySelectorAll("span:first-child")

Precies zoals selectors in css werken.

,: helemaal aan het eind staat nog een komma. De regel eindigt hier niet, er moeten nog meer variabelen worden aangemaakt.

De hele declaratie inputs = document.querySelectorAll("#container input"): sla alle <input>'s binnen div#container, zeven in totaal, op in variabele inputs. Dat wil zeggen dat in variabele inputs 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 inputs. 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 inputs.

Van al deze informatie in inputs 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 inputs opgeslagen. Er zitten dus eigenlijk zeven objecten in inputs. Naast allerlei informatie die in elk object in inputs wordt opgeslagen, kun je daardoor ook gebruik maken van allerlei methoden, die JavaScript gratis en voor niets toevoegt aan de objecten in inputs.

Als in een variabele maar één object is opgeslagen, is dat simpel aan te roepen: gewoon de naam van de variabele gebruiken. Maar hier worden zeven objecten in dezelfde variabele opgeslagen. Om dat te kunnen doen, worden ze bewaard in de vorm van een lijst. Elk item in die lijst is dan één van de zeven 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: inputs[0]. Tussen de teksthaken staat het volgnummer. Het zesde object kan het script vinden met inputs[5]. 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.)

Het script gebruikt de objecten in variabele inputs, de zeven <input>'s, om te luisteren of een radioknop <type input="radio"> wordt geselecteerd. Als dat het geval is, worden door het script bepaalde aanvullende handelingen uitgevoerd.

len = inputs.length,

len: in deze variabele wordt weer iets opgeslagen. Over wat een variabele is, is meer te vinden bij var inputs = document.querySelectorAll("#container input"), .

=: 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.

inputs.length: dit is, wat in de variabele len wordt opgeslagen.

inputs: gelijk hierboven zijn bij inputs = document.querySelectorAll("#container input"), in inputs in de vorm van objecten alle <inputs>'s binnen div#container opgeslagen.

length: in inputs zelf zit ook informatie, zoals het aantal erin opgeslagen objecten. Dat aantal zit in length. In het voorbeeld is dat 7.

In inputs.length zit dus eigenlijk gewoon 7, want dat aantal <input>'s is eerder gevonden. Maar als eerder 327 <input>'s zouden zijn gevonden, zou het 327 zijn. Dat is het handige van variabelen: ze kunnen veranderen.

,: helemaal aan het eind staat nog een komma. De regel eindigt hier niet, er moeten nog meer variabelen worden aangemaakt.

Alles nog eens samengevat: berg in len het aantal in inputs zittende <inputs>'s op.

In het voorbeeld zou je in het script len overal door 7 kunnen vervangen, dat zou geen enkel verschil maken. Maar als er dan een <input> bij zou komen, zou je overal 7 door 8 moeten vervangen. Nu wordt er gewoon één <input> meer gevonden en verandert de waarde in length zonder enige verandering in het script.

In plaats van len zou je ook overal inputs.length kunnen gebruiken. Dat zou hier nog wel gaan, omdat inputs.length nog redelijk leesbaar is. Maar een script wordt al snel volledig onleesbaar, als je zo zou werken. Daarom is het een gewoonte dit soort dingen in variabelen te stoppen met een begrijpelijke naam (inputs is de lijst met <input>'s, len is een afkorting van length, de hieronder gebruikte variabele i is een afkorting van 'index').

Bovendien kan de inhoud van een variabele het gevolg van een behoorlijk ingewikkelde operatie zijn. In de variabele inputs hier gelijk boven bijvoorbeeld zit het resultaat van de zoektocht naar alle <input>'s binnen div#container. Je zou de variabele inputs overal kunnen vervangen door die zoektocht. Maar dan moet die zoektocht elke keer volledig opnieuw worden uitgevoerd. Door het resultaat van de zoektocht in een variabele op te slaan, hoeft de zoektocht maar één keer te worden uitgevoerd.

extra = document.querySelector("#extra"),

Als je in de html een andere id dan 'extra' hebt gebruikt bij h1#extra, moet je die id ook in bovenstaande regel aanpassen.

extra: 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.

querySelector("#extra"): 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 dan een gewone functie, daarom heet een functie uit een object 'methode'.

JavaScript heeft een groot aantal ingebakken objecten, waar je gebruik van kunt maken. In document bijvoorbeeld zit heel veel informatie over de pagina, en er zitten heel veel methodes in om met die informatie te kunnen werken.

Met de methode querySelector() uit document kan JavaScript het eerste element van 'n bepaalde soort opzoeken, zoals de eerste <div>, het eerste element met een class="hoera", en dergelijke. Het gegeven waarnaar wordt gezocht, staat tussen aanhalingstekens tussen de haakjes:

querySelector("#extra")

Hierbij is de syntax van het deel tussen de aanhalingstekens precies hetzelfde als bij een selector in css. In bovenstaande regel wordt naar het element met id="extra" gezocht, net zoals je in css in een selector #extra {...} zou gebruiken.

Zou je naar de eerste <span> zoeken die een eerste kind is, dan zou je de volgende regel gebruiken:

querySelector("span:first-child")

Precies zoals selectors in css werken.

De hele declaratie extra = document.querySelector("#extra"): sla het element met id="extra" op in extra. Dat wil zeggen dat in variabele extra een ongelooflijke hoeveelheid informatie over h1#extra wordt gestopt, waar het script later gebruik van kan maken. Zo zit bijvoorbeeld alle css, die aan h1#extra is gegeven, ook in extra. 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 h1#extra en hun css, attributen, enzovoort, zitten ook in extra.

Van al deze informatie in extra 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 h1#extra in de vorm van een object in extra opgeslagen. Naast allerlei informatie die in dat object zelf in extra wordt opgeslagen, kun je daardoor ook gebruik maken van allerlei methoden, die JavaScript gratis en voor niets toevoegt aan het object in extra.

Het script gebruikt het object in variabele extra, de h1#extra, om een <h1> te tonen, met daarin een extra <button>. Als gebruikers van een schermlezer of van de Tab-toets de knoppenbalk verlaten, terwijl nog een paneel is geopend, kan dat paneel daarmee alsnog worden gesloten.

,: helemaal aan het eind staat nog een komma. De regel eindigt hier niet, er moeten nog meer variabelen worden aangemaakt.

sluit = document.querySelector("#sluit"),

Als je in de html een andere id dan 'sluit' hebt gebruikt bij button#sluit, moet je die id ook in bovenstaande regel aanpassen.

Deze regel is vrijwel hetzelfde als die hierboven bij extra = document.querySelector("#extra"), Hier gaat het om button#sluit, die als object wordt opgeslagen in variabele sluit. Als gebruikers van een schermlezer of van de Tab-toets de knoppenbalk verlaten, terwijl nog een paneel is geopend, kan dat paneel met deze <button> alsnog worden gesloten.

i;

i: deze variabele wordt alleen aangemaakt, er wordt nog niets in opgeborgen. Deze wordt later op een aantal plaatsen als een soort teller ('index') gebruikt.

;: dit geeft het einde van de regel aan, net zoals een punt dat in gewone tekst doet. Er hoeven geen verdere variabelen meer aangemaakt te worden.

document.addEventListener("keydown", escapeSluitPanelen);

document: dit is een zogenaamd 'object'. Een object in JavaScript is iets, waarin onder andere allerlei gegevens zijn opgeslagen. In document is de hele pagina opgeslagen op een voor JavaScript toegankelijke manier. (Je hebt hele series objecten voor de wildste dingen, maar de meeste zijn hier verder niet van belang.)

Het voor JavaScript toegankelijke model van de pagina heet 'Document Object Model', afgekort tot 'DOM'. Dit wordt automatisch door de browser gemaakt, als de pagina wordt geladen. Door de regel te beginnen met document, weet het script waar de rest van de code op deze regel bij hoort: bij de hele pagina. (Je kunt ook code koppelen aan één specifieke <div>, of aan één specifieke <input>, of ... Dat gebeurt in dit script verderop ook.)

addEventListener: er wordt een zogenaamde 'eventlistener' gekoppeld aan het voor de punt staande document, aan de pagina.

Een eventlistener luistert naar een gebeurtenis. Die gebeurtenis, de 'event', kan van alles zijn: het indrukken van een toets, klikken, scrollen, de video is afgespeeld, van alles. Tussen de haakjes van addEventListener() staat, naar welke soort gebeurtenis moet worden geluisterd, en wat er moet gebeuren, als die gebeurtenis zich voordoet. Zeg maar 'n soort rampenplan: áls gebeurtenis is 'doodsmak', dán handeling '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': als een toets wordt ingedrukt. Maakt niet uit welke toets.

Deze regel roept de functie aan, die het eigenlijke werk gaat doen.

escapeSluitPanelen: 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 escapeSluitPanelen (e) { en zorgt ervoor dat, als Escape wordt ingedrukt, eventueel geopende panelen worden gesloten.

(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...)

;: de puntkomma geeft het eind van de regel aan. In gewone tekst zou je hier een punt gebruiken.

De hele regel nog eens in gewone mensentaal: als een toets wordt ingedrukt, maakt niet uit welke toets of waar, voer dan de functie 'escapeSluitPanelen' uit.

De eventlistener wordt hier aan document gekoppeld: de hele pagina. Dat betekent dat de functie overal wordt aangeroepen, waar een toets wordt ingedrukt, ongeacht waar op de pagina je bent. Je zou de eventlistener ook kunnen koppelen aan een deel van de pagina, maar dat is hier niet nodig: het maakt in dit geval niets uit, wanneer een toets wordt ingedrukt.

for (i = 0; i < (len - 1); 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. (De } staat in werkelijkheid niet aan het eind van de regel, maar lager, achter de code die bepaalt, wat er waarmee moet gebeuren.)

Hier eerst het deel dat ervoor zorgt dat elke eerder bij inputs = document.querySelectorAll("#container input"), gevonden <input> netjes aan de beurt komt. Of eigenlijk: elke in variabele inputs opgeborgen object.

(: met dit haakje opent het deel dat ervoor zorgt dat elk <input>-object aan de beurt komt. Aan het eind van de regel staat de bijbehorende ), waardoor het script weet dat dit het einde van dit deel van de code is.

i = 0;: met het isgelijkteken geef je in JavaScript aan dat in de voor het isgelijkteken staande variabele het resultaat van wat achter het isgelijkteken staat, moet worden opgeslagen. In dit geval is dat resultaat heel simpel: het is het getal 0.

De variabele heet hier 'i'. De variabele is eerder al aangemaakt bij i;, maar wordt nu pas echt gebruikt. i wordt gebruikt als een teller (waarover later meer), en bij dit soort tellers is het gebruikelijk ze de korte naam 'i' (van 'index') te geven. Dat soort gewoontes maakt het voor mensen een stuk makkelijker elkaars code te lezen en te begrijpen.

Omdat i een variabele is, kan de waarde ervan veranderen, variëren. Wat later in de regel ook gaat gebeuren: elke keer als er een <input>-object is afgehandeld, wordt i met 1 verhoogd. Dat gebeurt in het laatste deel van de regel.

De ; geeft aan dat dit stukje code, waarin de variabele i z'n beginwaarde krijgt, hier eindigt.

i++: eerst het laatste deel van de regel, het middelste deel komt gelijk hieronder.

Elke keer als een in inputs zittend <input>-object is afgehandeld, 1 optellen bij i. Omdat programmeurs liederlijk lui zijn, wordt 'er 1 bij optellen' ingekort tot ++.

Als i 0 is, betekent i++: i wordt 0 + 1 ('1' dus).

Als i 1 is, betekent i++: i wordt 1 + 1 ('2' dus).

Als i 2 is, betekent i++: i wordt 2 + 1 ('3' dus).

Als i 5 is, betekent i++: i wordt 6 + 1 ('6' dus).

En meer dan 6 wordt het niet, vanwege redenen die gelijk hieronder staan.

(Op een pagina met een ander aantal <inputs>'s dan 7 zal eerder of later worden gestopt met het verhogen van i.)

i < (len - 1);: het middelste deel van de regel.

Net als bij gewoon rekenen geven de haakjes aan, dat het deel tussen de haakjes als eerste moet worden uitgevoerd. (Die haakjes zijn hier niet echt nodig, maar het maakt het leesbaarder voor mensen.)

In len is eerder bij len = inputs.length, het aantal <input>-objecten in inputs opgeslagen. Omdat er in dit voorbeeld zeven <inputs>'s op de pagina staan, is de waarde van len 7. Als je daar 1 van aftrekt, blijft 6 over. (len - 1) kun je dus vervangen door 6, en dan staat er: i < 6;,

i bevat een getal. Voordat het eerste <input>-object is verwerkt, is dat getal 0, want dat is hierboven opgegeven.

Het teken < betekent: kleiner dan.

De ; aan het einde geeft aan dat dit stukje code, de voorwaarde waaraan moet worden voldaan, hier eindigt.

In gewone mensentaal staat hier: zolang teller i kleiner is dan 6.

Als niet meer aan deze voorwaarde wordt voldaan, als teller i niet meer kleiner is dan (len - 1), dan 6, stop dan met het uitvoeren van de code die tussen de {} van de for-lus staat. Omdat len even groot is als het aantal <input>-objecten in inputs, geeft dit een mogelijkheid om elk <input>-object precies één keer te verwerken. Behalve het laatste, want len is met 1 verlaagd. En er, als ze allemaal verwerkt zijn, mee te stoppen.

(Die laatste <input> is een apart geval, want die wordt gebruikt om panelen te sluiten. Alle andere <input>'s openen juist een paneel. Dus die laatste moet apart worden behandeld, vandaar.)

): dit haakje hoort bij de ( gelijk achter for. Tussen deze twee haakjes staat de code die ervoor zorgt dat elk <input>-object aan de beurt komt.

{: het laatste teken op de regel. Hiermee geef je aan dat hierna de code volgt die uitgevoerd moet worden. Tot nu toe is alleen gezorgd dat er bij elk <input>-object (behalve de laatste) íéts moet gebeuren, maar nog niet wát. Dat wát volgt na deze {. Na de code die moet worden uitgevoerd staat nog een afsluitende }. Hiermee geef je aan dat het uitvoerende deel van de code bij de for-lus hier stopt.

Dat uitvoerende deel is op een nieuwe regel gezet, net zoals de afsluitende }. Dat soort dingen zijn informele afspraken, omdat het de code voor mensen leesbaarder maakt. Wat de computer betreft zou je alles ook achter elkaar kunnen zetten op één onwijs lange regel. Alleen is niet alleen die regel dan onwijs, ook de gemiddelde programmeur zou heel snel bijzonder onwijs worden, als alles op één lange regel wordt gezet.

Elke keer als een <input>-object is behandeld (met het stukje code tussen de {}, wat hieronder aan de beurt komt), wordt i door middel van i++ met 1 verhoogd.

Aan het begin heeft i de waarde 0. Na de eerste ronde heeft i de waarde 1. Na de tweede ronde heeft i de waarde 2. Na de derde ronde heeft i de waarde 3. Na de 6e ronde heeft i de waarde 6. Waarmee i niet meer kleiner is dan (len - 1), dan 6, dan het aantal <input>-objecten behalve de laatste.

Omdat niet meer aan de voorwaarde i < (len - 1) wordt voldaan, wordt gestopt met het behandelen van de <input>-objecten. En dat komt goed uit, want alle <input>-objecten, behalve de laatste, zijn allemaal precies één keer aan de beurt geweest. Niet meer, niet minder. Bij allemaal, behalve de laatste, is de tussen de {} staande code één keer toegepast.

De code tussen de haakjes in gewone taal: zet teller i op 0 als de for-lus de eerste keer wordt uitgevoerd. Herhaal de for-lus zolang teller i lager is dan het aantal <input>-objecten min 1. Verhoog teller i elke keer als de for-lus wordt uitgevoerd met 1.

inputs[i].addEventListener("change", function () {

Deze regel zit binnen een for-lus en wordt voor elke <input> binnen div#container, behalve de laatste, één keer uitgevoerd.

inputs[i]: in de variabele inputs zijn bij inputs = document.querySelectorAll("#container input"), alle <input>'s die in div#container zitten opgeslagen. (Feitelijk is voor elke <input> een object opgeslagen, waarin allerlei informatie over de <input> zit, zoals of de <input> is geselecteerd of niet.)

De teksthaken [] geven aan dat hierin een teller zit. Die teller is hier de variabele i. In de for-lus, waarbinnen deze regel staat, heeft i het volgnummer gekregen van de <input> die aan de beurt is.

addEventListener: er wordt een zogenaamde 'eventlistener' gekoppeld aan het voor de punt staande deel. Dat is hier inputs[i], de <input> die aan de beurt is.

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 een radioknop verandert van geselecteerd in niet-geselecteerd, of omgekeerd.

function (): Vaak staat hier de naam van een functie, die moet worden uitgevoerd, als de eventlistener wordt geactiveerd (in dit geval door het selecteren of deselecteren van een radioknop). 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.

De code binnen deze anonieme functie zorgt ervoor dat een extra <button> verschijnt, als gebruikers van schermlezer of Tab-toets de knoppenbalk verlaten, terwijl nog een paneel is geopend. Meer hierover is te vinden bij <h1 id="extra">.

{: geeft het begin van de code binnen de functie aan. Aan het eind van de functie staat een bijbehorende }.

De hele regel samengevat: als één van de radioknoppen wordt geselecteerd of gedeselecteerd, voer dan de code uit die tussen de {} achter function () staat. (Dit geldt alleen niet voor de laatste radioknop, want de code in de for-lus geldt niet voor de laatste radioknop.)

extra.setAttribute("style", "display: block;");

Deze regel zit binnen een for-lus en wordt voor elke <input> binnen div#container, behalve de laatste, één keer uitgevoerd.

Deze regel is onderdeel van de anonieme functie die begint bij inputs[i].addEventListener("change", function () {

extra: in de variabele extra is bij extra = document.querySelector("#extra"), h1#extra opgeslagen in de vorm van een object. Een object is een bij elkaar horende verzameling van functies en andere code, waarin veel informatie over het opgeslagen element, de kinderen daarvan, enzovoort zit. Elk object heeft ook een aantal ingebakken functies, die gratis en voor niets gebruikt kunnen worden. Bij een object werkt een functie alleen een klein beetje anders dan een gewone functie, daarom heet een functie uit een object 'methode'.

setAttribute(): dit is zo'n methode. Met setAttribute() kun je een attribuut aan een element in de html toevoegen. Dat kan van alles zijn: tabindex, een WAI-ARIA-code, noem maar op. Tussen de haakjes komen de naam en de waarde van het attribuut te staan.

Omdat voor de punt extra staat, werkt deze methode op het in de variabele extra opgeslagen object, op de daarin zittende h1#extra.

("style", "display: block;"): voor de komma staat tussen aanhalingstekens de naam van het attribuut dat wordt toegevoegd, achter de komma staat tussen aanhalingstekens de waarde van het attribuut.

In dit geval wordt:

<h1 id="extra">

veranderd in:

<h1 id="extra" style="display: block;">

Als je de aan de pagina toegevoegde css wilt bekijken moet je niet in de gewone code, maar in de Gegenereerde code kijken.

;: De puntkomma geeft het eind van de regel aan. In gewone tekst zou je hier een punt gebruiken.

De hele regel in gewone taal: voeg aan h1#extra de inline-stijl style="display: block"; toe. Omdat dit bij inputs[i].addEventListener("change", function () { is gekoppeld aan het selecteren of deselecteren van een radioknop (behalve de laatste), gebeurt dit alleen, als een paneel wordt geopend of gesloten.

Hierdoor wordt een extra <button> zichtbaar, waarmee gebruikers van schermlezer of Tab-toets alsnog een paneel kunnen sluiten, als ze de knoppenbalk hebben verlaten, terwijl nog een paneel was geopend. Meer over deze constructie is te vinden bij <h1 id="extra">.

inputs[len - 1].addEventListener("change", function () {

inputs[len - 1]:

inputs[]: in de variabele inputs zijn bij inputs = document.querySelectorAll("#container input"), alle <input>'s die in div#container zitten opgeslagen. (Feitelijk is voor elke <input> een object opgeslagen, waarin allerlei informatie over de <input> zit, zoals de of de <input> is geselecteerd of niet.)

De teksthaken [] geven aan dat hierin een teller zit. Die teller bevat het volgnummer van de <input>, waarvoor deze regel geldt.

[len - 1]: in variabele len is bij len = inputs.length, het aantal objecten, het aantal <input>'s, dat in variabele inputs zit opgeslagen. Dat zijn er hier zeven: zes radioknoppen om een paneel te openen, en één om de panelen te sluiten: de laatste.

In len zit dus het getal 7. Eigenlijk staat hier dus [7 - 1]. Je hoeft geen professor in de rekenkunde te zijn om hieruit te concluderen, dat hier eigenlijk gewoon simpelweg 6 staat. (In dit geval. Als er meer of minder <inputs>'s zouden zijn, zou het getal anders zijn. Dat is het handige van variabelen: de computer telt voor je en past het aantal automatisch aan.)

Omdat computers dol zijn op het getal 0, beginnen ze vaak te tellen met 0. Dat is hier ook zo. Het eerste object in inputs heeft volgnummer 0, het tweede volgnummer 1, enzovoort, tot het laatste, dat volgnummer 6 heeft: 1 minder dus dan het aantal objecten in inputs. [len - 1] is dus altijd het volgnummer van het laatste object, van de laatste <input>, ongeacht het aantal <input>'s.

Omdat deze laatste <input>, als enige, panelen sluit, moet deze anders worden behandeld dan al z'n voorgangers, die juist een paneel openen.

inputs[len - 1] samen: doe iets met de laatste in de vorm van een object in variabele inputs opgeslagen <input>, ongeacht hoeveel <input>'s er zijn.

addEventListener: er wordt een zogenaamde 'eventlistener' gekoppeld aan het voor de punt staande deel. Dat is hier inputs[len - 1], de laatste <input>.

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 een radioknop verandert van geselecteerd in niet-geselecteerd, of omgekeerd.

function (): Vaak staat hier de naam van een functie, die moet worden uitgevoerd, als de eventlistener wordt geactiveerd (in dit geval door het selecteren of deselecteren van een radioknop). 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.

De code binnen deze anonieme functie voorkomt dat een extra <button> verschijnt om panelen alsnog te kunnen sluiten, als gebruikers van schermlezer of Tab-toets de knoppenbalk verlaten, terwijl alle panelen al zijn gesloten. Meer hierover is te vinden bij <h1 id="extra">.

{: geeft het begin van de code binnen de functie aan. Aan het eind van de functie staat een bijbehorende }.

De hele regel samengevat: als de laatste radioknop wordt geselecteerd of gedeselecteerd, voer dan de code uit die tussen de {} achter function () staat.

extra.removeAttribute("style");

Deze regel is onderdeel van de anonieme functie die begint bij inputs[len - 1].addEventListener("change", function () {

Deze regel is vrijwel hetzelfde als die iets hierboven bij extra.setAttribute("style", "display: block;");. De beschrijving is daar te vinden.

De verschillen: deze regel is onderdeel van een andere anonieme functie en wordt alleen voor de laatste <input> uitgevoerd, de <input> waarmee de panelen worden gesloten.

En hier wordt het attribuut style niet met setAttribute() toegevoegd, maar met removeAttribute() juist verwijderd. Omdat het gewoon volledig wordt verwijderd, hoeft bij removeAttribute() alleen de naam van het attribuut opgegeven te worden, zonder waarde. Dit verandert:

<h1 id="extra" style="display: block;">

in:

<h1 id="extra">

Als er geen style-attribuut aanwezig is, gebeurt er gewoon niets.

Als gebruikers van schermlezer of Tab-toets de knoppen balk hebben verlaten, terwijl nog een paneel is geopend, wordt een extra <button> getoond, waarmee het paneel als nog kan worden gesloten. Nu de laatste radioknop is geselecteerd, zijn er geen panelen meer geopend, dus die <button> hoeft niet meer te worden getoond. Meer over deze constructie is te vinden bij <h1 id="extra">.

sluit.addEventListener("click", sluitPanelen);

sluit: in de variabele sluit is bij sluit = document.querySelector("#sluit"), button#sluit opgeslagen in de vorm van een object. Een object is een bij elkaar horende verzameling van functies en andere code, waarin veel informatie over het opgeslagen element, de kinderen daarvan, enzovoort zit. Elk object heeft ook een aantal ingebakken functies, die gratis en voor niets gebruikt kunnen worden. Bij een object werkt een functie alleen een klein beetje anders dan een gewone functie, daarom heet een functie uit een object 'methode'.

addEventListener: er wordt een zogenaamde 'eventlistener' gekoppeld aan het voor de punt staande deel. Dat is hier sluit, de variabele waarin button#sluit in de vorm van een object is opgeslagen.

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 naar een klik of, op een touchscreen, een aanraking.

sluitPanelen: 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 sluitPanelen () { { en zorgt dat bij indrukken van button#sluit openstaande panelen worden gesloten.

(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...)

;: De puntkomma geeft het eind van de regel aan. In gewone tekst zou je hier een punt gebruiken.

function escapeSluitPanelen (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. Het luisteren naar dat indrukken wordt geregeld bij document.addEventListener("keydown", escapeSluitPanelen);.

De code in deze functie regelt, wat er gebeurt, als de ingedrukte toets de Escape-toets blijkt te zijn. Er gebeuren drie dingen als Escape wordt ingedrukt:

- Alle radioknoppen worden gedeselecteerd. Hierdoor worden eventueel geopende panelen gesloten. Als er geen radioknoppen zijn geselecteerd, gebeurt er gewoon niets.

- Als een radioknop de focus heeft, wordt die verwijderd. Als geen enkele radioknop de focus heeft, gebeurt er niets.

- Bij h1#extra wordt de eventueel door het script aangebrachte inline-stijl style="display: block;" verwijderd. Als er geen inline-stijl aanwezig is, gebeurt er niets.

function: het sleutelwoord waarmee het begin van een functie wordt aangegeven.

escapeSluitPanelen: de naam van de functie. Als het beestje geen naam heeft, kun je het ook niet aanroepen en heb je er dus niets aan. (Dit klopt niet helemaal. JavaScript kent ook equivalenten van 'hé!', 'hé, jij daar!', 'hé, jij daar in die zwarte jas', en dergelijke, maar die worden hier niet gebruikt. En het is zo al ingewikkeld genoeg.)

(e): die haakjes horen nou eenmaal zo na 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 indrukken van een toets. In e zit bijvoorbeeld, welke toets is ingedrukt, of de toets blíjft ingedrukt (repeteert), en de taal waarvoor het toetsenbord is geconfigureerd.

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 indrukken van een toets. In e zit het object dat bij díé gebeurtenis hoort. Bij bijvoorbeeld een muisklik krijg je een ander object met andere informatie dan bij het indrukken van een toets.)

{: geeft het begin van de code binnen de functie aan. Aan het eind van de functie staat een bijbehorende }.

if (e.key === "Escape" || e.key === "Esc") {

Deze regel is onderdeel van function escapeSluitPanelen (e) {

Als de Escape-toets wordt ingedrukt, worden eventueel geopende panelen gesloten.

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 escapeSluitPanelen (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 gebruikt 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 in gewone taal: als de Escape-toets is ingedrukt, voer dan de code tussen de {} achter de if uit.

for(i = 0; i < len; i++) {

Deze regel is onderdeel van function escapeSluitPanelen (e) {

Deze regel wordt alleen uitgevoerd, als aan deze eerder gestelde voorwaarde is voldaan:

- De Escape-toets is ingedrukt.

Voer de code tussen de { aan het einde van de regel en de } een paar regels lager een bepaald aantal keren uit. Dat aantal wordt bepaald door variabele len, waarin bij len = inputs.length, het aantal <input>'s binnen div#container is opgeslagen: 7. De code tussen {} wordt dus zeven keer uitgevoerd, één keer voor elke <input>.

Deze regel is vrijwel hetzelfde als die bij for (i = 0; i < (len - 1); i++) {, waar een uitgebreide beschrijving is te vinden. Er is één klein verschil: hier wordt gewoon de waarde uit len gebruikt: 7. Bij die eerdere regel wordt de waarde uit (len - 1) gebruikt: 7 - 1 = 6.

inputs[i].checked = false;

Deze regel is onderdeel van function escapeSluitPanelen (e) {

Deze regel wordt alleen uitgevoerd, als aan deze eerder gestelde voorwaarde is voldaan:

- De Escape-toets is ingedrukt.

Deze regel zit binnen een for-lus en wordt voor elke <input> binnen div#container één keer uitgevoerd.

inputs[i]: in de variabele inputs zijn bij inputs = document.querySelectorAll("#container input"), alle <input>'s die in div#container zitten opgeslagen. (Feitelijk is voor elke <input> een object opgeslagen, waarin allerlei informatie over de <input> zit, zoals of de <input> is geselecteerd of niet.)

De teksthaken [] geven aan dat hierin een teller zit. Die teller is hier de variabele i. In de for-lus, waarbinnen deze regel staat, heeft i het volgnummer gekregen van de <input> die aan de beurt is.

checked = false: in de eigenschap checked is opgeslagen, of de <input> die aan de beurt is, is geselecteerd of niet. Afhankelijk daarvan zou je dan het script iets wel of niet kunnen laten doen. Maar je kunt ook checked veranderen: een geselecteerde <input> deselecteren, en omgekeerd. Dat is wat hier gebeurt: de <input> wordt gedeselecteerd. En omdat álle <input>'s aan de beurt komen, worden álle radioknoppen gedeselecteerd. En sluiten dus álle eventueel geopende panelen.

checked: in deze eigenschap is de waarde true (geselecteerd) of de waarde false (niet-geselecteerd) opgeslagen.

=: hiermee geef je in JavaScript aan dat in de voor het isgelijkteken staande eigenschap het resultaat van wat achter het isgelijkteken staat moet worden opgeslagen.

false: dit is, wat in de eigenschap checked wordt opgeslagen: niet geselecteerd.

Dit werkt eigenlijk hetzelfde als bij een zelfgemaakte variabele, waarin je eigen waardes kunt opslaan. Alleen is checked door de computer aangemaakt en kan er alleen true of false in worden opgeslagen. Meer is ook niet nodig, want een radioknop is wel of niet geselecteerd.

(Een uitgebreider verhaal over variabelen is te vinden bij var inputs = document.querySelectorAll("#container input"),. In grote lijnen is dat ook geldig voor dit stukje code.)

;: De puntkomma geeft het eind van de regel aan. In gewone tekst zou je hier een punt gebruiken.

De hele regel samengevat in gewone taal: deselecteer elke <input> binnen div#container. Als een <input> niet is geselecteerd, verandert er gewoon niets.

inputs[i].blur();

Deze regel is onderdeel van function escapeSluitPanelen (e) {

Deze regel wordt alleen uitgevoerd, als aan deze eerder gestelde voorwaarde is voldaan:

- De Escape-toets is ingedrukt.

Deze regel zit binnen een for-lus en wordt voor elke <input> binnen div#container één keer uitgevoerd.

inputs[i]: dit deel voor de punt werkt precies hetzelfde als bij inputs[i].checked = false; gelijk hierboven: elke in div#container zittende <input> wordt door het deel achter de punt bewerkt.

blur(): de <input>'s zijn in de vorm van een object in variabele inputs opgeslagen. Elk object heeft een aantal ingebakken functies. Bij een object werkt een functie alleen een klein beetje anders dan een gewone functie, daarom heet een functie uit een object 'methode'.

blur() is zo'n methode: het verwijdert een eventuele focus bij de <input> die aan de beurt is. Bij #container input:focus ...#container input:focus ... wordt de tekst in de bij de <input> horende <label> onderstreept, als de <label> de focus heeft, zodat mede hierdoor duidelijk is, bij welke <input> het getoonde paneel hoort. Als het paneel wordt gesloten, wat in de regel iets hierboven gebeurt, moet deze onderstreping ook worden weggehaald.

Door simpelweg de focus te verwijderen bij de <input>, gebeurt dat. De css-selector waarin input:focus wordt gebruikt, heeft dan geen effect meer.

extra.removeAttribute("style");

Deze regel is onderdeel van function escapeSluitPanelen (e) {

Deze regel wordt alleen uitgevoerd, als aan deze eerder gestelde voorwaarde is voldaan:

- De Escape-toets is ingedrukt.

Als gebruikers van een schermlezer of van de Tab-toets de knoppenbalk hebben verlaten, terwijl nog een paneel is geopend, wordt met behulp van JavaScript een tweede <h1> getoond: h1#extra. Met behulp van een <button> in deze <h1> kan het paneel dan alsnog worden gesloten. Daarna verdwijnt die tweede <h1> weer. Meer over deze constructie is te vinden bij <h1 id="extra">.

Als door het indrukken van Escape alle panelen zijn gesloten, hoeft die extra <h1> niet meer getoond te worden. Dat tonen gebeurt door de bij extra.setAttribute("style", "display: block;"); bij h1#extra aangebrachte inline-stijl style="display: block;".

Hier gebeurt het tegenovergestelde: die inline-stijl wordt weer weggehaald. Omdat de inline-stijl gewoon volledig wordt verwijderd, hoeft bij removeAttribute() alleen de naam van het attribuut opgegeven te worden, zonder waarde. Als er geen style-attribuut aanwezig is, gebeurt er gewoon niets.

Als je de verwijderde css wilt bekijken moet je niet in de gewone code, maar in de Gegenereerde code kijken.

function sluitPanelen () {

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 button#sluit wordt ingedrukt. Het luisteren naar dat indrukken wordt geregeld bij sluit.addEventListener("click", sluitPanelen);.

De code in deze functie regelt, wat er gebeurt, als button#sluit wordt ingedrukt.

button#sluit is een extra <button>, die alleen is bedoeld voor gebruikers van een schermlezer of van de Tab-toets. Als deze de knoppenbalk hebben verlaten, terwijl nog een paneel is geopend, wordt met behulp van JavaScript een tweede <h1> getoond. In deze <h1> zit button#sluit, waarmee het paneel dan alsnog worden gesloten. Daarna verdwijnt die tweede <h1> weer. Meer over deze constructie is te vinden bij <h1 id="extra">.

Er gebeuren vier dingen als button#sluit wordt ingedrukt:

- Alle radioknoppen worden gedeselecteerd. Hierdoor worden eventueel geopende panelen gesloten. Als er geen radioknoppen zijn geselecteerd, gebeurt er gewoon niets.

- Als een radioknop de focus heeft, wordt die verwijderd. Als geen enkele radioknop de focus heeft, gebeurt er niets.

- Bij h1#extra wordt de eventueel door het script aangebrachte inline-stijl style="display: block;" verwijderd. Als er geen inline-stijl aanwezig is, gebeurt er niets.

- h1#kop krijgt de focus.

function: het sleutelwoord waarmee het begin van een functie wordt aangegeven.

SluitPanelen: 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 }.

for(i = 0; i < len; i++) {

inputs[i].checked = false; inputs[i].blur(); } extra.removeAttribute("style");

Deze vijf regels zijn onderdeel van function sluitPanelen () {

Dit stukje code werkt precies hetzelfde als de for-lus met de vier daarop volgende regels bij function escapeSluitPanelen (e) {. Het uitgebreide verhaal staat daar, hier staat alleen kort wat ze doen.

De regel met for zorgt dat de twee gelijk daarop volgende regels met inputs[i] werken voor elke <input> binnen div#container. De eerste regel na for deselecteert elke <input>, de tweede regel na for haalt een eventuele focus weg bij elke <input>.

De vierde en laatste regel verwijdert een eventueel door het script bij h1#extra aangebrachte inline-stijl style="display: block;", zodat deze <h1> weer verdwijnt.

Als je de verwijderde css wilt bekijken moet je niet in de gewone code, maar in de Gegenereerde code kijken.

document.querySelector("#kop").focus();

Als je in de html een andere id dan 'kop' hebt gebruikt bij h1#kop, moet je die id ook in bovenstaande regel aanpassen.

Deze regel is onderdeel van function sluitPanelen () {

Vaak is het beter om iets in een variabele op te slaan, zoals beschreven bij var inputs = document.querySelectorAll("#container input"), omdat anders iets steeds opnieuw moet worden opgezocht. Of omdat er iets ingewikkelds met iets moet gebeuren en het de code leesbaarder maakt. Hier gaat het om een simpele handeling, die mogelijk zelfs nooit wordt uitgevoerd.

De functie, waarin deze regel zit, wordt alleen uitgevoerd, als button#sluit wordt ingedrukt: een extra <button>, die alleen is bedoeld voor gebruikers van een schermlezer of van de Tab-toets. Als deze de knoppenbalk hebben verlaten, terwijl nog een paneel is geopend, wordt met behulp van JavaScript een tweede <h1> getoond. In deze <h1> zit button#sluit, waarmee het paneel dan alsnog kan worden gesloten. Daarna verdwijnt die tweede <h1> weer. Meer over deze constructie is te vinden bij <h1 id="extra">.

Dit zal geen tientallen keren achter elkaar gebeuren.

Daarom wordt hier de hele handel gelijk in één keer uitgevoerd: het opzoeken van een element en het uitvoeren van de bewerking daarop, zonder de tussenliggende stap van het opslaan van het element in een variabele.

document: dit is een zogenaamd 'object'. 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.

querySelector("#kop"): 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.

Met de methode querySelector() uit document kan JavaScript het eerste element van 'n bepaalde soort opzoeken, zoals de eerste <div>, het eerste element met een class="hoera", en dergelijke. Het gegeven waarnaar wordt gezocht, staat tussen aanhalingstekens tussen de haakjes:

querySelector("#kop")

Hierbij is de syntax van het deel tussen de aanhalingstekens precies hetzelfde als bij een selector in css. In bovenstaande regel wordt naar het element met id="kop" gezocht, net zoals je in css in een selector #kop {...} zou gebruiken.

Zou je naar de eerste <span> zoeken die een eerste kind is, dan zou je de volgende regel gebruiken:

querySelector("span:first-child")

Precies zoals selectors in css werken.

In document.querySelector("#kop") wordt dus het element met id="kop" opgeslagen, ook weer in de vorm van een object. In het voorbeeld gaat het om h1#kop: de belangrijkste kopregel. De stap voor het opslaan in een variabele wordt overgeslagen, maar verder werk het min of meer hetzelfde als bij een variabele: ook in dit tijdelijke object (het wordt niet echt opgeslagen) zit heel veel informatie over h1#kop, en ook kunnen weer allerlei methoden worden gebruikt. Zo zit in het object bijvoorbeeld alle css, die aan h1#kop 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 h1#kop en hun css, attributen, enzovoort , zitten ook in het object.

Van al deze informatie in het object 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.

.focus(): dit is zo'n methode. h1#kop, het element dat bij het gevonden object hoort, krijgt de focus. Hierdoor gaan schermlezers verder met voorlezen bij h1#kop.

Zonder het geven van de focus aan h1#kop gaan sommige schermlezers terug naar het begin van de knoppenbalk, als de extra <button> voor het sluiten van panelen wordt ingedrukt. Wat natuurlijk niet opschiet, want die knoppenbalk is nou juist net verlaten, anders zou die <button> niet ingedrukt kunnen worden.

;: De puntkomma geeft het eind van de regel aan. In gewone tekst zou je hier een punt gebruiken.

De hele regel in gewone taal: geef de focus aan h1#kop.