Skip links en inhoudsopgave

Uitleg tijdelijk afbeelding laten over­vloeien in vervangende afbeelding

Laatst aangepast: .

De originele en de vervangende afbeelding met de tussenliggende fase

Korte omschrijving

Als je de knop indrukt door deze aan te raken, aan te klikken of met het toetsenbord te bedienen, wordt de originele afbeelding langzaam vervangen door een andere afbeelding. Door de knop nogmaals in te drukken, komt de originele afbeelding weer terug.

BELANGRIJK

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

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

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

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

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

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

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

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

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

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

Opmerkingen

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

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

Deze versie werkt fundamenteel anders dan vorige versies.

In vorige versies werden 75 <span>'s gebruikt, die allemaal 0 px breed en hoog waren. Elke <span> had als achtergrond-afbeelding de vervangende afbeelding. Omdat de <span>'s geen breedte en hoogte hadden, zag je de vervangende afbeelding niet. Bij hoveren over een <span> werd deze even breed en hoog gemaakt als de vervangende afbeelding, waardoor deze zichtbaar werd. Van boven naar beneden werd elke <span> iets minder doorzichtig. Als je met de muis van boven naar beneden over de <span>'s ging, verscheen hierdoor de vervangende afbeelding geleidelijk.

Dit werkt dus voor geen meter op touchscreens. Je zou met behulp van bloed, zweet, JavaScript en tranen iets soortgelijks kunnen maken, maar deze site is meer gericht op css. En met css krijg je dit dus op bovenstaande manier niet werkend.

Nog los van de problemen met hoveren op een touchscreen, is een vinger veel grover dan een muis. Als je een vinger als muis gebruikt, krijg je nooit een vloeiende overgang tussen de twee afbeeldingen. Je zou je bezoekers kunnen vragen hun wijsvinger bij te punten met een puntenslijper, maar dat ligt mogelijk wat gevoelig. Letterlijk en figuurlijk.

css3 geeft nieuwe mogelijkheden, die eerder stomweg niet bestonden.

De originele afbeelding en de vervangende afbeelding zijn beide gewone afbeeldingen. De vervangende afbeelding staat absoluut gepositioneerd boven de originele afbeelding, maar is volledig doorzichtig gemaakt. Je ziet dus bij openen van de pagina de originele afbeelding, hoewel die eigenlijk onder de vervangende afbeelding zit.

(Als je de originele afbeelding opent in een nieuw tabblad, kun je dat ook zien: niet de originele afbeelding wordt getoond, maar de vervangende. Want die is dan niet meer onzichtbaar. Dit heeft als bijwerking dat je de originele afbeelding niet kunt kopiëren, tenzij je css uitzet.)

De knop boven de afbeelding is eigenlijk een aankruisvakje. Als je dat aankruisvakje aanvinkt, wordt de vervangende afbeelding langzaam steeds minder doorzichtig, waardoor deze geleidelijk aan zichtbaar wordt. En de originele afbeelding verbergt. Omdat een aankruisvakje hier foeilelijk is, is dat verborgen onder de bij het aankruisvakje horende <label>, die eruit ziet als 'n soort knop.

Het terugzetten naar de originele afbeelding gebeurt zonder vertraging, maar als je bij #vervang een waarde opgeeft voor transition-delay kun je ook dat met een vertraging doen.

Omdat de knop eigenlijk een gewoon aankruisvakje is, kan deze ook door gebruikers van de Tab-toets worden bediend. (Met de Spatiebalk kun je het aankruisvakje aan- en uitvinken). Omdat dit niet gelijk duidelijk zal zijn, verschijnt bij gebruik van de Tab-toets gelijk voor het aankruisvakje wordt bereikt een kleine uitleg. Deze staat normaal genomen buiten het browservenster en is alleen zichtbaar, als je de Tab-toets gebruikt.

Ook schermlezers lezen deze korte uitleg voor, want dat die buiten het browservenster staat, maakt voor een schermlezer niets uit.

Schermlezers hebben wel een eigen probleem. Beide afbeeldingen zijn altijd aanwezig, al zie je er steeds maar één. Van beide afbeeldingen wordt dan ook altijd de alt-tekst gelezen, ongeacht welke afbeelding je ziet. Of de vervangende afbeelding zichtbaar of onzichtbaar is, maakt niets uit: een schermlezer kijkt alleen naar de alt-tekst. En die is gewoon aanwezig, van beide afbeeldingen.

Dit is nogal verwarrend. Daarom wordt voor schermlezers een beetje JavaScript gebruikt. Afhankelijk van of het aankruisvakje is aangevinkt of niet, wordt met behulp van aria-hidden één van beide afbeeldingen inclusief bijbehorende alt-tekst voor schermlezers verborgen. Nu wordt alleen de alt-tekst van de afbeelding die zichtbaar is voorgelezen.

Voorwaarden van het JavaScript waaraan html en css moeten voldoen

Er is steeds maar één afbeelding zichtbaar, de originele of de vervangende. Maar schermlezers lezen altijd beide alt-teksten voor, wat verwarrend is. Om dat te voorkomen, wordt een beetje JavaScript gebruikt. zonder dit script werkt alles nog gewoon, maar schermlezers lezen dan dus altijd beide alt-teksten voor.

Om dit script goed te laten werken, moet de html aan een aantal voorwaarden voldoen.

Id's

  • De <input type="checkbox"< moet een id="toon" hebben. Als je een andere id wilt gebruiken in de html, moet je die ook in het script en in de css wijzigen.
  • De originele <img> moet een id="origineel" hebben. Als je een andere id wilt gebruiken in de html, moet je die ook in het script en in de css wijzigen.
  • De vervangende <img> moet een id="vervang" hebben. Als je een andere id wilt gebruiken in de html, moet je die ook in het script en in de css wijzigen.

Het script gebruikt deze id's om de bijbehorende elementen te kunnen vinden. Als het aankruisvakje wordt aan- of uitgevinkt, wordt door het script bij de beide afbeeldingen de waarde van aria-hidden veranderd van true in false, en van false in true. (Als je die verandering wilt bekijken, moet je niet in de gewone code, maar in de Gegenereerde code kijken.)

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 wordt hiervan alleen <main> gebruikt. <main> gedraagt zich als een gewone <div>, maar dan een <div> met een semantische betekenis. Hierdoor kunnen schermlezers, zoekmachines, en dergelijke beter zien, hoe de pagina is samengesteld. De meeste schermlezers kunnen dit soort elementen ook gebruiken om snel over de pagina te navigeren.

<main>

Hierbinnen staat de belangrijkste inhoud van de pagina (in dit voorbeeld zijn dat de originele en vervangende afbeelding met alles erop en eraan).

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 wordt in dit voorbeeld één WAI-ARIA-code gebruikt: aria-hidden.

aria-hidden

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

Bij een afbeelding leest een schermlezer de bij de afbeelding horende alt-tekst voor. Op het scherm is altijd maar één afbeelding te zien, maar in werkelijkheid zijn beide afbeelding altijd aanwezig, inclusief beide alt-teksten. Een schermlezer zal daarom altijd beide alt-teksten voorlezen, ongeacht welke afbeelding zichtbaar is.

Dit is natuurlijk heel verwarrend. Bovendien is niet elke gebruiker van een schermlezer helemaal blind, en dan is het mogelijk nóg verwarrender: je ziet één afbeelding, maar hoort twee alt-teksten.

Daarom wordt met behulp van aria-hidden steeds één van beide afbeeldingen verborgen voor schermlezers, inclusief de bijbehorende alt-tekst.

Als de pagina wordt geopend, ziet de html voor de afbeeldingen er als volgt uit:

<img id="origineel" aria-hidden="false" src="053-pics/levend-standbeeld.jpg" alt="Originele (...) Amsterdam">

<img id="vervang" aria-hidden="true" src="053-pics/gitaristen.jpg" alt="Vervangende (...) Amsterdam">

Bij opening van de pagina is img#origineel, de originele afbeelding, zichtbaar. Deze heeft het attribuut aria-hidden="false": niet verbergen voor schermlezers. (Eigenlijk kun je dit hier weglaten, want dit is standaard zo. Maar om de bedoeling van deze constructie duidelijker te maken, wordt het toch gebruikt.)

Vervangende afbeelding img#vervang is bij openen van de pagina niet zichtbaar, en daar staat het attribuut aria-hidden="true": verberg voor schermlezers. Het attribuut geldt voor alles, wat binnen de <img>-tag zit, dus ook de alt-tekst van de vervangende afbeelding wordt hierdoor verborgen.

Met behulp van een paar regels JavaScript wordt gekeken, of het aankruisvakje wordt aan- of uitgevinkt. Als het wordt aangevinkt, wordt de vervangende afbeelding getoond. false en true bij aria-hidden wordt dan omgedraaid: de originele afbeelding krijgt nu aria-hidden="true", en de vervangende afbeelding krijgt aria-hidden="false".

Op het scherm is de vervangende afbeelding zichtbaar, en de bijbehorende alt-tekst is nu niet meer verborgen voor schermlezers. Tegelijkertijd wordt de alt-tekst van de originele afbeelding juist wel verborgen.

Als het aankruisvakje weer wordt uitgevinkt, gebeurt het omgekeerde.

(De reden dat display: none; en dergelijke niet gebruikt kan worden, om één van beide afbeeldingen te verbergen, staat bij opacity: 0;.)

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.

In dit voorbeeld wordt tabindex="-1" niet gebruikt.

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

Het aankruisvakje dat onder de knop zit verstopt, kan worden bediend met de Spatiebalk. Gebruiker van de Tab-toets zullen weten, hoe je een aankruisvakje aan- of uitvinkt, maar dit aankruisvakje is niet herkenbaar als aankruisvakje. Daarom is een korte uitleg voor gebruikers van de Tab-toets toegevoegd:

<p id="uitleg" tabindex="0">Gelijk op deze tekst volgt een aankruisvakje, vermomd als knop. Als je dit aanvinkt (met Spatiebalk of op soortgelijke wijze), wordt de daaronder staande afbeelding vervangen door een andere.</p>

Deze tekst is links buiten het browservenster neergezet, zodat deze onzichtbaar is. Omdat de gemiddelde gebruiker van de Tab-toets niet helderziend is, schiet dat niet echt op. Daarom is aan de <p> met de uitleg het attribuut tabindex="0" toegevoegd. Normaal genomen reageert een <p> niet op de Tab-toets, maar door deze toevoeging kan de <p> de focus krijgen. Bij #uitleg:focus wordt p#uitleg dan binnen het browservenster gezet, zodat de uitleg zichtbaar wordt.

Bij nogmaals indrukken van de Tab-toets verliest p#uitleg de focus weer, waardoor de uitleg weer buiten het browservenster wordt neergezet.

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".

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.

:hover wordt in dit voorbeeld niet gebruikt.

: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 gebeurt dat echter onbedoeld wel.

Als een aankruisvakje <input type="checkbox"> de focus heeft, komt het kadertje rondom het aankruisvakje te staan. Dat aankruisvakje wordt hier echter verborgen onder de bijbehorende <label>, en die krijgt bij focus geen kadertje. Logisch, want een <label> kan allerlei afmetingen hebben en op allerlei verschillende plaatsen worden neergezet.

Daarom krijgt hier, als het aankruisvakje de focus heeft, de bijbehorende <label> bij #toon:focus + label een brede blauwe rand. Voor zover de browser dat ondersteunt, gebeurt dat alleen als de Tab-toets wordt gebruikt, en niet bij aanraken of aanklikken van de <label>. Dat is ook niet nodig, want bij gebruik van een muis of bij een aanraking, weet je uiteraard, wat je hebt aangeraakt of aangeklikt.

: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 6 december 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 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 en Edge, in grotere en kleinere browservensters.

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

Tablets

iPad met iOS 12.5.5 (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 15.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 15.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 in een voorbeeld JavaScript is gebruikt, is ook getest of het werkt zonder JavaScript. Dat is alleen gedaan in de browsers, waarin in de instellingen JavaScript kan worden uitgeschakeld.

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

Schermlezers en dergelijke

Naast deze 'gewone' browsers is ook getest in Lynx, WebbIE, NVDA, TalkBack, VoiceOver 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.5 en 15.1, iPadOS 15.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 de overige problemen in één of meer specifieke browsers.

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

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

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

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

Zonder JavaScript

Probleem: zonder JavaScript lezen schermlezers altijd de alt-teksten van beide afbeeldingen voor.

Met behulp van JavaScript wordt de niet getoonde afbeelding met de daarbij horende alt-tekst voor schermlezers verborgen, door het toevoegen van aria-hidden="false", zoals beschreven bij aria-hidden.

Zonder JavaScript werkt dit niet en kan de niet-getoonde afbeelding niet worden verborgen. En de alt-tekst bij die afbeelding dus ook niet. Hierdoor worden beide alt-teksten voorgelezen, ongeacht welke afbeelding wordt getoond.

Niet het einde van de wereld, maar wel enigszins verwarrend.

Zonder css

Geen problemen.

Beide afbeeldingen en de gewone tekst worden volledig getoond. De tekst in de <label> is 'Toon vervangingToon origineel', want zonder css kan die tekst niet worden aangepast.</label>

Zonder afbeeldingen

Geen problemen.

Alt-teksten door elkaar heen en correct weergegeven

Uiteraard worden de afbeeldingen niet getoond. In plaats daarvan verschijnt de alt-tekst.

Omdat de vervangende afbeelding absoluut is gepositioneerd, komt deze over de originele afbeelding heen te staan. Hetzelfde geldt voor de alt-tekst. Dat is op de bovenste afbeelding te zien: beide alt-teksten staan door elkaar heen en zijn niet te lezen.

Door de vervangende afbeelding een witte achtergrond te geven, krijgt ook de alt-tekst die witte achtergrond en is de alt-tekst wel goed te lezen, zoals op de onderste afbeelding is te zien. Ongeveer zoals je vuil onder het vloerkleed veegt: het is er nog wel, maar je ziet het niet meer.

Zonder css én zonder afbeeldingen

Geen problemen.

Alle tekst wordt getoond, beide alt-teksten en als de tekst in de <label> is 'Toon vervangingToon origineel'. Kortom: er verdwijnt niets (behalve uiteraard de afbeeldingen), maar het is wel een zootje.

Gebruikers Tab-toets

Geen problemen.

Een aankruisvakje <input="checkbox"> kan gewoon met de Tab-toets worden bezocht. Omdat niet duidelijk is dat het om een aankruisvakje gaat, wordt bij gebruik van de Tab-toets eerst een kleine uitleg getoond, zoals beschreven bij tabindex="0".

Tekstbrowsers

Probleem: WebbIE toont de vervangende afbeelding niet.

Lynx levert geen problemen op: alle tekst en beide alt-teksten worden getoond, en als <label> 'Toon vervangingToon origineel'. Dit kun je natuurlijk problemen noemen, maar mensen die Lynx gebruiken kiezen nou eenmaal voor snelheid boven mooi en zullen dit soort dingen gewend zijn.

WebbIE toont ook alle tekst en als <label> ook weer 'Toon vervangingToon origineel'. De originele afbeelding wordt getoond.

Het aankruisvakje werkt gewoon, maar de vervangende afbeelding wordt toch niet getoond, hoewel dit wel zou moeten gebeuren. Hier is weinig aan te doen, want het is een probleem binnen WebbIE.

Schermlezers

Probleem: omdat beide afbeeldingen altijd aanwezig zijn, lezen schermlezers altijd beide alt-teksten voor.

Omdat beide afbeeldingen altijd aanwezig zijn, ook al zie je er maar één, zijn ook beide alt-teksten altijd aanwezig. Daardoor zullen schermlezers altijd beide alt-teksten voorlezen, ongeacht welke afbeelding wordt getoond.

Voor schermlezers wordt daarom de niet-getoonde afbeelding met de bijbehorende alt-tekst verborgen met behulp van aria-hidden en wat JavaScript, zodat niet beide alt-teksten worden voorgelezen.

Als JavaScript uitstaat kan dit uiteraard niet en worden wel beide alt-teksten voorgelezen, ongeacht welke afbeelding wordt getoond.

(De reden dat display: none; en dergelijke niet gebruikt kan worden, om één van beide afbeeldingen te verbergen, staat bij opacity: 0;.)

Zoomen en andere lettergrootte

Geen problemen.

Overige problemen

Probleem: de originele afbeelding is niet te kopiëren en dergelijke.

Als je de originele afbeelding probeert te kopiëren, op te slaan, te openen in een nieuwe tab, enzovoort, dan krijg je als resultaat de vervangende afbeelding. Logisch, want die staat over de originele afbeelding heen, ook al is die doorzichtig.

Door het uitzetten van css is de originele afbeelding wel te downloaden. Om die reden is dit ongeschikt als bescherming tegen kopiëren: veel te simpel te omzeilen.

Probleem: @supports not selector(:focus-visible) valideert niet.

Tot voor kort kon @supports alleen kijken of een eigenschap werd ondersteund, niet of een selector werd ondersteund. Officieel zit dit nog in een experimentele fase, maar het werkt in alle nieuwere browsers al prima. De css-validator van w3c rekent het echter nog fout.

Omdat het gewoon werkt, kan deze foutmelding worden genegeerd.

Wijzigingen

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

:

Nieuw opgenomen.

26 maart 2009:

12 april 2009:

Hierboven vermelde meta-tag weer weggehaald. Ook Internet Explorer 8 gebruikt nog steeds niet de standaard voor doorzichtigheid, maar 'n door Microsoft zelf bedachte methode. Nu werkt dit voorbeeld ook in de standaardmodus van Internet Explorer 8.

Overigens opmerkelijk dat Microsoft dit de 'super standards mode' noemt, terwijl zoiets simpels als opacity nog niet eens volgens de standaard werkt. En dan heb ik het maar helemaal niet over svg, css en html5, waarin deze 'super standards'-browser jaren en jaren achterloopt op álle concurrenten.

30 december 2009:

Afbeeldingen van lp's vervangen door eigengemaakte foto's van Koninginnedag 2009 in het Vondelpark in Amsterdam. Dit in verband met copyright. Ook op deze afbeeldingen bleek dat te zitten. Tja.

12 december 2011:

Tekst aangepast aan de inmiddels verschenen Internet Explorer 9. De code is niet gewijzigd.

7 december 2021:

Inhoud van de download en licenties

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

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

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

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

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

afbeelding-053-dl.html: de pagina met het voorbeeld.

afbeelding-053.pdf: deze uitleg (aangepast aan de inhoud van de download).

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

053-css-dl:

afbeelding-053-dl.css: stylesheet voor afbeelding-053-dl.html.

053-pics:

origineel.jpg: de originele afbeelding.

vervang.jpg: de vervangende afbeelding.

De foto's zijn gemaakt in het Vondelpark in Amsterdam op Koninginnedag 2009. Als je jezelf herkent en de originele foto wilt hebben, stuur dan even 'n mailtje naar info@css-voorbeelden.nl.

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 wordt de afbeelding nooit breder dan het venster.

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.

<p id="uitleg" tabindex="0">Gelijk op deze tekst volgt een aankruisvakje, vermomd als knop. Als je dit aanvinkt (met Spatiebalk of op soortgelijke wijze), wordt de daaronder staande afbeelding vervangen door een andere.</p>

De meeste mensen gebruiken een muis of een aanraking om te navigeren. Sommige mensen gebruiken echter de Tab-toets om links, knoppen, aankruisvakjes, en dergelijke langs te lopen. Ook allerlei andere dingen kunnen met het toetsenbord worden bediend.

Zo kan een aankruisvakje ook worden aan- en uitgevinkt met de Spatiebalk. Maar omdat het aankruisvakje hier is verborgen onder de bijbehorende <label>, is hier niet echt duidelijk, dat het hier om een aankruisvakje gaat. Daarom staat hier een korte uitleg.

Normaal genomen wordt een <p> genegeerd door de Tab-toets, maar door het toevoegen van tabindex="0" wordt deze toch herkend door de Tab-toets. De <p> staat normaal genomen onzichtbaar links buiten het browservenster. Pas als deze de focus krijgt, wordt de <p> met de uitleg binnen het venster gezet en daardoor zichtbaar.

Hoewel deze tekst buiten het browservenster staat, wordt deze toch gewoon voorgelezen door schermlezers. Ook deze weten daardoor, waarvoor de knop boven de afbeelding dient. (Hoewel een schermlezer een <input = "checkbox"> altijd al aankondigt als een aankruisvakje.)

<img id="origineel" aria-hidden="false" src="053-pics/levend-standbeeld.jpg" alt="Originele afbeelding: meisje van vijf jaar oud als levend standbeeld, kindervrijmarkt, koninginnedag 2009, Vondelpark, Amsterdam">

<img id="vervang" aria-hidden="true" src="053-pics/gitaristen.jpg" alt="Vervangende afbeelding: twee zingende gitaar spelende jongens, kindervrijmarkt, koninginnedag 2009, Vondelpark, Amsterdam">

De originele afbeelding en de vervangende afbeelding.

Schermlezers lezen bij een afbeelding de alt-tekst voor. Hier zouden beide alt-teksten worden voorgelezen, ongeacht welke afbeelding zichtbaar is. Beide afbeeldingen met bijbehorende alt-teksten zijn immers altijd aanwezig, ook al zie je er maar één.

Dat kan nogal verwarrend zijn.

Daarom wordt met behulp van aria-hidden steeds één van beide afbeeldingen met de bijbehorende alt-tekst voor schermlezers verborgen.

Bij openen van de pagina staat bij de originele afbeelding aria-hidden="false", waardoor deze wordt getoond. Bij de vervangende afbeelding staat juist aria-hidden="true", waardoor deze voor zschermlezers wordt verborgen.

Als input#toon wordt aangevinkt en de vervangende afbeelding wordt getoond, worden de waarden achter aria-hidden omgedraaid met behulp van wat JavaScript, waardoor dan juist de originele afbeelding met bijbehorende alt-tekst wordt verborgen voor schermlezers, en de vervangende wordt getoond.

Ook als input#toon weer wordt uitgevinkt, worden de waarden bij aria-hidden omgewisseld.

(De reden dat display: none; en dergelijke niet gebruikt kan worden, om één van beide afbeeldingen te verbergen, staat bij opacity: 0;.)

<script></script>

Binnen deze tag staat het JavaScript.

Dit script gebruikt onderdelen van de pagina: de twee afbeeldingen en het aankruisvakje. Om deze te kunnen gebruiken, moeten ze eerst door de browser worden gemaakt. Daarom staat het script helemaal onderaan de html: je weet dan zeker, dat alle elementen al zijn aangemaakt.

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

Als dit script op meerdere pagina's wordt gebruikt, kan het ook als een extern script worden gebruikt:

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

Ook in dat geval moet <script> helemaal onderaan de pagina worden gezet, gelijk boven </body>.

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 browsers

/* afbeelding-053-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.

font-size: 110%;

Iets groter dan standaard. 't Zal de leeftijd zijn, maar ik vind de standaardgrootte wat te klein.

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

margin: 0;

Slim om te doen vanwege verschillen tussen browsers.

#wrapper

Het element met id="wrapper". Een <div> waar de hele handel in is gezet, zodat het makkelijker is te plaatsen en dergelijke.

text-align: center;

Tekst horizontaal centreren. Eigenlijk zou je hier moeten zeggen 'inline-elementen horizontaal centreren', want het geldt ook voor de <label>, <img>'s, en andere inline-elementen.

margin-top: 40px;

Kleine afstand tot de bovenkant van het browservenster.

#uitleg

Het element met id="uitleg". Een <p> met een korte uitleg voor gebruikers van de Tab-toets.

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: 40vw;

Breedte.

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

border: black solid 1px;

Zwart randje.

padding: 5px;

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

position: absolute;

Om de <p> met de 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. Als zo'n voorouder er niet is, zoals hier het geval is, wordt gepositioneerd ten opzichte van het venster van de browser.

top: 0;

Bovenaan het browservenster neerzetten. Omdat een <p> standaard een marge aan boven- en onderkant heeft, komt deze toch iets van de bovenkant van het venster af te staan, wat mooier is.

left: -20000px;

Ver links buiten het browservenster parkeren. Pas als de <p> door gebruik van de Tab-toets de focus heeft gekregen, wordt deze bij #uitleg:focus binnen het venster gezet en daardoor zichtbaar.

z-index: 20;

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 <p> 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 de <p> gegeven, dus dat is geregeld.

#toon

Het element met id="toon". De <input> die bepaalt, welke afbeelding wordt getoond.

position: absolute; Aankruisvakje staat naast label

Een <input> en een <label> zijn beide inline-elementen. Normaal genomen worden ze dan ook achter elkaar op dezelfde regel neergezet, zoals op de afbeelding is te zien. Gecentreerd, want dat is bij #wrapper met text-align: center; opgegeven.

Door input#toon absoluut te positioneren, wordt de <input> genegeerd door de andere elementen. Hierdoor komt de bijbehorende <label> niet naast, maar over de <input> te staan. Wat precies de bedoeling is, want een aankruisvakje is hier foeilelijk.

Voor de werking van de <input> maakt het niets uit, want de <label> is in de html met for="toon" gekoppeld aan de <input>, waardoor het aankruisvakje ook wordt aan- of uitgevinkt door de <label> aan te raken.

#label-toon

Het element met id="label-toon". De bij input#toon horende <label>

background: white;

Witte achtergrond.

color: black;

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

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

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

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

display: inline-block;

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

width: 8em;

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.

border: black solid 1px;

Zwart randje.

border-radius: 5px;

Ronde hoeken. Omdat maar één maat is opgegeven, worden alle hoeken even rond.

padding: 10px;

Wat afstand tussen tekst in en buitenkant van de <label>.

position: relative;

Hier iets boven bij #toon is input#toon met position: absolute; absoluut gepositioneerd, zodat andere elementen de <input> negeren. Omdat #label-toon op dezelfde regel staat als input#toon, komt de <label> hierdoor over de <input> te staan. Precies de bedoeling, want de <input> is niet echt heel mooi en wordt dan verborgen onder de <label>.

Althans: dat is de bedoeling. Omdat #label-toon in de html volgt op input#toon, zou dat normaal genomen ook gebeuren, want latere elementen worden over eerdere heen gezet.

Alleen is input#toon absoluut gepositioneerd, en #label-toon heeft gewoon de standaard statische positie. En elementen die een andere positie dan statisch hebben, komen altijd boven elementen die niet zijn gepositioneerd te staan, ongeacht de volgorde in de html.

Door #label-toon een relatieve positie te geven, zijn de <input> en de <label> beide gepositioneerd, en komt de <label> weer boven de <input> te staan, want nu geldt de volgorde in de html weer gewoon. Waarmee de niet weg te meppen <input> eindelijk is verborgen.

Omdat voor top en dergelijke verder niets wordt opgegeven, heeft de relatieve positie verder geen enkele invloed op de <label>.

#label-toon span:last-child

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

#label-toon: het element met id="label-toon". De bij de <input> horende <label>.

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

:last-child: alleen de <span>'s die een laatste kind zijn. Binnen deze <span> zit de tekst 'Toon origineel'.

De hele selector in gewone mensentaal: alle <spans>'s binnen de label#label-toon, die een laatste kind zijn.

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

display: none;

Verbergen.

Pas als de <input> wordt aangevinkt en daardoor de vervangende afbeelding wordt getoond, wordt deze <span> bij #toon:checked + #label-toon span:last-child zichtbaar gemaakt.

#afbeeldingen

Het element met id="afbeeldingen". Een <div> waarin beide afbeeldingen zitten.

width: 400px;

Breedte. Dit is dezelfde breedte als de in de <div> zittende afbeeldingen hebben.

max-width: 90vw;

Hier gelijk boven is een breedte van 400 px opgegeven. Daardoor zou je in browservensters die smaller dan 400 px zijn horizontaal moeten scrollen om alles te zien. Daarom wordt hier een maximumbreedte opgegeven. Omdat voor de in deze <div> zittende afbeeldingen bij img een maximumbreedte van 100% wordt opgegeven, is dit ook gelijk de maximumbreedte van de afbeeldingen.

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

margin: 20px auto 0;

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

Aan de bovenkant 20 px afstand tot de erboven zittende witte knop, de <label>.

Aan de onderkant geen marge.

Links en rechts auto, wat hier hetzelfde betekent als evenveel. Hierdoor komt de <div> altijd horizontaal gecentreerd binnen z'n ouder div#wrapper te staan. div#wrapper is een blok-element en wordt daardoor normaal genomen automatisch even breed als z'n ouder <main>. Ook <main> is een blok-element en wordt daardoor normaal genomen even breed als z'n ouder <body>. Het wordt eentonig: ook <body> is weer een blok-element dat dus normaal genomen even breed wordt 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 div#afbeeldingen altijd horizontaal gecentreerd binnen het venster van de browser, ongeacht de breedte van het venster. En daarmee ook de in de <div> zittende afbeeldingen.

position: relative;

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

img

Alle afbeeldingen. Dat zijn er hier maar twee: de originele en de vervangende.

max-width: 100%;

Een maximumbreedte in procenten is normaal genomen ten opzichte van de ouder van het element. Dat is hier div#afbeeldingen, die bij #afbeeldingen met max‑width: 90vw; een maximumbreedte van 90% van het venster van de browser heeft gekregen. Hierdoor worden ook de afbeeldingen nooit breder dan 90% van het venster.

Omdat nergens een hoogte is opgegeven voor de afbeeldingen, wordt de hoogte automatisch op dezelfde schaal aangepast als de breedte, zodat je geen lachspiegel-effect krijgt.

#vervang

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.

img {max-width: 100%; float: left;}

Het element met id="vervang". De vervangende afbeelding.

background: white;

Witte achtergrond.

Als de afbeeldingen om wat voor reden dan ook niet worden getoond, komt de alt-tekst van de vervangende afbeelding door de alt-tekst van de originele afbeelding te staan, waardoor deze niet is te lezen. Een witte achtergrond voorkomt dat. Dit wordt uitgebreider beschreven bij Zonder afbeeldingen.

color: black;

Voorgrondkleur zwart. Dit is onder andere de kleur van de tekst, of hier eigenlijk: de alt-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.

opacity: 0;

Volledig doorzichtig maken.

Gelijk hieronder wordt met position: absolute; de vervangende afbeelding over de originele afbeelding heen gezet. Dit zorgt ervoor dat bij openen van de pagina toch de originele afbeelding wordt getoond.

Door de vervangende afbeelding op deze manier te verbergen, ontstaat een probleem voor schermlezers. Je ziet de vervangende afbeelding weliswaar niet, maar deze is wel gewoon aanwezig. En als de vervangende afbeelding bij #toon:checked ~ div #vervang zichtbaar wordt gemaakt en daardoor de originele afbeelding verbergt, is de originele afbeelding nog steeds gewoon aanwezig, ook al zie je die niet.

Beide afbeeldingen zijn dus altijd aanwezig, al zie je er maar één. Een schermlezer zal daardoor altijd beide alt-teksten voorlezen, ongeacht welke afbeelding zichtbaar is. Wat nogal verwarrend kan zijn. Dit wordt opgelost door met behulp van wat JavaScript en aria-hidden de niet-getoonde afbeelding met de bijbehorende alt-tekst te verbergen voor schermlezers.

Je zou dat verbergen voor schermlezers ook kunnen doen met display: none;, maar display is niet te combineren met de eigenschap transition. De vervangende afbeelding wordt dan niet vertraagd zichtbaar, maar in één keer, ongeacht wat je bij transition opgeeft.

visibility: none; is ook niet te gebruiken om één van de afbeeldingen te verbergen voor een schermlezer, want dat kent maar twee waarden: none en visible. En die kun je niet langzaam veranderen. Ook bij gebruik van visibility zou de vervangende afbeelding daardoor in één keer zichtbaar worden.

De vervangende afbeelding links buiten het browservenster parkeren of zoiets werkt ook niet, want schermlezers lezen gewoon voor, wat buiten het venster staat.

Omdat bij opacity als waarde een getal wordt gebruikt, kan dit wel geleidelijk aan van 0 naar 1 worden veranderd, waardoor de vervangende afbeeldingen geleidelijk aan minder doorzichtig wordt.

position: absolute;

Om de afbeelding 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. In dit geval is dat div#afbeeldingen, die bij #afbeeldingen een relatieve positie heeft gekregen.

top: 0; left: 0;

De afbeelding in de linkerbovenhoek van div#afbeeldingen zetten. Nu staat de vervangende afbeelding precies over de originele afbeelding heen.

#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: 40vw; border: black solid 1px; padding: 5px; position: absolute; top: 0; left: -20000px; z-index: 20;}

Het element met id="uitleg", maar alleen als dit de focus heeft. In deze <p> staat een korte uitleg voor gebruikers van de Tab-toets. Normaal genomen kan een <p> geen focus krijgen, maar door het toevoegen van het attribuut tabindex="0" aan de <p> kan dat hier toch.

left: 10px;

Bij #uitleg is de <p> met left: -20000px; links buiten het browservenster geparkeerd, waardoor deze - en de erin zittende tekst - verborgen worden. Nu wordt de <p> binnen het venster gezet, waardoor de tekst zichtbaar wordt.

Als de Tab-toets nogmaals wordt ingedrukt, verliest de <p> de focus weer en wordt de <p> weer links buiten het browservenster neergezet.

#toon:checked + #label-toon span:first-child

#toon:checked:

#toon: het element met id="toon". Het aankruisvakje <input id="toon" type="checkbox">.

:checked: maar alleen als dit is aangevinkt.

+: het element achter de + moet in de html direct volgen op het element voor de +. In dit geval gaat het om #label-toon gelijk volgend op input#toon .Beide elementen moeten ook nog dezelfde ouder hebben. Dat is hier het geval, ze hebben beide als ouder div#wrapper.

#label-toon: het element met id="label-toon". De <label> die bij input#toon hoort.

span:first-child:

span: alle <span>'s binnen die #label-toon.

:first-child: alleen de <span>'s die een eerste kind zijn. Binnen deze <span> zit de tekst 'Toon vervanging'.

De hele selector in gewone mensentaal: doe iets met de eerste <span> binnen #label-toon, maar alleen als de gelijk voor die <label> zittende input#toon is aangevinkt.

display: none;

De <span> en daarmee de erin zittende tekst 'Toon vervanging' verbergen.

Als het aankruisvakje is aangevinkt, wordt de vervangende afbeelding getoond. De tekst in de <label> moet dan veranderen van 'Toon vervanging' in 'Toon origineel'. Als onderdeel van deze verandering wordt dt tekst 'Toon vervanging' verborgen.

#toon:checked + #label-toon span:last-child

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

#label-toon span:last-child {display: none;}

Precies hetzelfde verhaal als bij de selector #toon:checked + #label-toon span:first-child gelijk hierboven, alleen geldt deze selector door het gebruik van span:last-child in plaats van span:first-child voor de laatste <span> in label#label-toon, de <span> met 'Toon origineel'.

display: inline;

Toon de <span> met de erin zittende tekst 'Toon origineel'.

Eerder is bij #label-toon span:last-child deze <span> met display: none; verborgen. Als het aankruisvakje is aangevinkt, wordt de vervangende afbeelding getoond. Daarom moet nu de <span> met 'Toon origineel' worden getoond.

#toon:checked ~ div #vervang

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.

img {max-width: 100%; float: left;}

#vervang {background: white; color: black; opacity: 0; position: absolute; top: 0; left: 0;}

#toon:checked:

#toon: het element met id="toon". Het aankruisvakje <input id="toon" type="checkbox">.

:checked: maar alleen als dit is aangevinkt.

~: het hierachter staande element moet ergens in de html staan, na het hiervoor staande element. Het hoeft er, anders dan bij de +, niet gelijk op te volgen, als het maar ergens na het eerste element in de html staat.

De enige voorwaarde is verder dat het voor en het na de ~ staande element dezelfde ouder hebben. Dat is hier het geval: input#toon voor de ~ en de <div> na de ~ (dat is div#afbeeldingen, de <div> met de afbeeldingen) hebben beide als ouder div#wrapper.

#vervang: de vervangende afbeelding img#vervang.

De hele selector in gewone mensentaal: doe iets met de vervangende afbeelding die in een <div> zit, die ergens in de html op input#toon volgt, maar alleen als de <input> is aangevinkt.

opacity: 1;

Maak de afbeelding zichtbaar.

De vervangende afbeelding is gewoon altijd aanwezig, maar is eerder bij #vervang met opacity: 0; onzichtbaar gemaakt. Daardoor zie je de eronder zittende originele afbeelding. Door de vervangende afbeelding zichtbaar te maken, wordt de eronder zittende originele afbeelding onzichtbaar.

transition: 2s linear;

Eerder is img#vervang bij #vervang met opacity: 0; onzichtbaar gemaakt.

Hier gelijk boven is met opacity: 1; de vervangende afbeelding img#vervang zichtbaar gemaakt, als input#toon is aangevinkt. Als je bij :hover, :focus, :checked, en dergelijke een andere waarde bij een eigenschap opgeeft, kun je er met transition bij veel eigenschappen voor zorgen dat de oude waarde niet in één keer in de nieuwe waarde wordt veranderd, maar geleidelijk, gedurende een op te geven tijdsduur.

Hier verandert opacity: 0; (volledig doorzichtig) niet in één keer in opacity: 1; (volledig ondoorzichtig), als input#toon wordt aangevinkt, maar geleidelijk. Als opacity van 0 (onzichtbaar) wordt veranderd naar 1 (zichtbaar), vindt die verandering geleidelijk aan plaats: geleidelijk van 0 naar 1.

Bij transition staat maar één tijd: 2s. Als er maar één tijd wordt opgegeven, bepaalt die over hoeveel tijd de verandering wordt uitgesmeerd. Dat is hier twee seconden. Een eventuele tweede tijd, die hier ontbreekt, geeft een eventueel uitstel aan het begin van de verandering aan.

linear is een sleutelwoord, dat het verloop van de verandering aangeeft. Als je hier niets opgeeft, wordt de standaardwaarde ease gebruikt: langzamer aan begin en einde van de verandering, sneller in het midden. linear zorgt ervoor dat de verandering overal even snel plaatsvindt.

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

Als je niet opgeeft, voor welke eigenschappen transition geldt, geldt het voor álle eigenschappen die bij :hover, :focus, :checked, en dergelijke worden veranderd. Als daar 'n eigenschap bij zit die op dit moment niet door transition kan worden vertraagd, verandert die gewoon in één keer. Maar als de specificatie of een van de browsermakers de geest krijgt en plotsklaps die eigenschap ook onder transition laat vallen, kan dat tot heel onverwachte resultaten leiden.

Stel dat je een bepaalde eigenschap heel traag wilt veranderen bij :hover en een andere eigenschap flitsend snel. Mogelijk wordt dat flitsend snel dan opeens ook heel traag, als transition in de toekomst die eigenschap ook gaat ondersteunen. Daarom moet je absoluut zeker weten dat ook in de toekomst geen problemen kunnen ontstaan. Bij twijfel: geef aan welke eigenschap(pen) transition mag aansturen.

Als alle eigenschappen mogen worden vertraagd door transition, is er geen enkel risico voor de toekomst. Dat is hier het geval: alleen opacity wordt veranderd. Verder verandert er niets. In dit geval mag dus alles vertraagd worden veranderd en is er geen risico voor de toekomst. Als dat niet zo zou zijn, moet je opgeven, voor welke eigenschappen transition geldt.

Je kunt transition beperken tot bepaalde eigenschappen door die eigenschappen te noemen, gevolgd door tijdsduur en eventueel vertraging:

transition: width 0.5s;

Nu geldt transition alleen voor width, ongeacht wat er in de toekomst mogelijk nog zou kunnen veranderen. Maar hier is dat dus niet nodig, omdat alleen opacity wordt veranderd.

Als de originele afbeelding verandert in de vervangende afbeelding, gaat dat geleidelijk aan: het duurt 2 seconden. Maar als de vervangende afbeelding weer terug wordt veranderd naar de originele afbeelding, gaat dat zonder vertraging in één keer.

De waarden hier bij transition gelden alleen, als input#toon wordt aangevinkt, alleen bij #toon:checked, als de originele afbeelding verandert in de vervangende afbeelding.

Voor het weer terug veranderen van de vervangende afbeelding naar de originele afbeelding kun je ook een vertraging opgeven, maar dat moet je dan bij #vervang doen. Als je daar transition: 20s ease-in; op zou geven, duurt het terug veranderen van de vervangende afbeelding naar de originele 20 seconden, en het verloop is daar ease-in: langzaam aan het begin, maar daarna overal hetzelfde tempo.

Hier is bij #vervang geen waarde opgegeven. Daarom wordt bij het terug veranderen de standaardwaarde van transition gebruikt: 0 seconden. Direct. Als je bij een eigenschap geen waarde opgeeft en de waarde wordt ook niet geërfd van 'n voorouder, wordt meestal de standaardwaarde gebruikt, wat bij transition dus 0 seconde is.

Maar bij transition is hier een uitzondering op: als je alleen bij #vervang een waarde voor transition opgeeft, wordt die waarde ook bij #vervang:hover, #vervang:focus, #vervang:checked, en dergelijke gebruikt. Als je precies dezelfde regel transition: 2s linear; niet hier bij #toon:checked ~ div #vervang zou neerzetten, maar bij #vervang, zou de verandering in beide richtingen 2 seconde duren en lineair zijn.

#toon:focus + 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.

#label-toon {background: white; color: black; display: inline-block; width: 8em; border: black solid 1px; border-radius: 5px; padding: 10px; position: relative;}

#toon:focus: het element met id="toon", maar alleen als dit de focus heeft.

input#toon krijgt de focus, als de <input> wordt aangeraakt of -geklikt, of als er met de Tab-toets naartoe wordt gegaan.

+: het element achter de + moet in de html direct volgen op het element voor de +. In dit geval gaat het om #label-toon gelijk volgend op input#toon. Beide elementen moeten ook nog dezelfde ouder hebben. Dat is hier het geval, ze hebben beide als ouder div#wrapper.

label: de in de html gelijk op input#toon volgende label#label-toon.

De hele selector in gewone mensentaal: doe iets met de <label> die op input#toon volgt, maar alleen als die <input> de focus heeft.

outline: blue solid 5px;

Blauwe outline van 5 px breed.

Sommige mensen gebruiken, om wat voor reden dan ook, de Tab-toets om links, tekstvelden, knoppen, en dergelijke langs te gaan. De browser geeft dan standaard met een kadertje aan, waar ze zijn, welk element de focus heeft. Daarmee weet de bezoeker, welke link bijvoorbeeld wordt gevolgd als op Enter wordt gedrukt.

outline bij focus op input en op label

Bij een <input type="checkbox">, zoals input#toon hier is, wordt dat kadertje om het aankruisvakje gezet. Op de linkerafbeelding is het kadertje van Chrome te zien rond het aankruisvakje linksboven. Maar dat aankruisvakje is op de afbeelding alleen te zien, omdat de erboven zittende <label> even wat doorzichtig is gemaakt. In werkelijkheid is het vanwege aangeboren lelijkheid onzichtbaar gemaakt: je ziet alleen de erboven zittende witte <label>. En daarmee is ook het kadertje rondom het aankruisvakje onzichtbaar.

Daarom wordt hier aan de bij de <input> horende <label> een blauwe outline gegeven, zoals op de rechterafbeelding is te zien.

Dat kadertje is eigenlijk alleen nodig voor gebruikers van de Tab-toets, want als je de <label> aanklikt of aanraakt, weet je wat je hebt aangeklikt of aangeraakt. Als je door een ietwat uitbundige levensstijl niet meer weet wat je aanraakt, valt te vrezen dat een kadertje ook niet veel meer zal helpen.

Als je het eigenlijke aankruisvakje vanwege ongeneeslijke lelijkheid verstopt, moet je dat eigenlijk niet vervangen door een ongeveer even lelijke outline bij de <label>. Bij gebruik van de Tab-toets is die outline zinvol, maar bij een aanraking of een klik niet. Daarom wordt gelijk hieronder in browsers die dat ondersteunen, de outline bij gebruik van de Tab-toets wel getoond, maar niet bij een aanraking of klik.

css voor browsers die :focus-visible ondersteunen

@supports selector(:focus-visible)

De css die hier tot nu toe staat, geldt voor alle browsers.

De css die binnen deze 'feature query' staat, geldt alleen voor browsers die :focus-visible ondersteunen. (Er bestaat geen goede Nederlandse vertaling voor 'feature query'. Het is een vraag of een bepaalde eigenschap en waarde, of een bepaalde selector, worden ondersteund: iets als 'eigenschap vraag'.)

@supports: hierachter komt tussen haakjes de te onderzoeken selector (of de te onderzoeken eigenschap met een bepaalde waarde) te staan.

selector(): als je, zoals hier gebeurt, niet op een eigenschap met waarde, maar op een selector wilt testen, gebruik je het sleutelwoord selector(). Tussen de haakjes komt dan de selector te staan, waarvan je de ondersteuning wilt testen.

(:focus-visible): de selector, waarop je wilt testen. Omdat :focus-visible altijd zonder verdere toevoeging wordt gebruikt, is dit voldoende. Maar zou je bijvoorbeeld op ondersteuning van :is() willen testen, dan zou hier iets als (:is(a, b)) moeten staan.

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

@supports selector(:focus-visible) { body {color: silver;} (...) rest van de css voor deze @media-regel (...) footer {color: gold;} }

Voor de eerste css binnen deze @supports-regel staat dus een extra {, en aan het eind staat een extra }.

Je kunt ook als voorwaarde opgeven dat meerdere eigenschappen en/of waarden en/of selectors moeten worden ondersteund, of dat een eigenschap of selector juist niet mag worden ondersteund, maar dat gebeurt hier allemaal niet. Hier is de enige voorwaarde dat :focus-visible wordt ondersteund.

Alleen browsers die :focus-visible ondersteunen, voeren de css binnen deze feature query uit. Oudere browsers kennen :focus-visible niet en voeren de css binnen deze feature query daarom niet uit. Als een browser zo oud is dat het hele @supports niet wordt ondersteund, negeert ook die browser alle css binnen de feature query.

Het met @supports testen op eigenschappen wordt al langer ondersteund, maar het op selectors testen met behulp van selector() is relatief nieuw. Niet alle browsers die @supports ondersteunen, ondersteunen ook al selector(). Ook als dat het geval is, werkt het weer hetzelfde: de css binnen de feature query wordt genegeerd. (Standaard negeren browsers css die ze niet kennen.)

Omdat selector() zo nieuw is, valideert dit ook nog niet in de css-validator van w3c. Maar omdat nieuwere browsers het allemaal wel al ondersteunen, is dat geen enkel probleem en kan de foutmelding van de validator gewoon worden genegeerd.

#toon:focus + label

Deze selector werkt alleen in browsers die :focus-visible ondersteunen. Voor andere browsers is de uitleg hieronder niet van belang.

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

#label-toon {background: white; color: black; display: inline-block; width: 8em; border: black solid 1px; border-radius: 5px; padding: 10px; position: relative;}

#toon:focus + label {outline: blue solid 5px;}

#toon:focus: het element met id="toon", maar alleen als dit de focus heeft.

input#toon krijgt de focus, als de <input> wordt aangeraakt of -geklikt, of als er met de Tab-toets naartoe wordt gegaan.

+: het element achter de + moet in de html direct volgen op het element voor de +. In dit geval gaat het om #label-toon gelijk volgend op input#toon. Beide elementen moeten ook nog dezelfde ouder hebben. Dat is hier het geval, ze hebben beide als ouder div#wrapper.

label: de in de html gelijk op input#toon volgende label#label-toon.

De hele selector in gewone mensentaal: doe iets met de <label> die op input#toon volgt, maar alleen als die <input> de focus heeft.

outline: none;

Eerder is bij #toon:focus + label opgegeven dat de <label> een blauwe outline moet krijgen, als input#toon de focus heeft. Dat is echter foeilelijk en eigenlijk alleen nodig voor gebruikers van de Tab-toets, niet bij gebruik van een muis of bij een aanraking. Daarom wordt die blauwe outline bij :focus hier weer verwijderd.

Omdat dit alleen gebeurt bij browsers die :focus-visible ondersteunen, houden andere browsers die outline gewoon. Browsers die :focus-visible ondersteunen krijgen gelijk hieronder dezelfde outline weer terug, maar dan alleen voor gebruikers van de Tab-toets.

Op deze manier houdt elke browser, ouder of nieuwer, in ieder geval een kadertje bij gebruik van de Tab-toets, als een element de focus heeft.

#toon:focus-visible + label

Deze selector werkt alleen in browsers die :focus-visible ondersteunen. Voor andere browsers is de uitleg hieronder niet van belang.

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

#label-toon {background: white; color: black; display: inline-block; width: 8em; border: black solid 1px; border-radius: 5px; padding: 10px; position: relative;}

#toon:focus + label {outline: blue solid 5px;}

#toon:focus + label {outline: none;}

#toon:focus-visible: het element met id="toon", maar alleen als dit de focus heeft, en alleen als die focus door gebruik van de Tab-toets is gekregen. Een focus die door een klik of aanraking is verkregen, wordt door de browser genegeerd.

+: het element achter de + moet in de html direct volgen op het element voor de +. In dit geval gaat het om #label-toon gelijk volgend op input#toon .Beide elementen moeten ook nog dezelfde ouder hebben. Dat is hier het geval, ze hebben beide als ouder div#wrapper.

label: de in de html gelijk op input#toon volgende label#label-toon.

De hele selector in gewone mensentaal: doe iets met de <label> die op input#toon volgt, maar alleen als die <input> de focus heeft, en alleen als die focus door gebruik van de Tab-toets is verkregen.

outline: blue solid 5px;

Blauwe outline van 5 px breed.

Deze outline verschijnt, door het gebruik van :focus-visible in plaats van :focus, alleen als input#toon de focus krijgt door gebruik van de Tab-toets, en niet bij een klik of een aanraking.

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.

Met dit script wordt voorkomen dat schermlezers altijd de alt-teksten bij beide afbeeldingen voorlezen. Door het aanpassen van de waarde bij aria-hidden wordt alleen de alt-tekst van de getoonde afbeelding voorgelezen.

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

;: met de puntkomma wordt in JavaScript een regel afgesloten. Het is te vergelijken met een gewone punt in een tekst.

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

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

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

In dit script is dat ook zo. Er zit een functie in die reageert op het aan- en uitvinken van input#toon. Om die functie goed te laten werken, moet de computer eerst wat voorbereidend werk verrichten. Daarvoor moet het script worden gelezen. Maar dat hele script zit in een functie, en die functie doet dus pas wat, als die wordt aangeroepen.

Om te zorgen dat de buitenste functie, die waar alle code in zit, toch vanzelf wordt uitgevoerd, zet je er een ( voor. En helemaal aan het eind, achter de afsluitende }, zet je de tegenhanger ). Om te zorgen dat de functie echt vanzelf wordt uitgevoerd, moeten hierachter nog twee haakjes () worden gezet. Nu wordt de functie automatisch uitgevoerd, zonder dat deze hoeft te worden aangeroepen. En kan de code in het script worden gelezen, waardoor het benodigde voorbereidende werk kan worden uitgevoerd.

(Je kunt de code ook in een niet alles omvattende functie zetten. Dan wordt de code gelijk uitgevoerd. Maar dat brengt belangrijke risico's met zich mee. In dit script worden namen voor variabelen gebruikt als 'toon' en 'origineel'. 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.

let toon = document.querySelector("#toon"),

Als je in de html een andere id dan 'toon' hebt gebruikt bij input#toon, moet je die id ook in bovenstaande regel aanpassen. Dit is de enige plaats in het script, waar je dit moet aanpassen.

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 let. Dit sleutelwoord let 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 let voor alle delen geldt, dat hier drie variabelen worden aangemaakt: in elke regel één.

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

let: het begin van het eerste deel van de eerste regel. Met het sleutelwoord let 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 let 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 'toon', 'origineel' en 'vervang' Elke variabele staat óf helemaal alleen op een eigen regel, óf aan het begin van een regel, gelijk gevolgd door een isgelijkteken.

In 'toon', 'origineel' en 'vervang' wordt dus iets opgeborgen. Omdat de variabele een naam heeft, kan de rest van het script de variabele aanroepen bij deze naam. Net zoals je iemand die 'Marie' heet kunt aanroepen met 'Marie', ongeacht of Marie aardig, onaardig, muzikaal of arrogant is, ongeacht de 'inhoud' van Marie.

Wat er precies in die variabelen wordt opgeslagen, kun je hier opgeven of elders in het script. Als het om iets simpels gaat, wordt het hier opgegeven. Als het om iets meer ingewikkelds gaat, of als nog niet bekend is, wat moet worden opgeslagen, gebeurt het later.

(Heel vaak zou je in plaats van iets opbergen in deze variabelen iets elke keer dat het nodig is opnieuw kunnen opzoeken. Zo wordt hieronder input#toon opgezocht met document.querySelector("toon") en opgeslagen in de variabele 'toon'. Je zou ook elke keer input#toon 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.

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

Als je in de html een andere id dan 'toon' hebt gebruikt bij input#toon, moet je die id ook in bovenstaande regel aanpassen. Dit is de enige plaats in het script, waar je dit moet aanpassen.

toon: dit is de variabele, waarin iets moet worden opgeborgen.

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

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

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

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

Het script gebruikt het object in variabele toon, de input#toon, om te kijken of input#toon wordt aan- of uitgevinkt. Aan de hand daarvan kan dan met behulp van het veranderen van de waarde bij aria-hidden worden gezorgd, dat schermlezers alleen de alt-tekst van de getoonde afbeelding voorlezen.

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

De hele regel in gewone taal: stop input#toon in de vorm van een object in variabele 'toon'.

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

Als je in de html een andere id dan 'origineel' hebt gebruikt bij img#origineel, moet je die id ook in bovenstaande regel aanpassen. Dit is de enige plaats in het script, waar je dit moet aanpassen.

Dit werkt precies hetzelfde als bij toon = document.querySelector("#toon"), hier gelijk boven, alleen wordt hier in variabele 'origineel' de originele afbeelding img#origineel opgeslagen.

vervang = document.querySelector("#vervang");

Als je in de html een andere id dan 'vervang' hebt gebruikt bij img#vervang, moet je die id ook in bovenstaande regel aanpassen. Dit is de enige plaats in het script, waar je dit moet aanpassen.

Het enige verschil is verder dat hier aan het einde van de regel geen , staat, maar een ;. Dit is de laatste variabele die wordt aangemaakt, en daarom eindigt de regel hier echt. In gewone tekst zou je hier een punt gebruiken om het eind van de regel aan te geven, in JavaScript gebruikt je daar een puntkomma voor.

toon.addEventListener("change", wijzigAriaHidden);

toon: in de variabele toon is bij toon = document.querySelector("#toon"), input#toon 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 toon, de variabele waarin input#toon 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'.

"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', 'verandering'. Er wordt geluisterd naar een verandering. Omdat het in toon opgeslagen element een <input type="checkbox"> is, gaat het om het aan- of uitvinken van het aankruisvakje. Dat is hier de 'verandering'.

wijzigAriaHidden: 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 wijzigAriaHidden (e) { en zorgt dat bij het aan- of uitvinken van input#toon de waarde van aria-hidden wordt aangepast, zodat schermlezers alleen de alt-tekst van de getoonde afbeelding voorlezen.

(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 in gewone taal: als toon#input wordt uit- of aangevinkt, voer dan de functie 'wijzigAriaHidden' uit.

function wijzigAriaHidden (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 input#toon wordt aan- of uitgevinkt. Het luisteren daarnaar wordt geregeld bij toon.addEventListener("change", wijzigAriaHidden);.

De code in deze functie regelt, wat er gebeurt, als input#toon wordt aan- of uitgevinkt.

Als input#toon wordt aangevinkt (dan wordt de vervangende afbeelding getoond):

- voeg aan originele afbeelding img#origineel het attribuut aria-hidden="true" toe, waardoor de originele afbeelding met bijbehorende alt-tekst wordt verborgen voor schermlezers. Een schermlezer leest de alt-tekst nu niet voor.

- voeg aan vervangende afbeelding img#vervang het attribuut aria-hidden="false" toe, waardoor de vervangende afbeelding met bijbehorende alt-tekst zichtbaar wordt voor schermlezers. Een schermlezer kan de alt-tekst nu voorlezen.

Als input#toon wordt uitgevinkt (dan wordt de originele afbeelding getoond):

- voeg aan originele afbeelding img#origineel het attribuut aria-hidden="false" toe, waardoor de originele afbeelding met bijbehorende alt-tekst zichtbaar wordt voor schermlezers. Een schermlezer kan de alt-tekst nu voorlezen.

- voeg aan vervangende afbeelding img#vervang het attribuut aria-hidden="true" toe, waardoor de vervangende afbeelding met bijbehorende alt-tekst wordt verborgen voor schermlezers. Een schermlezer leest de alt-tekst nu niet voor.

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

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

Het is bij namen in JavaScript gebruikelijk om nieuwe woorden met een hoofdletter te beginnen, omdat spaties, koppeltekens, en dergelijke niet gebruikt mogen worden in een naam ('camelCase'). In css zou je hier bijvoorbeeld wijzig-aria-hidden kunnen gebruiken in plaats van wijzigAriaHidden.

(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 aan- of uitvinken van input#toon. In e zit bijvoorbeeld, welke toets is ingedrukt, of de toets blíjft ingedrukt (repeteert), en nog van alles meer.

Los hiervan voegt JavaScript aan e allerlei methodes toe: functies binnen het object, waarmee je allerlei dingen kunt doen.

Heel formeel is e eigenlijk geen object, maar is e een parameter, iets dat wordt doorgegeven aan de functie, zodat het binnen die functie gebruikt kan worden. e is de naam van het object, en de inhoud van e is een object. Om het object iets te kunnen vragen, of het iets te laten doen, moet het beestje 'n naam hebben: e.

De naam e voor het object is niet verplicht, maar 'n soort afspraak, zodat code makkelijker door anderen is te begrijpen. Maar als je het object niet e, maar 'hetIsStervenskoud' wilt noemen, is daar technisch geen enkel bezwaar tegen. Het is dan wel verstandig een cursus zelfverdediging te volgen, voor het geval iemand anders ooit je code moet bekijken.

(e is een afkorting van 'event', gebeurtenis. De functie reageert op een gebeurtenis, in dit geval het aan- of uitvinken van input#toon. In e zit het object dat bij díé gebeurtenis hoort. Bij bijvoorbeeld een muisklik krijg je een ander object met andere informatie dan bij het indrukken van een toets.)

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

if (e.target.checked) {

Deze regel is onderdeel van function wijzigAriaHidden (e) {

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 wijzigAriaHidden (e) {, de functie waar deze regel een onderdeel van is, is dit object aan de functie doorgegeven. Hierdoor kan de code in de functie de informatie uit dit object gebruiken.

target: dit is zo'n stukje informatie uit het hierboven genoemde object: hierin zit het element, wat de functie heeft aangeroepen. Hier is dat input#toon, want bij toon.addEventListener("change", wijzigAriaHidden); is gezorgd dat bij aan- of uitvinken van input#toon deze functie wordt aangeroepen.

Feitelijk is target ook weer een object. Hierdoor zit er allerlei informatie in over input#toon (en zou je ook van alles met input#toon kunnen doen, maar dat gebeurt hier niet.)

checked: dit is zo'n stukje informatie uit target. Als input#toon is aangevinkt, heeft checked de waarde 'true'. Als input#toon is uitgevinkt, heeft checked de waarde 'false'.

{: de code die wordt uitgevoerd, als aan deze if-voorwaarde wordt voldaan, wordt tussen accolades gezet. Het script weet dan, wat bij deze if hoort. Aan het eind van de code bij deze if staat de afsluitende }.

De hele regel in gewone taal: als input#toon is aangevinkt, voer dan de code tussen de {} achter de if uit.

origineel.setAttribute("aria-hidden", true);

Deze regel is onderdeel van function wijzigAriaHidden (e) {

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

- input#toon moet zijn aangevinkt.

origineel: in de variabele origineel is bij origineel = document.querySelector("#origineel"), img#origineel 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 origineel staat, werkt deze methode op het in de variabele origineel opgeslagen object, op het daarin zittende element.

("aria-hidden", "true"): voor de komma staat tussen aanhalingstekens de naam van het attribuut dat wordt toegevoegd, achter de komma staat tussen aanhalingstekens de waarde van het attribuut.

In dit geval levert dat aria-hidden="true" op. Dit attribuut wordt aan img#origineel toegevoegd:

<img id="origineel" aria-hidden="true" href="..." alt="...">

Hierdoor wordt dit hele element, de originele afbeelding met alles erop en eraan, verborgen voor schermlezers, zodat deze de alt-tekst niet voorlezen.

Als je het aan img#origineel toegevoegde attribuut wilt bekijken, moet je niet in de gewone code, maar in de Gegenereerde code kijken.

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

De hele regel in gewone taal: voeg aan img#origineel het attribuut aria-hidden="true" toe.

(Als het attribuut aria-hidden al aanwezig is, wordt dit gewoon vervangen door aria-hidden="true".)

vervang.setAttribute("aria-hidden", false);

Deze regel is onderdeel van function wijzigAriaHidden (e) {

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

- input#toon moet zijn aangevinkt.

Deze regel is vrijwel hetzelfde als gelijk hierboven bij origineel.setAttribute("aria-hidden", "true");. De beschrijving is daar te vinden.

Alleen is in vervang de vervangende afbeelding img#vervang opgeslagen, dus hier wordt de handeling uitgevoerd bij img#vervang. En omdat, als input#toon is aangevinkt, de vervangende afbeelding wordt getoond, krijgt aria-hidden hier juist de waarde 'false'. Hierdoor kunnen schermlezers de alt-tekst bij de vervangende afbeelding voorlezen.

} else {

Deze regel is onderdeel van function wijzigAriaHidden (e) {

Deze else hoort bij if (e.target.checked) {: als input#toon is aangevinkt. De code tussen de {} achter deze else wordt daarom alleen uitgevoerd, als input#toon niet is aangevinkt.

}: dit is de afsluitende accolade van de code die bij de hierboven genoemde if hoort.

else: als de voorwaarde bij die if niet waar is, voer dan de code tussen de {} bij deze else uit. Er staat hier verder geen voorwaarde of zo: deze code wordt gewoon altijd uitgevoerd, als de voorwaarde bij de if niet waar is.

{: de code die wordt uitgevoerd, als aan deze else wordt voldaan, wordt tussen accolades gezet. Het script weet dan, wat bij deze else hoort. Aan het eind van de code bij deze else staat de afsluitende }.

De hele regel in gewone taal: voer de code tussen de {} bij de else uit, als input#toon niet is aangevinkt.

origineel.setAttribute("aria-hidden", false);

Deze regel is onderdeel van function wijzigAriaHidden (e) {

Deze regel hoort bij de iets hierboven staande else en wordt alleen uitgevoerd, als input#toon wordt uitgevinkt.

Deze regel is vrijwel hetzelfde als iets hierboven bij origineel.setAttribute("aria-hidden", "true");. De beschrijving is daar te vinden.

Alleen wordt, omdat input#toon wordt uitgevinkt, de originele afbeelding weer getoond. Daarom krijgt aria-hidden hier juist de waarde 'false'. Hierdoor kunnen schermlezers de alt-tekst bij de originele afbeelding voorlezen.

vervang.setAttribute("aria-hidden", true);

Deze regel is onderdeel van function wijzigAriaHidden (e) {

Deze regel hoort bij de iets hierboven staande else en wordt alleen uitgevoerd, als input#toon wordt uitgevinkt.

Deze regel is vrijwel hetzelfde als iets hierboven bij vervang.setAttribute("aria-hidden", "false");. De beschrijving is daar te vinden.

Alleen wordt, omdat input#toon wordt uitgevinkt, de originele afbeelding weer getoond. Daarom krijgt aria-hidden hier juist de waarde 'true'. Hierdoor wordt voor schermlezers de alt-tekst bij de vervangende afbeelding verborgen .