Skip links en inhoudsopgave

Met behulp van counter(), calc(), en derge­lijke allerlei statis­tische gegevens over aankruis­vakjes weergeven - uitleg

Laatst aangepast: .

Afbeelding 1: 35 keuzevakjes met getallen, waaronder statistische gegevens staan

Korte omschrijving

Met behulp van counter(), calc() en css-variabelen aantal gekozen even, oneven en priemgetallen, totaal, en dergelijke tonen.

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, downloadt dan de hele handel (ga terug naar het voorbeeld en kies daar voor downloaden). In de download zit 'n voorbeeld dat wel naadloos aansluit op de uitleg in de download.

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

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

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

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

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

Alle code is geschreven in een afwijkende lettersoort en -kleur. De code die te maken heeft met de basis van dit voorbeeld (essentiële code), is in de hele uitleg blauw gekleurd. 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.

Iets gevonden waar je wat aan hebt? Mooi. Als je je waardering wilt uiten, maak dan een donatie over aan War Child Nederland, een organisatie die kinderen uit oorlogsgebieden helpt hun trauma's te verwerken. Of - nog beter - wordt donateur:
Naar site van War Child Nederland

Achterliggend idee

Met behulp van counter() kunnen in css automatische tellers worden aangemaakt, die met behulp van content op het scherm kunnen worden gezet.

Met de calc()-functie kunnen in css simpele berekeningen worden uitgevoerd.

Door gebruik te maken van css-variabelen, kan de waarde van een attribuut worden gewijzigd, afhankelijk van of bijvoorbeeld een aankruisvakje wel of niet is aangevinkt.

Door deze drie eigenschappen van css op verschillende manier te gebruiken (en uiteraard nog andere eigenschappen), kun je op het scherm dingen zetten als een gekleurde voortgangsbalk, een waarschuwing als meer dan tien aankruisvakjes zijn aangevinkt, het aantal aangevinkte getallen, het aantal aangevinkte oneven en even getallen, het aantal aangevinkte priemgetallen, hoeveel je er nog moet aanvinken, hoeveel getallen er in elke kolom en regel zijn aangevinkt, en het totaal van de aangevinkte getallen.

De verschillende tellers werken niet op dezelfde manier, daarom worden ze hier per teller behandeld.

Om te beginnen worden bij #wrapper met counter-reset zeventien counters aangemaakt. Hiermee kunnen zeventien verschillende getallen op het scherm worden gezet. (Totaal krijgt geen counter, dat wordt op een andere manier geregeld.) Deze counters kunnen afzonderlijk van elkaar worden verhoogd en verlaagd, zodat ze elk een eigen gegeven kunnen bijhouden. ('Afzonderlijk' klopt niet helemaal, maar dat wordt verderop duidelijk.)

Alle counters beginnen met 0, de standaardinstelling. Alleen counter nog-te-doen begint met 10, want die telt omlaag.

De voortgangsbalk en de foutmelding

De voortgangsbalk is een gewone <div> met een achtergrondkleur. Als er nog geen getal is aangevinkt, is de achtergrondkleur egaal oranje. Als er één getal is aangevinkt, wordt 10% vanaf links groen. Als een tweede getal is aangevinkt, wordt 20% vanaf links groen, enzovoort. Als er tien getallen zijn aangevinkt, is de achtergrond volledig groen.

Als twee tot negen getallen zijn aangevinkt, bestaat de achtergrondkleur uit een gradiënt, met de grens tussen groen en oranje bij één aangevinkt getal op 10%, bij 2 getallen op 20%, enzovoort.

Het juiste percentage wordt op een bijzonder primitieve manier gevonden: de aangevinkte <inputs>'s tellen. Voor bijvoorbeeld dertig procent wordt dat:

input:checked ~ input:checked ~ input:checked {}

Afbeelding 2: de voortgangsbalk bij drie aangevinkte getallen

Om 100% van de achtergrond groen te maken, staat in de selector tien keer input:checked. Omdat in de selector ~ wordt gebruikt, maakt de volgorde van de <input>'s in de html niet uit. Alleen het aantal is van belang.

Dit werkt nog wel enigszins leuk, als je maximaal tien aankruisvakjes aan mag vinken. Maar bij duizend vakjes gaat het toch wat problematisch worden: duizend keer input:checked achter elkaar... Met JavaScript is dat veel simpeler op te lossen, omdat je daarmee daadwerkelijk het aantal aangevinkte <input>'s kunt tellen en dat aantal op het scherm kunt zetten.

Als je meer dan tien aankruisvakjes aanvinkt, verschijnt een foutmelding. Dat werkt op dezelfde manier: elf (of meer) keer input:checked achter elkaar zorgt voor het verschijnen van een <p> met de waarschuwing. Zodra er weer tien of minder aankruisvakjes zijn aangevinkt, verdwijnt de waarschuwing weer.

(Hoe deze selectors precies werken, is te vinden bij input:checked ~ #tekst #kleur en de daaropvolgende selectors.)

Aantal aangevinkte getallen

Dit is een simpele: elke keer als een aankruisvakje wordt aangevinkt (bij elke input:checked), wordt counter aangevinkt met 1 verhoogd.

Aantal oneven getallen

Ook deze counter is niet al te ingewikkeld. Met behulp van input:nth-of-type(odd):checked worden alleen de aangevinkte oneven (odd) <input>'s gebruikt om counter oneven te verhogen.

Aantal even getallen

De tegenhanger van die gelijk hierboven. Met behulp van input:nth-of-type(even):checked worden alleen de aangevinkte even <input>'s gebruikt om counter even te verhogen.

Aantal priemgetallen

Een priemgetal is een getal dat alleen door zichzelf en door 1 deelbaar is. In de reeks getallen 1 tot en met 35 zitten elf priemgetallen: 2, 3, 5, 7, 11, 13, 17, 19, 23, 29 en 31.

In theorie zou je misschien een formule kunnen bedenken om alleen de priemgetallen eruit te halen. Maar dat wordt heel ingewikkeld, als het al kan. En bij iets meer getallen wordt het helemaal hopeloos. (Had ik al gezegd dat JavaScript voor dit soort dingen veel geschikter is, omdat je er wel echt mee kunt rekenen?)

Je zou ook counter priem kunnen verhogen als de tweede of de derde of de vijfde, enzovoort tot en met de eenendertigste is aangevinkt:

input:nth-of-type(2):checked, input:nth-of-type(3), (...) input:nth-of-type(31) {}

In dit geval is voor een andere oplossing gekozen, het gebruik van het data-attribuut:

<input id="i-7" data-priem type="checkbox">

Bovenstaande regel hoort bij het getal 7, een priemgetal. Aan de <input> is het attribuut data-priem toegevoegd. Dat is een eigengemaakt attribuut. Dat kan, als de naam maar met 'data-' begint. Je kunt eventueel een waarde in het attribuut opslaan:

<input id="i-7" data-priem="ja" type="checkbox">

Nu heeft data-priem de waarde 'ja'. In de html zijn de <input>'s bij alle elf de priemgetallen voorzien van dit attribuut.

Met behulp van de selector input:nth-of-type(odd)[data-priem]:checked kun je selecteren op <input>'s die zijn aangevinkt, maar alleen als ze het attribuut data-priem hebben. Alleen in dat geval wordt counter priem verhoogd.

Nog te doen

Counter nog-te-doen houdt bij, hoeveel getallen nog moeten worden aangevinkt. Deze counter is als enige niet met 0, maar met 10 begonnen. Elke keer als een <input> is aangevinkt, wordt deze counter een verlaagd.

Standaard wordt een counter met 1 verhoogd, maar door achter de counter een waarde te zetten, wordt die waarde gebruikt:

input:nth-of-type(odd):checked {counter-increment: aangevinkt nog-te-doen -1 oneven;}

Achter de counters aangevinkt en oneven staat niets, deze worden daarom met de standaardwaarde 1 verhoogd. Achter nog-te-doen staat -1, deze counter wordt daarom met 1 verlaagd.

Aha, dat verlagen is duidelijk. Maar waarom staan die counters zo chaotisch bij elkaar? En waar is counter even gebleven? En hoe zit dat met counter priem?

Dat brengt ons bij het laatste stukje van deze vijf counters.

Alleen de laatste counter-increment werkt

Counters hebben één groot probleem. Nou ja, nog wel meer, maar we houden het hier bij één probleem. Als je bij een bepaalde selector meerdere counters wilt bijwerken, moet dat in dezelfde regel gebeuren. Anders wordt alleen de laatste counter veranderd.

input:checked {counter-increment: aangevinkt;}

input:checked {counter-increment: nog-te-doen -1;}

input:checked {counter-increment: oneven;}

Anders dan je misschien zou verwachten, wordt van de bovenste drie counters alleen counter oneven bijgewerkt, als een <input> wordt aangevinkt. Dit werkt op dezelfde manier als bij andere css-eigenschappen. Als je twee achtergrondkleuren gebruikt bij dezelfde selector, wordt ook alleen de laatste achtergrondkleur gebruikt.

Om alle drie de counters bij te werken, moet je de volgende regel gebruiken:

input:checked {counter-increment: aangevinkt nog-te-doen -1 oneven;}

Maar dit kan in deze vorm niet worden gebruikt. aangevinkt moet altijd worden verhoogd, maar oneven niet. En counter priem werkt weer anders, want die moet alleen bij priemgetallen worden bijgewerkt.

Om dit op te lossen, worden vier regels gebruikt:

input:nth-of-type(odd):checked {counter-increment: aangevinkt nog-te-doen -1 oneven;}

input:nth-of-type(even):checked {counter-increment: aangevinkt nog-te-doen -1 even;}

input:nth-of-type(odd)[data-priem]:checked {counter-increment: aangevinkt nog-te-doen -1 priem oneven;}

input:nth-of-type(even)[data-priem]:checked {counter-increment: aangevinkt nog-te-doen -1 priem even;}

De eerste regel verhoogt de counters aangevinkt en oneven en verlaagt nog-te-doen bij alle oneven (odd) aangevinkte aankruisvakjes.

De tweede regel doet hetzelfde, maar nu bij alle even aankruisvakjes.

Hiermee worden álle <input>'s bestreken, want elke <input> is even of oneven. En daarmee worden ook álle bij de <input>'s horende getallen bestreken.

De derde en vierde regel zijn hetzelfde als de eerste en de tweede regel, alleen moet hier ook nog het attribuut 'data-priem' aanwezig zijn bij <input>: [data-priem]. Bovendien wordt hier ook counter priem verhoogd.

Als 'data-priem' niet aanwezig is, verhogen (en verlagen) de derde en vierde regel de counters niet. De eerste en tweede regel voor oneven en even getallen werken gewoon.

Als 'data-priem' wel aanwezig is, verhogen (en verlagen) de derde en vierde regel de counters wel. Omdat de derde en vierde regel in de css na de eerste en tweede komen, worden de eerste en tweede regel genegeerd: de derde en vierde regel 'winnen' van de eerste en tweede regel. In alle gevallen worden de counters dus met slechts 1 verhoogd of verlaagd, er zijn geen dubbeltellingen.

De eerste regel werkt bij oneven <input>'s, maar alleen als 'data-priem' afwezig is.

De tweede regel werkt bij even <input>'s, maar alleen als 'data-priem' afwezig is.

De derde regel werkt bij oneven <input>'s, maar alleen als 'data-priem' aanwezig is.

De vierde regel werkt bij even <input>'s, maar alleen als 'data-priem' aanwezig is.

Met vier regels worden automatisch alle mogelijkheden bestreken. Het enige dat handmatig moet gebeuren, is het in de html aanbrengen van data-priem bij elke bij een priemgetal horende <input>. Dat is bij weinig getallen nog wel te doen. Maar bij een grote hoeveelheid wordt dit wel bezwaarlijk. Met JavaScript kun je op een simpele manier vaststellen, of een getal een priemgetal is. Of er nou weinig of veel getallen zijn.

Aangeven hoeveel in elke kolom is aangevinkt

Er zijn vijf kolommen, dus er zijn vijf counters voor de kolommen nodig: kolom-1 tot en met kolom-5.

De eerste vijf counters (aantal aangevinkte, aantal even en oneven getallen, aantal priemgetallen, en aantal nog te doen) zijn in vier regels te vangen, omdat er maar vier mogelijke combinaties zijn.

Als de counters voor kolommen net zo zouden werken, zou het aantal mogelijke combinaties en daarmee het aantal regels vijf keer zo hoog worden: 20. Want in elke kolom staan even getallen, oneven getallen en priemgetallen.

(En als je de iets hieronder staande counters voor de zeven regels ook zo zou afhandelen, zou het aantal css-regels 7 x 20 = 140 worden. Een waanzinnig aantal.)

Daarom worden de counters voor de kolommen (en de regels) op een andere manier afgehandeld.

Zoals iets hierboven bij Alleen de laatste counter-increment werkt beschreven, moeten counters bij eenzelfde selector in dezelfde css-regel worden bijgewerkt. Met andere woorden: als je 'n andere selector gebruikt voor de counters voor de kolommen, kun je die vijf counters los van de vijf eerder beschreven counters bijwerken.

Voor de eerste kolom wordt de volgende selector gebruikt:

input:nth-of-type(5n + 1):checked + label {counter-increment: kolom-1;}

:nth-of-type(5n + 1) selecteert alleen de eerste <input> en vervolgens elke <input> die vijf hoger is: de zesde, de elfde, enzovoort. Deze staan allemaal in de eerste kolom.

+ label wil zeggen: de <label> die in de html direct op deze <input> volgt. Hiermee wordt de counter voor de eerste kolom kolom-1 gekoppeld aan <label>. Dat is een andere selector dan bij de eerste vijf counters is gebruikt.

Bij elke aangevinkte <input> uit de eerste kolom, wordt met behulp van het extra + label de bij de eerste kolom horende counter kolom-1 met 1 verhoogd.

(Hoe deze selector precies werkt is te vinden bij input:nth-of-type(5n + 1):checked + label.)

Op een soortgelijke manier worden de <input>'s die bij de tweede, derde, vierde en vijfde kolom horen, met hun bijbehorende counters, afgehandeld.

Aangeven hoeveel in elke regel is aangevinkt

Er zijn zeven regels, dus er zijn zeven counters voor de regels nodig: regel-1 tot en met regel-7.

Ook deze counters worden apart afgehandeld, los van de andere counters, omdat er anders veel te veel css nodig is (zoals iets hierboven bovenaan Aangeven hoeveel in elke kolom is aangevinkt is beschreven.)

Bij de kolommen kon de combinatie <input> met de in de html gelijk daaropvolgende <label> worden gebruikt: input:nth-of-type(...) + label. Maar omdat counters bij dezelfde selector in dezelfde css-regel moeten worden verhoogd, is die selector voor deze counters niet meer te gebruiken.

Daarom wordt achter elke <label> een lege <span> gezet:

<input id="i-1" type="checkbox"><label for="i-1">1</label><span></span>

De counters voor de regels kunnen nu worden bijgewerkt, omdat met behulp van de <span> een nieuwe selector kan worden gebruikt:

input:nth-of-type(-n + 5):checked + label + span {counter-increment: regel-1;}

Deze selector geldt voor de eerste vijf <input>'s, oftewel: de <input>'s op de eerste regel. Als een van de eerste vijf <input>'s is aangevinkt, wordt counter regel-1 met 1 verhoogd. (Hoe deze selector precies werkt, is te vinden bij input:nth-of-type(-n + 5):checked + label + span.)

Op soortgelijke wijze kunnen de <input>'s met bijbehorende getallen van de tweede regel worden geselecteerd:

input:nth-of-type(n + 6):nth-of-type(-n + 10):checked + label + span {counter-increment: regel-2;}

Deze selector geldt voor elke zesde tot en met tiende <input>, oftewel: de <input>'s uit de tweede regel. Of feitelijk: voor de <span>'s die bij die <input>'s horen. (Hoe deze selector precies werkt, is te vinden input:nth-of-type(n + 6):nth-of-type(-n + 10):checked + label + span.)

Op soortgelijke kunnen de <input>'s uit de derde tot en met de zevende regel worden geselecteerd.

Op het scherm tonen van de counters

Een counter kan met behulp van content op het scherm worden gezet:

#aangevinkt::after {content: counter(aangevinkt);}

Hiermee wordt de counter met het aangevinkte aantal aankruisvakjes achter span#aangevinkt op het scherm gezet met behulp van een pseudo-element bij span#aangevinkt. Hoe dit precies werkt, is te vinden bij aangevinkt::after.

Op soortgelijke wijze kunnen alle counters op het scherm worden gezet.

Totaal van de aangevinkte getallen

Deze 'teller' is duidelijk van de generatie Redeloos, Reddeloos en Radeloos. Oftewel: dit kan dus eigenlijk niet zonder JavaScript.

Het probleem, of beter: de problemen.

Css werkt voornamelijk met zogenaamde strings: reeksen letters en cijfers. Maar om met behulp van css te kunnen rekenen heb je geen strings, maar getallen nodig. JavaScript (en andere talen) hebben uitgebreide mogelijkheden om een string in een getal om te zetten, en omgekeerd, maar css heeft die niet.

Het verschil tussen een string en een getal is met behulp van huisnummers duidelijk te maken. Als ik op nummer 100 woon, wil dat niet zeggen dat ik 50 voordeuren (50 'getallen') van nummer 50 woon. Er kunnen huizen ontbreken, oneven en even huisnummers kunnen worden gescheiden, toevoegsels als 'A' kunnen worden gebruikt, enzovoort, enzovoort. De 'berekening' huisnummer 100 min huisnummer 50, dus 50 huisnummers afstand is onzinnig. '100' is hier wel een getal, maar om te rekenen heb je er niets aan. '100' is hier een string en geen getal.

Bij getallen is 100 min 50 gewoon altijd 50. (Althans: in het tientallig talstelsel, maar laat ik nou niet vervelend worden.)

Je zou elke <input> een data-attribuut met het volgnummer kunnen geven of iets soortgelijks. En dan zou je de volgnummers van de aangevinkte <input>'s kunnen optellen met behulp van calc(). Maar attributen bevatten geen getallen, maar strings. data-volgnummer = "27" is ongeschikt om mee te rekenen, omdat '27' een 'huisnummer' is, een string, en geen getal. En calc() kan niets met strings, die heeft echte getallen nodig.

In het voorbeeld is gebruik gemaakt van zogenaamde css-variabelen (ook vaak 'custom properties' genoemd). Zo'n variabele moet beginnen met -- (twee streepjes). Er worden 35 variabelen aangemaakt met de naam --w-1 tot en met --w-35, één voor elke <input>. Bij het aanmaken krijgen al die variabelen de waarde 0.

Zodra een <input> wordt aangevinkt, krijgt de variabele met hetzelfde volgnummer als de <input> de waarde van het volgnummer:

#i-11:checked ~ #tekst {--w-11: 11;}

Als input#i-11 (de elfde <input>) is aangevinkt, geef --w-11 dan de waarde 11. Op dezelfde manier kunnen alle 35 variabelen van 0 naar de waarde van het volgnummer van de <input> worden veranderd. En dat volgnummer is hetzelfde als het bijbehorende getal. Als --w-11 als waarde 11 heeft, kan het niet anders dan dat input#i-11 is aangevinkt, dat het elfde getal is aangevinkt.

In tegenstelling tot 'gewone' attributen en dergelijke kunnen css-variabelen echte getallen bevatten, waardoor calc() hiermee wel kan rekenen. Als je de 35 variabelen bij elkaar optelt, krijg je in eerste instantie als uitkomst 0, omdat alle variabelen bij openen van de pagina 0 zijn.

Zodra bijvoorbeeld de elfde <input> is aangevinkt, heeft de elfde variabele de waarde 11 gekregen en is de uitkomst van de optelling 11. Als de zeventiende <input> is aangevinkt, heeft de zeventiende variabele de waarde 17 gekregen en is de uitkomst 10 + 17 = 27. Enzovoort.

Kortom: je kunt nu daadwerkelijk het totaal van de gekozen getallen optellen.

Op dat moment besefte ik dat ik werkelijk geniaal was en iets voor elkaar had weten te krijgen, dat niemand nog ooit gelukt was. Zonder JavaScript. Echt uniek. Ik ben antimilitarist, maar in dit geval citeer ik graag De Generaal: mijn gemoed schoot vol. Roem, glorie, standbeelden en andere liederlijkheden zouden mij eindelijk toevallen.

Goed. Ik was dus een kleinigheidje en m'n medicijnen vergeten.

calc() levert een getal als uitkomst, en dat kun je op geen enkele manier met alleen html en css op het scherm zetten. En het is wel ontroerend om te weten dat diep in de ingewanden van de computer het totaal van de getallen bekend is, maar het zou toch ook wel aardig zijn, als je daar vervolgens iets mee zou kunnen. Bijvoorbeeld dat totaal tonen.

Dat kan dus stomweg niet, waardoor het geniale van deze oplossing helaas beperkt blijft tot heldercomputerenden.

Er is op dit moment geen enkele mogelijkheid om dat getal weer terug te zetten naar een string, waarmee je iets kunt. (Dat het totaal toch op het scherm wordt gezet, is pure belazerij. Dat is helemaal geen totaal, maar dat komt later nog.)

Ook is de uitkomst van calc() niet te gebruiken in een selector als :nth-of-type met iets als :nth-of-type(calc(--w-1 + --w-2). Zo'n selector wordt stomweg genegeerd, omdat het ongeldige css is.

Er zijn wel plannen om dit soort dingen in de toekomst mogelijk te maken door iets als attrib() ook binnen calc() te laten werken, en omgekeerd, maar dat schijnt waanzinnig ingewikkeld te zijn, dus daar heb je nu nog niets aan.

Uit pure frustratie is voor een omweg gekozen. calc() wordt nog steeds gebruikt om het totaal te berekenen. Dat totaal wordt vervolgens met 1 rem vermenigvuldigd. (1 rem is de grootte van de letter. Normaal genomen is 1 rem 16 px, maar als de bezoeker dit heeft veranderd kan het meer of minder zijn.)

Alleen wordt niet met 1 rem, maar met -1 rem vermenigvuldigd. Maar dat maakt voor calc() verder niet uit.

Door het totaal met -1 rem te vermenigvuldigen, wordt de uitkomst omgezet naar de eenheid rem. Als de tien hoogste getallen worden gekozen (26 tot en met 35) is het totaal 305. Hoger kan de uitkomst dus nooit zijn. (Als meer dan 10 getallen worden gekozen, wordt de tekst met de counters en dergelijke onder een waarschuwing verborgen.

Deze berekening levert een bepaald aantal rem op, en daar kan css gewoon mee werken.

Als bijvoorbeeld de elfde en de twaalfde <input> zijn aangevinkt, is de uitkomst van de berekening (11 + 12) x -1 rem = -23 rem. En dat is een bruikbare eenheid.

In een aparte <span> staan alle mogelijke uitkomsten van het totaal van getallen bij de aangevinkte keuzevakjes. Dat zijn 306 mogelijkheden, van 0 (geen enkel aangevinkt) tot en met de 305 (de tien hoogste getallen aangevinkt). Die getallen zijn onder elkaar gezet. Als niets is aangevinkt, zie je de 0, want die staat bovenaan. Zodra iets is aangevinkt wordt een berekening gemaakt, die het aantal rem oplevert dat de <span> naar boven moet worden gezet om het juiste getal te tonen.

Bij 35 getallen is dit nog te doen, bij 1000 getallen wordt het toch wat begrotelijk.

Zodra je gaat zoomen of de lettergrootte verandert, levert dit in sommige browsers een afwijking op, waardoor de verkeerde uitkomst wordt getoond. Meer hierover is te vinden bij Bekende problemen (en oplossingen). Bovendien is dit volstrekt onbruikbaar voor schermlezers, omdat die in trouwhartige stompzinnigheid álle getallen van 0 tot en met 305 menen te moeten voorlezen. Daarom wordt de regel met het totaal voor schermlezers verborgen.

Voor dit soort dingen is JavaScript echt beter geschikt. Dan kan het ook in schermlezers worden gebruikt en leveren zoomen en andere lettergroottes ook geen problemen op.

Voor vermenigvuldigen en dergelijke van de getallen is dit uiteraard helemaal onbruikbaar, want de mogelijke uitkomsten van vermenigvuldigen lopen in de triljoenen mogelijkheden. Denk ik, want mijn rekenmachientje en ik begonnen wat tekenen van trumpisme te vertonen, toen ik dit probeerde te berekenen. Maar ook vermenigvuldigen zou met JavaScript vrij simpel te doen zijn.

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.

<main>

Hierbinnen staat de belangrijkste inhoud van de pagina (in dit voorbeeld is dat de hele pagina).

Met behulp van dit soort nieuwe semantische elementen kan bijvoorbeeld een schermlezer in één keer een heel menu passeren en gelijk naar de echte inhoud gaan. Alleen hadden deze nieuwe elementen tot voor kort één probleem: ze hadden in de praktijk nog weinig nut, omdat schermlezers en dergelijke ze nog niet herkenden. Daarom werd een zogenaamde WAI-ARIA-code toegevoegd aan deze elementen. Dat is een al veel langer bestaande code, die schermlezers en dergelijke wel herkennen. Voor <main> ziet dat er zo uit:

<main role="main">

Inmiddels is dit behoorlijk veranderd. Het advies is nu om deze speciale toevoeging niet meer te gebruiken, omdat de meeste schermlezers en dergelijke dit soort nieuwe elementen inmiddels herkennen.

WAI-ARIA-codes

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

Er 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 en dergelijke, zodat dit niet wordt voorgelezen. Op de normale weergave op het scherm heeft dit verder geen enkele invloed.

De aangevinkte getallen worden met behulp van calc() bij elkaar opgeteld, en het resultaat daarvan wordt op het scherm gezet. Alleen is dat nep, want het resultaat van een berekening met calc() kan niet zonder meer worden getoond.

Daarom is voor een omweg gekozen: alle mogelijke uitkomsten (dat zijn er 306) staan onder elkaar in een aparte <span>. De uitkomst van calc() wordt gebruikt om die <span> zover naar boven te plaatsen dat de juiste uitkomst wordt getoond.

Dat betekent dat al die 306 mogelijke uitkomsten voortdurend aanwezig zijn, alleen zie je er steeds maar één. Die 306 getallen worden dus netjes door schermlezers voorgelezen. Wat ook correct is, want ze staan er. Daarom wordt de hele regel over het optellen met aria-hidden="true" verborgen voor schermlezers:

<p id="optellen" aria-hidden="true">Als je alle gekozen getallen bij elkaar optelt, is het totaal

(...)

</p>

De in de <p> zittende <span>'s worden hiermee ook verborgen. De héle regel wordt voor schermlezers verborgen, want het is niet zo zinvol het totaal aan te kondigen en dat dan vervolgens weg te laten.

Meer over deze <span> met 306 uitkomsten is te vinden bij <span>0.&nbsp;&nbsp; ...

(Als je voor deze optelling JavaScript zou gebruiken, kan dit allemaal veel simpeler en is het ook voor schermlezers bruikbaar.)

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 29 januari 2018.

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

Dit voorbeeld is getest op de volgende systemen:

Desktopcomputers

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

OS X 10.11.6 ('El Capitan') (1680 x 1050 px, resolution: 96: dpi, device-pixel-ratio: 1):
Firefox, Safari, Opera en Google Chrome, in grotere en kleinere browservensters.

Linux (Kubuntu 14.04 LTS, 'Trusty Tahr') (1280 x 1024 px, resolution: 96 dpi):
Firefox, Opera en Google Chrome, in grotere en kleinere browservensters.

Laptops

Windows 8.1 (1366 x 768 px, resolution: 96 dpi):
Bureaublad-versie: Firefox, UC Browser, Google Chrome, Opera en Internet Explorer 11, in grotere en kleinere browservensters.
Startscherm-versie: Internet Explorer 11.

Windows 10 (1600 x 900 px, resolution: 96 dpi):
Firefox, UC Browser, Google Chrome, Internet Explorer 11, Opera en Edge, in grotere en kleinere browservensters.

Tablets

iPad met iOS 9.3.5 (1024 x768 px, device-pixel-ratio: 1):
Safari, Chrome for iOS, UC Browser, Firefox (alle portret en landschap).
Opera Mini (Opera Turbo) portret en landschap.

iPad met iOS 11.2.5 (2048 x 1536 px, device-pixel-ratio: 2 ('retina'):
Safari, Chrome for iOS, Firefox (alle portret en landschap).
Opera Mini (Opera Turbo) portret en landschap.

Android 4.4.2 ('Kitkat') (1280 x 800 px, resolution: 96 dpi):
Android browser, UC Browser, Firefox en Chrome (alle portret en landschap).
Opera Mini (besparingen uitgeschakeld) portret en landschap.

Android 4.4.2 ('Kitkat') (2560 x 1600 px, resolution: 192 dpi):
Android browser, UC Browser, Firefox en Chrome (alle portret en landschap).
Opera Mini (besparingen uitgeschakeld) portret en landschap.

Android 6.0 ('Marshmallow') (1920 x 1200 px, resolution: 144 dpi):
Dolphin, Samsung Internet, UC Browser, Firefox en Chrome (alle portret en landschap).
Opera Mini (besparingen uitgeschakeld) portret en landschap.

Android 7.0 ('Nougat') (1920 x 1200 px, resolution: 144 dpi):
Dolphin, Samsung Internet, UC Browser, Firefox en Chrome (alle portret en landschap).
Opera Mini (besparingen uitgeschakeld) portret en landschap.

Smartphones

Windows 10 Mobile (1280 x 720 px, resolution: 192 dpi):
Edge en UC browser (portret en landschap).

Android 4.1.2 ('Jelly Bean') (800 x 480 px, resolution: 144 dpi):
Chrome, Android browser, UC Browser en Firefox (alle portret en landschap).
Opera Mini (besparingen uitgeschakeld) portret en landschap.

Android 7.0 ('Nougat') (1280 x 720 px, resolution: 192 dpi):
Dolphin, Samsung Internet, UC Browser, Firefox en Chrome (alle portret en landschap).
Opera Mini (besparingen uitgeschakeld) 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 de iPad, Android, Windows Phone en Windows 10 Mobile, waar een touchscreen is gebruikt. Op Windows 8.1 en 10 is getest met een touchscreen, met een combinatie van toetsenbord en touchpad, en met een combinatie van toetsenbord en muis.

Als JavaScript is gebruikt, is op de desktop ook getest zonder JavaScript. (Op iOS, Android en Windows 10 Mobile is niet getest zonder JavaScript, omdat je JavaScript in een toenemend aantal mobiele browsers niet uit kunt zetten. Bovendien is een mobiel apparaat zonder JavaScript niet veel meer dan een duur en groot uitgevallen horloge.) Ook is getest zonder css en - als afbeeldingen worden gebruikt - zonder afbeeldingen.

Schermlezers en dergelijke

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

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

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

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

TalkBack is een in Android ingebouwde schermlezer. Er is getest in combinatie met Chrome op Android 4.4,2, 6.0 en 7.0.

VoiceOver is een in iOS en OS X ingebouwde schermlezer. Er is getest in combinatie met Safari op iOS (9.3.5 en 11.0.2) en OS X 10.11.6.

ChromeVox is een schermlezer in de vorm van een extensie bij Google Chrome. Er is getest op een systeem met Kubuntu Linux 14.04.

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

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. De kans is (heel erg) groot dat dit voorbeeld niet (volledig) werkt op niet-geteste systemen en apparaten. Om het wel (volledig) werkend te krijgen, zul je vaak (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 Android browser 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 eigenlijk geen beginnen aan.

De html is gevalideerd met de validator van w3c, de css ook. 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, omdat er anders 'n redelijke kans is 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!)

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.

Voor zover van toepassing wordt eerst het ontbreken van JavaScript, css en/of afbeeldingen besproken. Vervolgens toegankelijkheid voor specifieke groepen bezoekers, zoals gebruikers van het toetsenbord en van schermlezers. Als laatste volgen algemene problemen in alle of in specifieke browsers.

Zonder CSS

Zonder css worden álle teksten getoond, inclusief alle 306 mogelijke totalen en de waarschuwing voor als er meer dan tien aankruisvakjes zijn aangevinkt. Deze teksten en totalen worden verborgen met behulp van css, dus zonder css zijn ze gewoon zichtbaar.

Alle counters daarentegen zie je niet, want die worden met behulp van css op het scherm gezet. Je ziet wel de tekst waar de counter bij hoort, maar niet de counters zelf. Ook het woord 'priemgetal(len)' wordt niet getoond, want ook hier wordt css voor gebruikt. De gekleurde voortgangsbalk ontbreekt.

Omdat het in dit voorbeeld nou juist om bovenstaande dingen gaat, werkt dit voorbeeld zonder css dus stomweg niet.

Belangrijke informatie mag nooit afhankelijk zijn van css, maar moet altijd worden getoond. Ook als css uitstaat en/of niet goed is geïmplementeerd. Als je technieken uit dit voorbeeld gebruikt, moet je je echt goed afvragen, of er geen belangrijke informatie kan verdwijnen.

Tekstbrowsers

Lynx toont alle aankruisvakjes, gevolgd door alle teksten, inclusief de waarschuwing dat er meer dan tien vakjes zijn aangevinkt. Lynx toont ook alle 306 totalen. De counters en het woord 'priemgetal(len)' worden niet getoond.

WebbIE toont de aankruisvakjes en de correcte teksten. De waarschuwing voor het aankruisen van meer dan tien vakjes wordt alleen getoond, als meer dan tien vakjes zijn aangevinkt. De counters, het woord 'priemgetal(len)' en de 306 totalen worden niet getoond.

Omdat het in dit voorbeeld nou juist om bovenstaande dingen gaat, werkt dit voorbeeld dus stomweg niet in deze tekstbrowsers. Belangrijke informatie hoort in de tekst aanwezig te zijn, zodat ook tekstbrowsers deze kunnen tonen. Als je technieken uit dit voorbeeld gebruikt, moet je je echt goed afvragen, of er geen belangrijke informatie kan verdwijnen.

Schermlezers

Eerst een algemene opmerking: bij Schermlezers en dergelijke kun je zien, in welke schermlezers met welke browsers op welke systemen is getest. Dit is vooral van belang voor de 'generated content': de met behulp van ::after en ::before op het scherm gezette counters en het woord 'priemgetal(len)'.

  • Het zit er dik in dat een aantal niet-geteste schermlezers de hierboven genoemde gegenereerde tekst niet voorlezen. En ook een aantal wel geteste schermlezers hebben hier problemen mee, zoals hieronder is te lezen.

    Als je technieken uit dit voorbeeld gebruikt, moet je je echt goed afvragen, of er geen belangrijke informatie kan verdwijnen.

    (In het verleden las vrijwel geen enkele schermlezer gegenereerde tekst voor, dit is al sterk verbeterd. In de ontwerp-specificatie voor gegenereerde inhoud staat dat gegenereerde tekst ook voor spraak beschikbaar moet zijn. Dus ooit gaat dit overal werken, maar dat is nu nog niet het geval.)

    VoiceOver met Safari op iOS 11

    Leest de gegenereerde tekst niet voor, dus de counters en het woord 'priemgetal(len)' vallen weg in deze schermlezer. Op iOS 9 doet VoiceOver met Safari het wel goed. iOS 11 is nog niet zo lang uit, en in het begin was VoiceOver helemaal een puinhoop. Inmiddels werkt VoiceOver op iOS 11 al veel beter, dus hopelijk wordt ook dit snel opgelost.

    (Grappig genoeg doet Firefox met VoiceOver het wel goed op iOS 11, terwijl Apple andere browsers verplicht dezelfde rendering machine als Safari te gebruiken. Normaal genomen wordt niet op Firefox met VoiceOver getest, maar hier won de nieuwsgierigheid het.)

    ChromeVox

    Leest de gegenereerde tekst niet voor, dus de counters en het woord 'priemgetal(len)' vallen weg in deze schermlezer.

    Alle andere geteste schermlezers

    De counters en het woord 'priemgetal(len)' worden op de juiste manier voorgelezen.

  • Omdat schermlezers de 306 mogelijke totalen allemaal voorlezen, wordt met behulp van de WAI-ARIA-code aria-hidden="true" de hele regel over het totaal voor schermlezers verborgen. (Met JavaScript zou dit wel kunnen worden uitgevoerd op 'n manier, die ook voor schermlezers bruikbaar is.)
  • Alle geteste schermlezers lezen de waarschuwing voor meer dan tien aangevinkte aankruisvakjes voor, als er meer dan tien zijn aangevinkt. En ze laten die waarschuwing achterwege, als er tien of minder zijn aangevinkt. Precies zoals het hoort dus.

Zoomen en andere lettergroottes

In meerdere browsers wordt bij het totaal een verkeerde uitkomst getoond, als wordt gezoomd of als de lettergrootte wordt veranderd. Voor het tonen van het totaal wordt het juiste getal met behulp van translateY() getoond. Gebruik van een andere eigenschap (zoals top en margin-top) maakte geen verschil. Gebruik van andere eenheden dan rem (zoals vh en px) maakte geen verschil. Ook het gebruik van een waarde als 0,99 of 1, 01 in plaats van 1 maakte geen verschil.

(Sommige veranderingen gaven weliswaar andere afwijkingen, maar helaas niet minder. Sterker nog: de eenheid em leverde in nog meer browsers fouten op dan de nu gebruikte eenheid rem.)

Een browser kan bij inzoomen tot 110% een verkeerd totaal tonen, bij 125% het juiste totaal en bij nog meer inzoomen weer een verkeerd totaal. Het lijkt daarom te gaan om afrondingsfouten bij het berekenen van de juiste positie. 100 een klein verschil is maakt bij elkaar 'n fors verschil.

Hoe dan ook: zodra je gaat zoomen of de lettergrootte veranderd, is het totaal in de hieronder staande browsers niet meer betrouwbaar.

Dolphin op Android

Bij een andere lettergrootte wordt een verkeerd totaal getoond. Bij zoomen wordt wel het juiste totaal getoond.

Google Chrome en Opera op Linux, OS X en Windows, UC browser op Windows

Bij zoomen wordt een verkeerd totaal getoond. Bij een andere lettergrootte wordt wel het juiste totaal getoond.

Als je JavaScript zou gebruiken om het totaal te berekenen, kun je dat totaal gewoon op het scherm zetten en zou dit probleem niet spelen.

Voortgangsbalk en kleurenblinden

De voortgangsbalk maakt alleen gebruik van de kleuren oranje en groen. In het algemeen is het een bijzonder slecht idee om een verandering alleen met kleur aan te geven, want heel veel mensen kunnen dat niet goed zien.

Het contrast tussen oranje en groen is absoluut onvoldoende voor tekst. Maar in dit geval gaat het om een voortgangsbalk, wat minder lastig is te zien dan tekst. In alle gebruikte simulaties van de verschillende vormen van kleurenblindheid is het verschil tussen de kleuren nog redelijk goed te zien, hoewel het bij alleen grijstinten wel erg lastig wordt.

Onder de balk staat echter ook nog met getallen aangegeven, hoeveel getallen er al zijn aangevinkt, en hoeveel er nog moeten worden gedaan. Als iemand echt de voortgangsbalk niet kan gebruiken, kan die dus terugvallen op de tekst.

Als alleen deze balk aanwezig zou zijn, zou dat absoluut onvoldoende zijn. Je zou dan bijvoorbeeld in de balk zelf een percentage kunnen laten zien. Of op de juiste plaats iets laten uitsteken boven of onder de balk. Of een gestreepte gradiënt gebruiken, waarbij je de richting van de strepen op de juiste plaats laat veranderen. Alles is goed, zolang de verandering maar niet alleen van kleur afhankelijk is.

UC browser op Android en Windows 10 Mobile, Internet Explorer 11, Opera Mini op Android

De regel met het totaal ontbreekt volledig

Om dit totaal te berekenen, wordt gebruik gemaakt van css-variabelen. Deze browsers kunnen daar niet mee uit de voeten, waardoor het totaal altijd op 0 zou blijven staan.

De regel met het totaal wordt verborgen. Vervolgens wordt met behulp van @supports (--w-1: 1) gekeken, of de browser css-variabelen ondersteunt. Pas als dat het geval is, wordt de regel met het totaal getoond.

Alle browsers

Aankruisvakjes staan iets te hoog of te laag

Normaal genomen kun je het best een <input type="checkbox"> binnen de bijbehorende <label> zetten. De browser regelt dan zelf dat de aankruisvakjes op de goede hoogte staan ten opzichte van de tekst in de <label>.

Hier kan dat echter niet, omdat op allerlei plaatsen in de selectors varianten van input + en input ~ worden gebruikt. Dat kan alleen als het element voor en na de + en de ~ dezelfde ouder hebben. Bij <label><input></label> is <label> de ouder van <input>, en dan werken deze selectors niet.

Elke browser zet de <input> (het aankruisvakje) nu op een iets andere hoogte ten opzichte van de tekst in de <label>. Het is uitermate moeilijk om <input>'s met css in het gareel te krijgen, als dat al kan. Bovendien werkt dat bij verschillende browsers ook nog 'ns op 'n verschillende manier.

In de meeste browsers staan de aankruisvakjes nu goed ten opzichte van de erbij horende tekst. In enkele browsers staan de vakjes iets hoger of lager dan je zou willen, maar het is overal acceptabel.

Het is wat zinloos om hier een hele lijst met de verschillende browsers neer te zetten, omdat dezelfde browser op een ander systeem of een scherm met een andere resolutie een andere afwijking kan hebben.

Dit ís trouwens wel op te lossen. Je kunt het 'echte' aankruisvakje, de 'echte' <input>, verbergen en met behulp van ::before een nepvakje maken. Dat kun je vervolgens precies goed neerzetten. Omdat dit nepvakje bij de <label> hoort, werkt het precies hetzelfde als een 'echt' aankruisvakje. De <label> staat achter de bijbehorende <input>, dus met 'n selector als input:checked + label::after kun je het nepvakje markeren, als de <input> is aangevinkt.

In dit voorbeeld is dit niet gedaan, omdat het hier niet om gaat. Het is vrij veel werk om die namaak-aankruisvakjes goed te krijgen, en het is zo al ingewikkeld genoeg met al die ::after's en ::before's.

Android browser, Opera Mini en UC browser op Android 4.1.2 en 4.4.2

De aankruisvakjes staan soms niet mooi horizontaal verdeeld

In deze browsers staan de aankruisvakjes soms niet mooi verdeeld. Op nieuwere versies van Android staat het wel goed (behalve in Android browser, want die ellendepukkel ontbreekt gelukkig in nieuwere versies van Android).

Een precies lijstje is niet zo zinvol, want op dezelfde versie van Android doet UC browser het bijvoorbeeld wel goed in een lagere resolutie, maar niet in een hogere.

Ernstig is dit probleem niet, en deze versies van Android zijn in rap tempo aan het verdwijnen, dus hier is verder niets aan gedaan.

(Dit wordt veroorzaakt door het gebruik van flexbox, waar deze browsers niet mee uit de voeten kunnen. Je kunt hier meer over lezen bij float: left;.)

De voortgangsbalk verkleurt niet

Althans: de balk blijft oranje en verspringt, als er tien vakjes zijn aangevinkt, in één keer naar groen. Ook hier is een lijstje weer niet zinvol, omdat het zich niet in álle versies van de browser voordoet.

Sommige versies van deze browsers op oudere versies van Android hebben nog de -webkit-variant van linear-gradient nodig: overal waar linear-gradient wordt gebruikt, moet voor deze browsers -webkit-linear-gradient worden gebruikt, met een iets andere syntax.

Hier is verder niets aan gedaan, omdat dit niet echt belangrijk is. En de css voor die verkleurende balk bestaat uit tien nogal lange regels, dus om dit nou tien keer te gaan herhalen voor deze snel verdwijnende fossielen...

Maar mocht je dit echt belangrijk vinden, dit is de syntax voor deze browsers:

-webkit-linear-gradient(left, green 5%, orange 15%);

Dit is de gradiënt als één <input> is aangevinkt. De enige verschillen zijn het voorvoegsel -webkit- voor linear-gradient, en left in plaats van to right. (In deze oudere syntax geef je niet aan naar welke richting de gradiënt loopt, maar juist vanaf welke richting.)

Omdat in de selectors voor de gradiënt het teken + wordt gebruikt, moet je ook nog de oplossing voor 'priemgetal(len)' gelijk hieronder toepassen om de gradiënts werkend te krijgen.

Het woord 'priemgetallen' verandert niet in 'priemgetal' (en omgekeerd)

Bij openen van de pagina staat op het scherm '0 priemgetallen'. Zodra één priemgetal is aangevinkt, moet dit veranderen in '1 priemgetal'. Als er nog 'n priemgetal wordt aangevinkt, moet het weer veranderen in '2 priemgetallen'.

Sommige versies van deze browsers op oudere versies van Android kunnen niet uit de voeten met een + of een ~ in een selector, daardoor verandert het woord niet. Maar als je het scherm draait, zetten sommige browsers alsnog het gewijzigde woord op het scherm.

Dit is eventueel op te vangen met de volgende regel bovenin de css:

@-webkit-keyframes bugfix {from {padding-left: 0;} to {padding-left: 0;}}

Deze regel doet niets (hij verandert de padding-left van 0 naar 0), maar dit zorgt ervoor dat het woord wel op de juiste manier verandert. Normaal genomen is het een bijzonder slecht idee alleen een -webkit-variant te gebruiken, maar hier hebben alleen browsers met een verouderde versie van de webkit rendering machine dit probleem, dus hier kan dat zonder bezwaar.

Verder moet je in de css nog aan div#tekst de volgende regel toevoegen:

-webkit-animation: bugfix infinite 1s;

Dit zorgt ervoor dat de iets hoger opgegeven 'verandering' van 0 naar 0 eindeloos wordt herhaald. De 'verandering' duurt 1 seconde.

Omdat deze versies van Android snel aan het uitsterven zijn, is deze truc in dit voorbeeld niet gebruikt.

De counters bij de regels veranderen niet

Dit is precies hetzelfde probleem als gelijk hierboven wordt beschreven voor het woord 'priemgetal(len)'. De eventuele oplossing is ook hetzelfde.

De melding dat er meer dan tien zijn aangevinkt verschijnt niet

Ook dit probleem is hetzelfde als iets hierboven wordt beschreven voor het woord 'priemgetal(len)'. De eventuele oplossing is ook hetzelfde.

iOS 9.3.5

Het totaal blijft op 0 staan.

Pas na draaien van het scherm, wordt het totaal bijgewerkt. Op iOS 11 werkt dit wel goed.

Dankzij het geweldige beleid van Apple om op iOS elke browser te verplichten de rendering machine van Safari te gebruiken, doet dit probleem zich in elke browser voor.

Dit lijkt op een oud probleem met de webkit rendering engine bij gebruik van ~ of + in een selector, behalve dat hier geen enkele truc werkt. Het verbergen van de regel met het totaal kan ook niet, want iOS beweert gewoon alles te ondersteunen, dus een feature query als @supports (--w-1: 1) werkt niet.

Omdat het totaal op 0 blijft staan, zal duidelijk zijn dat het niet klopt. Maar echt elegant is dit natuurlijk niet.

Firefox op Android

Het getal bij totaal staat te laag

In sommige versies van Android staat het totaal iets te laag. Wel wordt het juiste totaal weergegeven.

Het getal staat te laag in de smartphones met Android 4.1.2 en 7.0 en in de tablets met Android 6 en 7, maar het staat goed in de tablets met Android 4.4.2.

Firefox op Android heeft wel wat vaker problemen met dingen als lettergrootte en regelhoogte. De precieze oorzaak is onduidelijk. Dit kon niet worden opgelost.

Validatie

@supports geeft een fout in de css-validator van w3c. Hetzelfde geldt voor css-variabelen. Omdat de oorzaak van deze fouten bekend is, kunnen deze gewoon worden genegeerd. Kennelijk zijn deze eigenschappen en regels nog niet in de validator geïmplementeerd.

Wijzigingen

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

:

Nieuw opgenomen.

Inhoud van de download en licenties

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

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

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

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

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

tekst-122-dl.html: de pagina met het voorbeeld.

tekst-122.pdf: deze uitleg (aangepast aan de inhoud van de download).

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

122-css-dl:

tekst-122-dl.css: stylesheet voor tekst-122-dl.html.

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 blauw gekleurd. 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.

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, downloadt dan de hele handel (ga terug naar het voorbeeld en kies daar voor downloaden). In de download zit 'n voorbeeld dat wel naadloos aansluit op de uitleg in de download.

<!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 breedte. En dat is een probleem. Sites waren, in ieder geval tot voor kort, 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. Op deze pagina bijvoorbeeld wordt de pagina 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 gebruik ik niet. De bezoeker kan zelf nog gewoon zoomen, wat belangrijk is voor mensen die wat slechter zien.

<input id="i-1" type="checkbox"><label for="i-1">1</label><span></span>

Dit zijn de <input> en de <label> voor het getal '1'. Normaal genomen is het beter om een <input> bínnen de bijbehorende <label> te zetten:

<label>1<input type="checkbox"></label>

Dan is gelijk duidelijk, welke <label> (me de daarin zittende tekst) bij welke <input> hoort. In dit geval kan dat niet, omdat in verschillende selectors dingen als input:checked + label of input:checked ~ #tekst. Een + en een ~ in een selector werken alleen, als het element voor en het element achter de + en de ~ dezelfde ouder hebben.

Als <input> binnen <label> zou staan, zou <label> de ouder van <input> zijn en zou het achter de + of ~ een andere ouder hebben. Daarom wordt in dit geval de <input> buiten de <label> gezet.

Door het gebruik van het attribuut for="i-1" in <label> wordt de <label> gekoppeld aan de <input> met id="i-1". (Hetzelfde geldt voor de andere 34 <input>'s en <label>'s, die elk een eigen volgnummer hebben.

De lege <span> aan het einde van de regel wordt alleen gebruikt om te kunnen tellen, hoeveel getallen in elke regel zijn aangevinkt. Hier is meer over te vinden bij input:nth-of-type(-n + 5):checked + label + span.

<input id="i-2" data-priem type="checkbox"><label for="i-2">2</label><span></span>

Dit zijn de <input> en de <label> voor het getal '2'. Het verhaal is precies hetzelfde als gelijk hierboven voor de <input> en <label> voor het getal '1'. Alleen heeft <input> hier een extra attribuut: data-priem.

Je kunt zelf attributen bedenken, op voorwaarde dat de naam begint met data-. Wat er achter het koppelteken komt, mag je zelf bedenken. In dit geval is dat 'priem'. Dit attribuut wordt aan de <input> toegevoegd, als de <input> bij een priemgetal hoort: een getal dat alleen door zichzelf en door 1 is te delen, zoals 2, 3, 5 en 7.

In een aantal selectors wordt gekeken, of dit attribuut aanwezig is. Als dat zo is, gaat het om een priemgetal en wordt de counter voor priemgetallen met 1 verhoogd.

Vaak wordt in een data- een bepaalde waarde opgeslagen, bijvoorbeeld data-priem="ja" of data-kleur="rood". In een selector kan ook worden gekeken, of de inhoud van het data-attribuut aan een bepaalde voorwaarde voldoet. Zo zou je bijvoorbeeld alleen elementen met data-kleur="rood" rood kunnen kleuren. Ook kan de inhoud van het data- met behulp van content en attrib() op het scherm worden gezet.

In dit geval is geen inhoud gegeven aan data-priem, omdat dat niet nodig is. Alleen priemgetallen hebben dit attribuut, andere getallen niet, dus een simpele test op het al dan niet aanwezig zijn van dit attribuut is hier voldoende.

<p id="optellen" aria-hidden="true">Als je alle getallen bij elkaar optelt, is het totaal (...) tot en met (...) </p>

Binnen deze <p> staan nog twee <span>'s, met behulp waarvan het totaal van de aangevinkte getallen wordt weergegeven. Om dat totaal – zonder JavaScript – weer te kunnen geven, zijn alle 306 mogelijke uitkomsten onder elkaar gezet. Met behulp van translateY() wordt dan de juiste uitkomst getoond.

Omdat alle 306 mogelijke uitkomsten aanwezig zijn, ook al zie je ze niet allemaal tegelijk, worden deze door schermlezers allemaal voorgelezen: 0 tot en met 305. Daar zit per definitie ook de juiste bij, maar helaas ook 305 die niet juist zijn.

Dit is natuurlijk volkomen zinloos. Ook het voorlezen van de zin 'Als je alle getallen bij elkaar optelt, is het totaal' zonder daar vervolgens het totaal bij voor te lezen, is weinig zinvol. Daarom wordt deze hele <p> met behulp van de WAI-ARIA-code aria-hidden="true" verborgen voor schermlezers. Door de <p> te verbergen, worden ook gelijk de daarin zittende <span>'s verborgen, en dus niet voorgelezen.

<span>0.&nbsp;&nbsp; 1.&nbsp;&nbsp; 2.&nbsp;&nbsp; 3.&nbsp;&nbsp; 4.&nbsp;&nbsp; 5.&nbsp;&nbsp; 6.&nbsp;&nbsp; 7.&nbsp;&nbsp; 8.&nbsp;&nbsp; 9.&nbsp;&nbsp; 10.&nbsp; 11.&nbsp; 12.&nbsp; (...) tot en met (...) 98.&nbsp; 99.&nbsp; 100. 101. (...) tot en met (...) 304. 305.</span>

In deze <span> staan alle mogelijke totalen van de aangevinkte getallen, van 0 (niets aangevinkt), 1 (alleen de 1 aangevinkt) tot en met 305 (de tien hoogste getallen aangevinkt), en alles daartussenin. Deze getallen staan allemaal op één regel, achter elkaar. Bij #optellen span span wordt het juiste getal echter met behulp van translateY() getoond. Dat is een verticale beweging. Daarom moeten de getallen in deze <span> niet naast, maar onder elkaar staan.

Om dat voor elkaar te krijgen heeft span#totaal, de ouder van deze <span>, bij #totaal een breedte van 2 em gekregen. Omdat tussen de getallen in deze <span> een spatie staat, wordt elk getal nu op een nieuwe regel gezet.

Om te voorkomen dat bij de kortere getallen toch twee getallen worden getoond (binnen de breedte van 2 em passen bijvoorbeeld de 1 en de 2 naast elkaar op dezelfde regel), zijn bij de getallen 0 tot en met 9 twee spaties achter het getal gezet, en bij de getallen 10 tot en met 99 één spatie. Omdat opeenvolgende gewone spaties worden samengevoegd tot één spatie, zijn zogenaamde 'non-breaking spaces' &nbsp; gebruikt. Deze spaties worden niet samengevoegd.

Om de punt aan het eind van de zin netjes aan te laten sluiten op het getal, is ook de punt in deze <span> neergezet, gelijk achter het getal.

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 blauw gekleurd. Alle niet-essentiële code is bruin. (In de inhoudsopgave staat alles in een gewone letter vanwege de leesbaarheid.)

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, downloadt dan de hele handel (ga terug naar het voorbeeld en kies daar voor downloaden). In de download zit 'n voorbeeld dat wel naadloos aansluit op de uitleg in de download.

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 Gereedschap → Snelheid, testen, gzip, comprimeren links naar sites vinden, waar je bestanden kunt comprimeren.

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

css voor alle vensters

/* tekst-122-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.

Algemene lay-out

Onder dit kopje staat de css die wordt gebruikt voor het uiterlijk van het voorbeeld. Omdat er ook counters voor kolommen en regels zijn, hoort deze css ook gedeeltelijk bij de essentiële css voor dit voorbeeld. Als je wilt tellen, hoeveel getallen in bijvoorbeeld de derde kolom zijn aangevinkt, moet je een vaste indeling in kolommen hebben. (Althans: met css is dat nodig. Met JavaScript zou je flexibeler kunnen zijn en het aantal kolommen en dergelijke bij weergave kunnen berekenen.)

Verder staat hier de css voor de melding die verschijnt, als er meer dan tien getallen zijn aangevinkt.

body

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

background: #ff9;

Achtergrondkleurtje.

color: black;

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

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

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

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

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

margin: 0; padding: 0;

Slim om te doen vanwege verschillen tussen browsers.

#opdracht

Het element met id="opdracht". Binnen deze <div> staan de teksten boven de getallen.

background: #eee;

Achtergrond lichtgrijs.

color: black;

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

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

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

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

box-sizing: border-box;

Hier gelijk onder wordt een breedte van 600 px opgegeven. Nog iets verder omlaag wordt aan alle kanten een border opgegeven. Normaal genomen worden marge en border bij de breedte opgeteld. Dat is hier wat onhandig, omdat de borders van de hieronder zittende div#wrapper, waarin de rest van de pagina zit, moeten aansluiten op de borders van deze <div>.

Door deze regel worden de borders niet bij de breedte opgeteld, waardoor div#tekst inclusief borders precies 600 px breed blijft. Omdat bij #wrapper hetzelfde wordt opgegeven voor div#wrapper, zijn beide nu precies 600 px breed en sluiten netjes op elkaar aan.

width: 600px;

Breedte.

max-width: 90%;

Maximumbreedte 90%.

Hier gelijk boven is een breedte van 600 px opgegeven. In browservensters smaller dan 600 px levert dat problemen op. Daarom wordt hier de breedte beperkt tot maximaal 90%.

Een breedte in procenten is normaal genomen ten opzichte van de ouder van het element. Dat is hier het blok-element <main>. Een blok-element wordt normaal genomen automatisch even breed als z'n ouder. De ouder van <main> is <body>, ook weer een blok-element. Ook <body> wordt daarom normaal genomen even breed als z'n ouder <html>. Omdat <html> het buitenste element is, wordt dit normaal genomen even breed als het venster van de browser.

Uiteindelijk wordt div#normaal hierdoor nooit breder dan 90% van de breedte van het browservenster.

text-align: center;

Tekst horizontaal centreren.

margin: 10px auto 0;

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

Boven een marge van 10 px, onderaan geen marge.

Links en rechts auto, wat hier hetzelfde betekent als evenveel. Hierdoor staat div#opdracht altijd horizontaal gecentreerd binnen z'n ouder <main>, ongeacht de breedte van <main>.

<main> is een blok-element en wordt daarom normaal genomen even breed als z'n ouder <body>, ook een blok-element. Ook <body> wordt daarom normaal genomen even breed als z'n ouder <html>. Omdat <html> het buitenste element is, wordt dit normaal genomen even breed als het venster van de browser. div#opdracht staat hierdoor altijd horizontaal gecentreerd binnen het venster, ongeacht de breedte van het venster.

Deze manier van horizontaal centreren van een blokelement werkt alleen, als het te centreren element een breedte heeft.

border: black solid 1px;

Zwart randje.

border-bottom: none;

Aan de onderkant geen randje.

Gelijk onder deze <div> staat div#wrapper, die bij #wrapper aan alle kanten een border krijgt. Als div#opdracht aan de onderkant ook een border zou hebben, zou tussen div#opdracht en div#wrapper een dubbele border staan.

#opdracht p:first-of-type

#opdracht: het element met id="opdracht".

p: alle <p>'s binnen #opdracht.

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

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

In normale mensentaal: elke eerste <p> binnen div#opdracht. Dit is de <p> met de bovenste regel tekst.

De selector :first-of-type (of :nth-of-type()) kan onverwachte bijwerkingen hebben. In dit geval zit er maar één serie <p>'s in div#opdracht. Maar als binnen div#opdracht bijvoorbeeld nog 'n geneste <div> zou zitten, waarin ook <p>'s zouden zitten, zou deze selector ook voor die <p>'s gelden. (Dit zou je in dit geval kunnen oplossen door het toevoegen van >: #opdracht > p:first-of-type. Nu geldt de selector alleen voor directe kinderen van div#opdracht.)

font-size: 1.1em;

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

margin: 0;

Standaard heeft een <p> een marge aan boven- en onderkant. Die is hier niet welkom.

#opdracht p:nth-of-type(2)

In deze <p> staat de tweede regel tekst boven de getallen. Deze selector werkt precies hetzelfde als die voor de eerste <p> bij #opdracht p:first-of-type, alleen is de selector :first-of-type vervangen door :nth-of-type(2), met tussen de haakjes het volgnummer van de <p>: de tweede <p>.

font-size: 0.6em;

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

margin: 0;

Standaard heeft een <p> een marge aan boven- en onderkant. Die is hier niet welkom.

#wrapper

Het element met id="wrapper". De <div> waar de aankruisvakjes, de getallen en de onder de getallen staande teksten zitten.

background: white;

Witte achtergrond.

color: black;

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

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

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

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

counter-reset: aangevinkt nog-te-doen 10 even oneven priem kolom-1 kolom-2 kolom-3 kolom-4 kolom-5 regel-1 regel-2 regel-3 regel-4 regel-5 regel-6 regel-7;

Met behulp van counters kunnen allerlei dingen worden geteld. Zo'n counter wordt aangemaakt met counter-reset. Als er meerdere counters zijn, zoals hier het geval is, worden die gewoon allemaal achter elkaar gezet. Standaard krijgt een counter de waarde 0, maar als je 'n ander getal achter de counter zet, wordt dat getal de beginwaarde.

Voor elk gegeven is een eigen counter nodig

aangevinkt: houdt het aantal aangevinkte getallen bij.

nog-te-doen: houdt bij, hoeveel er nog aangevinkt moeten worden. Dit is de enige counter die niet met 0 begint. Omdat deze counter terug moet tellen van 10 naar 0, begint deze met 10.

even en oneven: houden het aantal aangevinkte even en oneven getallen bij.

priem: houdt het aantal aangevinkte priemgetallen bij.

kolom-1 tot en met kolom-5: houden bij hoeveel getallen er in elke kolom zijn aangevinkt. Er zijn vijf kolommen met getallen, dus er zijn vijf counters.

regel-1 tot en met regel-7: houden bij, hoeveel getallen er in elke regel zijn aangevinkt. Er zijn zeven regels met getallen, dus er zijn zeven counters.

display: flex;

Hiermee wordt div#wrapper in een zogenaamde 'flex container' veranderd. Dit maakt het veel makkelijker om de directe kinderen van dit element, de 'flex items', netjes verdeeld binnen het venster van de browser neer te zetten.

De directe kinderen van div#wrapper zijn de 35 <input>'s, de 35 daarop volgende <label>'s en de 35 <span>'s die daar weer op volgen. Deze hebben alle div#wrapper als ouder en veranderen dus in flex items.

Oudere browsers ondersteunen flexbox niet en negeren daarom deze regel. Voor die oudere browser wordt verderop bij input en label float: left; gebruikt. Waarom dit wordt gedaan, is te vinden bij float: left;.

(In sommige van die oudere browsers is een verouderde versie van flexbox geïmplementeerd. Die bevat echter zoveel bugs, dat alleen gediplomeerde masochisten die gebruiken.)

flex-wrap: wrap;

Standaard worden directe kinderen van een flex container naast elkaar gezet. Als die kinderen te breed zijn, worden ze verkleind, of moet je horizontaal scrollen. In dit geval is het nog erger, want er passen stomweg geen 35 aankruisvakjes met bijbehorende getallen naast elkaar binnen div#wrapper (de <span>'s zijn leeg en nemen geen ruimte in). Verschillende browsers lossen dit op verschillende manieren op. Nou ja, oplossen...

Firefox bijvoorbeeld zet de aankruisvakjes en getallen zo dicht mogelijk op elkaar, zodat er zoveel mogelijk binnen div#wrapper passen. Helaas passen alleen 1 tot en met 17 binnen div#wrapper, dus de rest verdwijnt gewoon.

Google Chrome daarentegen laat de aankruisvakjes weg, zodat er 32 getallen naast elkaar passen. Dat zijn er nog steeds drie te weinig, want er zijn 35 getallen. Ook al zijn er geen aankruisvakjes te zien, je kunt de getallen nog steeds aanvinken.

Andere browsers zijn niet uitgeprobeerd, maar het zal duidelijk zijn dat alles-op-een-regel een gigantische puinhoop oplevert.

Met deze regel worden de kinderen, als er te weinig ruimte is, niet naast maar onder elkaar gezet. Bovendien wordt nu ook rekening gehouden met de breedte die bij input en label aan de flex items wordt opgegeven, waardoor deze netjes in kolommen komen te staan.

box-sizing: border-box;

Hier gelijk onder wordt een breedte van 600 px opgegeven. Nog iets verder omlaag wordt aan alle kanten een border opgegeven. Normaal genomen worden marge en border bij de breedte opgeteld. Dat is hier wat onhandig, omdat de borders van div#opdracht, waarin de tekst boven de getallen zit, moeten aansluiten op de borders van deze <div>.

Door deze regel worden de borders niet bij de breedte opgeteld, waardoor div#wrapper inclusief borders precies 600 px breed blijft. Omdat bij #opdracht hetzelfde wordt opgegeven voor div#opdracht, zijn beide nu precies 600 px breed en sluiten netjes op elkaar aan.

width: 600px;

Breedte.

max-width: 90%;

Maximumbreedte 90%.

Hier gelijk boven is een breedte van 600 px opgegeven. In browservensters smaller dan 600 px levert dat problemen op. Daarom wordt hier de breedte beperkt tot maximaal 90%.

Een breedte in procenten is normaal genomen ten opzichte van de ouder van het element. Dat is hier het blok-element <main>. Een blok-element wordt normaal genomen automatisch even breed als z'n ouder. De ouder van <main> is <body>, ook weer een blok-element. Ook <body> wordt daarom normaal genomen even breed als z'n ouder <html>. Omdat <html> het buitenste element is, wordt dit normaal genomen even breed als het venster van de browser.

Uiteindelijk wordt div#wrapper hierdoor nooit breder dan 90% van de breedte van het browservenster.

margin: 0 auto;

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

Boven en onder geen een marge.

Links en rechts auto, wat hier hetzelfde betekent als evenveel. Hierdoor staat div#wrapper altijd horizontaal gecentreerd binnen z'n ouder <main>, ongeacht de breedte van <main>.

<main> is een blok-element en wordt daarom normaal genomen even breed als z'n ouder <body>, ook een blok-element. Ook <body> wordt daarom normaal genomen even breed als z'n ouder <html>. Omdat <html> het buitenste element is, wordt dit normaal genomen even breed als het venster van de browser. div#wrapper staat hierdoor altijd horizontaal gecentreerd binnen het venster, ongeacht de breedte van het venster.

Deze manier van horizontaal centreren van een blokelement werkt alleen, als het te centreren element een breedte heeft.

border: black solid 1px;

Zwart randje.

input

Alle <input>'s. Dat zijn er 35: één aankruisvakje voor elk getal.

width: 5%; height: 2em;

Een <input> is een inline-element en daarom zouden breedte en hoogte er eigenlijk geen invloed op moeten hebben. Maar een <input> is een wat apart element: breedte en hoogte hebben er wel invloed op. Helaas handelen verschillende browsers dit op een verschillende manier af.

Afbeelding 3: verschil in weergave input

Sommige browsers vergroten het aankruisvakje zelf. Andere browsers laten het aankruisvakje even groot, maar zetten ruimte rondom het aankruisvakje. Op de afbeelding staat links Firefox op Linux, rechts Google Chrome op Linux. Om de grootte van de <input> aan te geven, heeft die even een rode outline gekregen.

In alle gevallen is het effect hetzelfde: de <input>'s nemen 5 procent van de breedte van de browser in beslag. Omdat de <label>'s bij label een breedte van 15% krijgen, passen er op elke regel vijf <input>'s met bijbehorende <label> naast elkaar.

Een breedte in procenten is normaal genomen ten opzichte van de ouder van het element. Dat is hier div#wrapper, die bij #wrapper een breedte van 600 px heeft gekregen, met een maximumbreedte van 90% van het venster van de browser.

De hoogte is één van de eigenschappen die wordt gebruikt om de <input> in alle browsers op ongeveer dezelfde hoogte te krijgen als het bijbehorende getal in de <label>.

(Omdat verschillende browsers <input>'s zo apart behandelen, zijn ze heel erg moeilijk met behulp van css goed op te maken. Hopelijk verandert dat ooit nog 'ns.)

float: left;

Sommige versies van Android browser, Opera Mini en UC browser op Android 4.1.2. en 4.4.2 kennen het bij #wrapper gebruikte display: flex; (onderdeel van flexbox) niet. Omdat <input> en <label> inline-elementen zijn, worden deze hoe dan ook op dezelfde regel gezet, totdat de regel vol is. Maar bij inline-elementen werken dingen als breedte niet.

Als je 'n inline-element float, verandert het in 'n soort blok-element, waardoor dingen als breedte wel zijn te gebruiken. Voor browsers die flexbox wel kennen, maakt dit geen verschil. Met display: flex; is div#wrapper in een zogenaamde 'flex container' veranderd, en binnen zo'n flex container werkt float niet. Browsers die flexbox kennen, negeren deze regel dus gewoon.

margin: 0;

Sommige browsers voegen een marge aan een <input> toe. Weg ermee.

Deze eigenschap is één van de eigenschappen die wordt gebruikt om de <input> in alle browsers op ongeveer dezelfde hoogte te krijgen als het bijbehorende getal in de <label>.

position: relative; top: 6px;

De <input>'s 6 px omlaag zetten. In tegenstelling tot iets als margin-top blijkt dit in alle browsers te werken.

Deze eigenschappen zijn twee van de eigenschappen die worden gebruikt om de <input> in alle browsers op ongeveer dezelfde hoogte te krijgen als het bijbehorende getal in de <label>.

input:nth-of-type(n + 6)

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

input {width: 5%; height: 2em; float: left; margin: 0; position: relative; top: 6px;}

input: alle <input>'s.

:nth-of-type: een zogenaamde pseudo-class. nth wil zeggen 'de zoveelste', of-type betekent 'van de soort'. Aangezien dit achter input staat, gaat het hier dus om één (of meer) <input>'s.

(n + 6): tussen de haakjes staat aangegeven, om welke <input> of <input>'s het gaat.

De 'n' is een soort teller, die steeds met 1 wordt verhoogd. Bij de eerste keer is de 'n' 0, bij de tweede keer 0 + 1 = 1, bij de derde keer 1 + 1 = 2, enzovoort.

De eerste keer is het resultaat 0 + 6 = 6. Oftewel: de zesde <input>.

De tweede keer is het resultaat 1 + 6 = 7. Oftewel: de zevende <input>.

De derde keer is het resultaat 2 + 6 = 8. Oftewel: de achtste <input>.

Bij de laatste berekening is het resultaat 29 + 6 = 35. Hoger is niet zinvol, want er zijn maar 35 <input>'s.

In dit geval gaat het om de zesde en hogere <input>. Dit zijn de <input>'s op de tweede en lagere rij.

Deze selector telt alleen de <input>'s die dezelfde ouder hebben. Alle 35 <input>'s hebben hier als ouder div#wrapper en tellen dus mee. Maar stel dat de eerste tien <input>'s in een <div> stonden, en de laatste 25 ook. In dat geval zou je twee series hebben: één van tien <input>'s, en één van 25 <input>'s.

position: static;

Bij input zijn de <input>'s met behulp van position: relative; en top: 6px; naar beneden gezet. Dat is echter alleen bij de vijf <input>'s in de eerste regel nodig. Door de positie te veranderen van relatief naar de standaardpositie statisch, werkt top niet meer.

Deze eigenschap is één van de eigenschappen die wordt gebruikt om de <input> in alle browsers op ongeveer dezelfde hoogte te krijgen als het bijbehorende getal in de <label>.

input:nth-of-type(5n + 6)

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

input {width: 5%; height: 2em; float: left; margin: 0; position: relative; top: 6px;}

input:nth-of-type(n + 6) {position: static;}

input: alle <input>'s.

:nth-of-type: een zogenaamde pseudo-class. nth wil zeggen 'de zoveelste', of-type betekent 'van de soort'. Aangezien dit achter input staat, gaat het hier dus om één (of meer) <input>'s.

(5n + 6): tussen de haakjes staat aangegeven, om welke <input> of <input>'s het gaat.

De getallen '5' en '6' zijn gewoon getallen, daar is verder weinig geheimzinnigs aan.

De 'n' is een soort teller, die steeds met 1 wordt verhoogd. Bij de eerste keer is de 'n' 0, bij de tweede keer 0 + 1 = 1, bij de derde keer 1 + 1 = 2, enzovoort.

5n betekent, net als in de wiskunde, 5 x de waarde van 'n'. De eerste keer is dat dus 5 x 0 = 0, de tweede keer 5 x 1 = 5, de derde keer 5 x 2 = 10, enzovoort. Deze berekening levert een reeks getallen op, beginnend met 0, die steeds 5 hoger worden: 0, 5, 10, 15, 20, ...

De eerste keer is het resultaat 5 x 0 = 0. Daarachter staat nog + 6. De eerste keer is de volledige berekening dus 5 x 0 + 6 = 6. Oftewel: de zesde <input>.

De tweede keer is het resultaat 5 x 1 = 5. Daarachter staat nog + 6. De tweede keer is de volledige berekening dus 5 x 1 + 6 = 11. Oftewel: de elfde <input>.

De derde keer is het resultaat 5 x 2 = 10. Daarachter staat nog + 6. De derde keer is de volledige berekening dus 5 x 2 + 6 = 16. Oftewel: de zestiende <div>.

Bij de laatste berekening is het resultaat 5 x 5 = 25. Daarachter staat nog + 6. De laatste keer is de volledige berekening dus 5 x 5 + 6 = 31. Hoger is niet zinvol, want bij de volgende berekening zou de uitkomst 5 x 6 + 6 = 36 zijn, en er zijn maar 35 <input>'s.

In dit geval gaat het om de zesde, elfde, zestiende, 21e, 26e en 31e <input>. Dit zijn de <input>'s in de eerste kolom, behalve die uit de eerste regel.

Deze selector telt alleen de <input>'s die dezelfde ouder hebben. Alle 35 <input>'s hebben hier als ouder div#wrapper en tellen dus mee. Maar stel dat de eerste tien <input>'s in een <div> stonden, en de laatste 25 ook. In dat geval zou je twee series hebben: één van tien <input>'s, en één van 25 <input>'s.

clear: both;

Sommige versies van Android browser, Opera Mini en UC browser op Android 4.1.2. en 4.4.2 kennen het bij #wrapper gebruikte display: flex; (onderdeel van flexbox) niet. Voor die browsers zijn de <inputs> bij input naar links gefloat, zodat breedte en dergelijke toch gebruikt kunnen worden bij <input>.

Alleen blijkt in enkele van die verouderde browsers dat die breedte bij <input> niet helemaal goed werkt. Er passen net geen vijf <input>'s met vijf <label>'s op één regel, of juist vijf <input>'s en zes <label>'s. Als er niet precies vijf <input>'s en <label>'s op één regel staan, kloppen de counters voor de aangevinkte regels en kolommen niet meer.

Daarom wordt met behulp van deze regel de zesde, elfde, enzovoort <input> hoe dan ook op een nieuwe regel gezet. Voor browsers die flexbox wel kennen, maakt dit geen verschil. Met display: flex; is div#wrapper in een zogenaamde 'flex container' veranderd, en binnen zo'n flex container werkt clear niet. Browsers die flexbox kennen, negeren deze regel dus gewoon.

label

Alle <label>'s. Dat zijn er 35, voor elke <input> één, met in elke <label> 'n getal.

box-sizing: border-box;

Bij input zijn de <input>'s 5% breed gemaakt. Hier gelijk onder worden de <label>'s 15% breed gemaakt. (Die breedte is ten opzichte van ouder div#wrapper.) Bij elkaar passen er precies vijf <input>'s en vijf <label>'s naast elkaar: 5 x 5% + 5 x 15% = 100%.

Hier iets onder krijgen de <label>'s echter een border rechts van 1 px breed en een padding links van 8 px breed. Die border en padding worden normaal genomen bij de breedte opgeteld, waardoor de breedte 100% + (5 x 1 px) + (5 x 8 px) = 100% + 45 px wordt. Dat past dus niet meer.

Met behulp van deze regel worden border en padding niet bij de breedte opgeteld, maar binnen de breedte gezet. Hierdoor passen er weer vijf <input>'s en vijf <label>'s op elke regel.

width: 15%;

Een <label> is een inline-element. Normaal genomen heeft een breedte daar geen invloed op. Maar deze <label>'s zijn directe kinderen van div#wrapper, die bij #wrapper met display: flex; is veranderd in een zogenaamde 'flex container'. En bij directe kinderen van een flex container, 'flex items', werkt een breedte wel.

Een breedte in procenten is normaal genomen ten opzichte van de ouder van het element. Dat is hier div#wrapper, die bij #wrapper een breedte van 600 px heeft gekregen, met een maximumbreedte van 90% van het venster van de browser.

height: 2.4em;

Hoogte. Als eenheid wordt de relatieve eenheid em genomen, zodat de hoogte mee verandert met de lettergrootte. Bij een absolute eenheid als px is dat niet het geval. Hierdoor blijven, bij een andere lettergrootte, de aankruisvakjes iets beter naast de bijbehorende getallen staan.

Deze eigenschap is één van de eigenschappen die wordt gebruikt om de <input> in alle browsers op ongeveer dezelfde hoogte te krijgen als het bijbehorende getal in de <label>.

float: left;

Sommige versies van Android browser, Opera Mini en UC browser op Android 4.1.2. en 4.4.2 kennen het bij #wrapper gebruikte display: flex; (onderdeel van flexbox) niet. Omdat <input> en <label> inline-elementen zijn, worden deze hoe dan ook op dezelfde regel gezet, totdat de regel vol is. Maar bij inline-elementen werken dingen als breedte niet.

Als je 'n inline-element float, verandert het in 'n soort blok-element, waardoor dingen als breedte wel zijn te gebruiken. Voor browsers die flexbox wel kennen, maakt dit geen verschil. Met display: flex; is div#wrapper in een zogenaamde 'flex container' veranderd, en binnen zo'n flex container werkt float niet. Browsers die flexbox kennen, negeren deze regel dus gewoon.

font-family: monospace;

Meestal nemen brede cijfers meer ruimte in dan smalle cijfers. Om de getallen netjes onder elkaar te krijgen, moeten ze echter even breed zijn. Daar zorgt deze regel voor. De lettersoort mag de browser zelf uitzoeken, want dat verschilt nogal per besturingssysteem en versie. Omdat het om alleen de cijfers bij de aankruisvakjes gaat, is dit ook niet zo belangrijk. Als ze maar even breed zijn.

border-right: black solid 1px;

Rechts zwart lijntje.

padding: 10px 0 29px 8px;

Aan de bovenkant 10 px padding, zodat de bovenste rij getallen netjes naast de bijbehorende aankruisvakjes komt te staan. Rechts geen padding. Onderaan 29 px padding voor iets meer afstand tussen de eerste en tweede regel.

De padding van 8 px links is alleen bedoeld voor de getallen 1 tot en met 9. Deze komen hierdoor op dezelfde plaats te staan als de eenheden van grotere getallen. Deze padding wordt voor de getallen van 10 en hoger verderop bij label:nth-of-type(n + 10) weer weggehaald.

Deze eigenschap is één van de eigenschappen die wordt gebruikt om de <input> in alle browsers op ongeveer dezelfde hoogte te krijgen als het bijbehorende getal in de <label>.

(Het is goed mogelijk dat de padding van 29 px aan de onderkant iets anders zou kunnen zijn, als je ook wat elders gebruikte marges en dergelijke aan zou passen. Dit werkt echter en het is echt 'n hels karwei om alle <input>'s en <label>'s op de juiste hoogte te krijgen. Maar als je je verveelt op de lange winteravonden, ligt hier een mooie bezigheidstherapie binnen handbereik.)

label:nth-of-type(5n)

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

label {background: white; color: black; box-sizing: border-box; width: 15%; height: 2.4em; float: left; font-family: monospace; border-right: black solid 1px; padding: 10px 0 29px 8px;}

label: alle <label>'s.

:nth-of-type: een zogenaamde pseudo-class. nth wil zeggen 'de zoveelste', of-type betekent 'van de soort'. Aangezien dit achter label staat, gaat het hier dus om één (of meer) <label>'s.

(5n): tussen de haakjes staat aangegeven, om welke <label> of <label>'s het gaat. Het getal '5' is een gewoon getal, daar is verder weinig geheimzinnigs aan.

De 'n' is een soort teller, die steeds met 1 wordt verhoogd. Bij de eerste keer is de 'n' 0, bij de tweede keer 0 + 1 = 1, bij de derde keer 1 + 1 = 2, enzovoort.

5n betekent, net als in de wiskunde, 5 x de waarde van 'n'. De eerste keer is dat dus 5 x 0 = 0, de tweede keer 5 x 1 = 5, de derde keer 5 x 2 = 10, enzovoort. Deze berekening levert een reeks getallen op, beginnend met 0, die steeds 5 hoger worden: 0, 5, 10, 15, 20, ...

De eerste keer is het resultaat 5 x 0 = 0. Aangezien er geen 'nulde' <label> is, selecteert dit geen enkel element.

De tweede keer is het resultaat 5 x 1 = 5. Oftewel: de vijfde <label>.

De derde keer is het resultaat 5 x 2 = 10. Oftewel: de tiende <label>.

Bij de laatste berekening is het resultaat 5 x 7 = 35. Hoger is niet zinvol, want bij de volgende berekening zou de uitkomst 5 x 8 = 40 zijn, en er zijn maar 35 <label>'s.

In dit geval gaat het om de vijfde, tiende, vijftiende, enzovoort <label>. Dit zijn de <label>'s in de laatste kolom.

Deze selector telt alleen de <label>'s die dezelfde ouder hebben. Alle 35 <label>'s hebben hier als ouder div#wrapper en tellen dus mee. Maar stel dat de eerste tien <label>'s in een <div> stonden, en de laatste 25 ook. In dat geval zou je twee series hebben: één van tien <label>'s, en één van 25 <label>'s.

In gewone mensentaal: de vijfde, tiende, vijftiende, enzovoort <label>. Dit zijn de <label>'s in de meest rechtse kolom.

border-right: none;

Bij label hebben alle <label>'s een border rechts gekregen. Maar div#wrapper heeft ook een border aan de rechterkant. De rechterborders van de <label>'s in de meest rechtse kolom zouden hierdoor tegen de border van div#wrapper komen te staan, waardoor de border daar twee keer zo dik zou zijn.

Door bij de <label>'s in de meest rechtse kolom de border rechts weg te halen, wordt dat voorkomen.

label:nth-of-type(n + 6)

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

label {background: white; color: black; box-sizing: border-box; width: 15%; height: 2.4em; float: left; font-family: monospace; border-right: black solid 1px; padding: 10px 0 29px 8px;}

label:nth-of-type(5n) {border-right: none;}

Deze selector selecteert de zesde en daarop volgende <label>'s. Dat zijn de <label>'s in de tweede rij en daaronder. Hoe deze selector precies werkt, is te vinden bij input:nth-of-type(n + 6). Het enige verschil is dat het hier niet om <input>'s, maar om <label>'s gaat.

line-height: 2em;

Regelhoogte. Als eenheid wordt de relatieve eenheid em gebruikt, omdat bij gebruik van een absolute eenheid zoals px de regelhoogte niet mee verandert met de lettergrootte.

Deze eigenschap is één van de eigenschappen die wordt gebruikt om de <input> in alle browsers op ongeveer dezelfde hoogte te krijgen als het bijbehorende getal in de <label>.

padding-top: 0;

Bij label hebben alle <label>'s een padding van 10 px aan de bovenkant gekregen. Deze padding is alleen in de bovenste rij nodig, daarom wordt hij hier voor de <label>'s in de tweede en lagere rij verwijderd.

Deze eigenschap is één van de eigenschappen die wordt gebruikt om de <input> in alle browsers op ongeveer dezelfde hoogte te krijgen als het bijbehorende getal in de <label>.

label:nth-of-type(n + 10)

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

label {background: white; color: black; box-sizing: border-box; width: 15%; height: 2.4em; float: left; font-family: monospace; border-right: black solid 1px; padding: 10px 0 29px 8px;}

label:nth-of-type(5n) {border-right: none;}

label:nth-of-type(n + 6) {line-height: 2em; padding-top: 0;}

Deze selector selecteert de tiende en daarop volgende <label>'s. Dat zijn de <label>'s met het getal tien en hoger. Hoe deze selector precies werkt, is te vinden bij label:nth-of-type(n + 6). Het enige verschil is dat het hier niet om <input>'s, maar om <label>'s gaat. Verder wordt er niet 6, maar 10 bij n opgeteld.

padding-left: 1px;

Bij label hebben alle <label>'s een padding links van 8 px gekregen. Dat is nodig bij de cijfers 1 tot en met 9 om die uit te lijnen met de eenheden in de getallen 10 en hoger.

Bij de getallen 10 en hoger is dat niet nodig, daarom wordt die padding hier weggehaald voor de tiende en hogere <label>, oftewel: voor het tiende en hogere getal.

input:focus

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

input {width: 5%; height: 2em; float: left; margin: 0; position: relative; top: 6px;}

input:nth-of-type(n + 6) {position: static;}

input:nth-of-type(5n + 6) {clear: both;}

Als een <input> focus heeft.

outline: none;

Soms gebruiken mensen niet de muis, maar de Tab-toets om links, knoppen, tekstvelden, en dergelijke af te gaan. Omdat ze de muis niet kunnen gebruiken, of omdat dat soms gewoon veel sneller werkt.

Het element dat wordt bezocht heeft 'focus'. Dat wil zeggen dat een link wordt gevolgd bij het indrukken van Enter, in een tekstveld kan tekst worden ingevoerd, een aankruisvakje kan worden aan- of uitgevinkt met de Spatiebalk, enzovoort.

Welk element focus heeft, wordt in alle browsers aangegeven door een kadertje om dat element te trekken. Dat kadertje moet je nooit zomaar weghalen, want dan weten gebruikers van het toetsenbord niet meer, waar ze zijn.

Dat kadertje is een gewone outline. In dit voorbeeld wordt die weggehaald, omdat het hier foeilelijk is. Bovendien is dat kadertje hier soms ook wat onduidelijk, omdat sommige browsers het niet duidelijk rondom het aankruisvakje neerzetten.

Gelijk hieronder wordt een duidelijker en beter passende indicatie voor het element met focus aangebracht.

input:focus + label

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

label {background: white; color: black; box-sizing: border-box; width: 15%; height: 2.4em; float: left; font-family: monospace; border-right: black solid 1px; padding: 10px 0 29px 8px;}

label:nth-of-type(5n) {border-right: none;}

label:nth-of-type(n + 6) {line-height: 2em; padding-top: 0;}

label:nth-of-type(n + 10) {padding-left: 1px;}

input:focus: Als een <input> focus heeft.

+: het element achter de + moet in de html direct volgen op het element voor de +. In dit geval gaat het om een <label> die gelijk op een <input> volgt.

label: alle <label>'s.

In gewone mensentaal: doe iets met de <label> die in de html gelijk op de <input> met focus volgt.

color: deeppink;

Voorgrondkleur veranderen naar het smaakvolle dieproze. Dit is ook de kleur van de tekst.

font-weight: bold;

Vette letter.

Als de gebruikte lettersoort een vette variant heeft, wordt die gebruikt. De meeste lettersoorten hebben wel een vette variant, soms zelfs meerdere. Een vette variant wordt speciaal ontworpen, het is iets anders dan 'alle lijntjes wat dikker maken'. Sommige letters kunnen er zelfs heel anders uitzien.

Als de lettersoort geen vette variant heeft, maakt de browser de letter vet. Dat wil zeggen dat de browser gewoon alle lijntjes wat dikker maakt. Dat kan mooi zijn, maar vaak is het foeilelijk. Het is heel iets anders dan een speciaal ontworpen font. Elke rechtgeaarde typograaf gruwt hiervan.

text-decoration: overline;

Streepje boven de tekst, in dit geval is dat alleen het getal in de <label>.

#tekst

Het element met id="tekst". In deze <div> staan de teksten onder de aankruisvakjes en getallen.

background: #eee;

Achtergrond lichtgrijs.

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.

clear: both;

Sommige versies van Android browser, Opera Mini en UC browser op Android 4.1.2. en 4.4.2 kennen het bij #wrapper gebruikte display: flex; (onderdeel van flexbox) niet. Voor deze browsers zijn de <input>'s en <label>'s bij input en label met float: left; naar links gefloat. Browsers die flexbox kennen, hebben die float: left; genegeerd.

In de hier genoemde oudere browsers is soms wat ruimte over, rechts van de <input>'s en <label>'s. Bij een bepaalde breedte van het browservenster past in die ruimte soms wat van de tekst, die onder de <input>'s en <label>'s moet staan. Deze regel zorgt ervoor dat de tekst altijd onder de <input>'s en <label>'s komt te taan.

Voor browsers die flexbox wel kennen, maakt deze regel niets uit, omdat die float hebben genegeerd.

border-top: black solid 1px;

Zwart randje aan de bovenkant.

padding: 0 3px;

Kleine afstand tussen tekst in en buitenkant van div#tekst.

position: relative;

Om nakomelingen van div#tekst te kunnen positioneren ten opzichte van deze <div>, moet de <div> zelf een positie hebben. Omdat voor top en dergelijke niets wordt opgegeven, heeft dit verder geen invloed op div#tekst zelf.

#tekst p

Alle <p>'s in het element met id="tekst". De <p>'s waar de gekleurde voortgangsbalk en de teksten onder de aankruisvakjes en getallen in zitten.

margin: 3px 0;

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

Een <p> heeft van zichzelf een vrij grote marge aan boven- en onderkant. Die wordt hier verkleind tot 3 px.

#kleur

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.

#tekst p {margin: 3px 0;}

Het element met id="kleur". De <p> waar de gekleurde voortgangsbalk in zit, alsmede de waarschuwing die verschijnt, als er meer dan tien getallen worden aangevinkt.

height: 15px;

Hoogte.

background: orange;

Achtergrond oranje.

Bij openen van de pagina is er nog niets aangevinkt, dus is de voortgangsbalk volledig oranje. Bij elk getal dat wordt aangevinkt, wordt de balk iets meer groen. Bij tien aangevinkte getallen is de balk volledig groen.

#kleur > span

Alle <span>'s binnen het element met id="kleur". Het teken > geeft aan, dat het alleen <span>'s mogen zijn die een direct kind van #kleur zijn. Onderstaande <span> is een direct kind van p#kleur

<p id="kleur"> <span></span> </p>

De middelste <span> hieronder is geen direct kind van p#kleur, omdat er een <span> tussen p#kleur en de binnenste <span> zit:

<p id="kleur"> <span> <span></span> </span> </p>

In dit voorbeeld is er maar één <span> die een direct kind van p#kleur is. Binnen deze <span> zit de waarschuwing die je krijgt, als je meer dan tien getallen aanvinkt. Binnen deze <span> zit nog een andere <span>, die wordt gebruikt om het aantal aangevinkte getallen te tonen. Maar deze selector geldt dus alleen voor de <span> met de waarschuwing, want alleen die is een direct kind van p#kleur.

background: yellow;

Gele achtergrond.

color: black;

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

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

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

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

display: none;

Normaal genomen wordt deze waarschuwing verborgen. Pas als er meer dan tien getallen zijn aangevinkt, wordt deze waarschuwing bij input:checked (...) ~ input:checked ~ #tekst #kleur span met display: inline; zichtbaar gemaakt.

font-size: 1.1em;

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

border: red solid 8px;

Lekker dikke knalrode border.

padding: 4px;

Kleine afstand tussen tekst in en rand rondom de <span>.

position: absolute;

Om de waarschuwing op de juiste plaats neer te kunnen zetten. Er wordt gepositioneerd ten opzichte van de eerste voorouder die zelf een positie heeft. Dat is hier div#tekst, die bij #tekst relatief is gepositioneerd.

Een <span> is van zichzelf een inline-element. Daardoor kunnen eigenschappen als padding en border niet goed worden gebruikt. Door de <span> absoluut te positioneren, verandert deze in een soort blok-element en kunnen deze eigenschappen wel worden gebruikt.

top: 0; right: 0; bottom: 0; left: 0;

Er wordt gepositioneerd ten opzichte van div#tekst, waarin de voortgangsbalk, de tekst en de counters onder de aankruisvakjes en getallen zitten. Door de waarschuwing precies even groot te maken als div#tekst, worden al die dingen verborgen onder de waarschuwing. De enige counter die je nog ziet, is de counter met het aantal aangevinkte getallen, die binnen de <span> met de waarschuwing zelf zit.

Dat afdekken is niet zozeer geniaal of zo, het is meer om tekortkomingen te verbergen. Er is dus hooguit sprake van geniaal bedrog, en of je daar nou trots op moet zijn...

Als er meer dan tien getallen worden aangevinkt, verdwijnt het totaal. Er zijn maar 306 uitkomsten aanwezig, en meer dan tien getallen is per definitie hoger dan de hoogst aanwezige uitkomst. Het aantal nog aan te vinken getallen verandert in een negatief getal. De voortgangsbalk blijft groen.

Je zou dit allemaal misschien wel kunnen oplossen, maar 'n beetje bedrog is veel simpeler: gewoon onder het tapijt vegen. Wat je niet ziet, is er niet.

z-index: 10;

De verklaring hiervoor is wat ingewikkeld. De simpele verklaring: een hogere z-index zorgt ervoor, dat het element altijd alles afdekt, wat eronder zit. Een z-index werkt alleen in bepaalde omstandigheden. Eén van die omstandigheden is een absolute positie. Die is iets hierboven gegeven, dus dat is geregeld.

Als je met deze uitleg tevreden bent, kun je hier stoppen met lezen over deze z-index.

Afbeelding 4: totaal is niet goed zichtbaar

Zonder deze iets hogere z-index zie je door de waarschuwing heen het totaal van de getallen verschijnen. (Als dat totaal niet hoger is dan 305. Als het totaal hoger is, wordt de <span> met de uitkomsten zo hoog neergezet, dat je deze niet meer ziet. En dan zie je dus ook geen totaal meer.) Op de afbeelding is rechts in het midden het totaal '152' te zien, door het woordje 'dan' heen.

In principe worden elementen die lager in de html staan, over eerdere elementen op het scherm gezet. Je kunt dat eventueel veranderen door een z-index te gebruiken: hoe hoger de z-index, hoe hoger het element (en de nakomelingen van dat element) op het scherm worden gezet. Ook over elementen die later in de html staan en die dus eigenlijk op het scherm bovenaan zouden moeten staan.

Omdat de <span> met de waarschuwing in de html boven de teksten en dergelijke staat, zou je verwachten dat die teksten boven de waarschuwing staan. Dat is hier echter niet zo.

Afbeelding 5
Afbeelding 5: zonder position bij de <span> met de waarschuwing is de eronder zittende tekst zichtbaar, dwars door de tekst van de waarschuwing heen,

Deze <span> is absoluut gepositioneerd. En elementen met een positie worden altijd boven elementen zonder positie op het scherm gezet. Omdat de teksten en dergelijke niet zijn gepositioneerd, wordt de waarschuwing toch boven de teksten en dergelijke neergezet. Ook al staan die teksten en dergelijke lager in de html. Ook zonder gebruik van een z-index gebeurt dit, gewoon omdat de <span> met de waarschuwing een absolute positie heeft.

Met één uitzondering. De <span> waarin alle 306 uitkomsten staan, wordt bij #optellen span span met behulp van transform: translateY(); op de juiste hoogte gezet, zodat de juiste uitkomst wordt vertoond. Een bijwerking van de eigenschap transform is dat, als deze een andere waarde dan 'none' heeft, ook elementen met deze eigenschap op het scherm over andere elementen worden gezet. Net als bij een element met een positie.

Eigenlijk is het volledige verhaal dat een aantal eigenschappen – waaronder position: absolute; en transform – een zogenaamde nieuwe 'stacking context' vormen. Dat betekent onder andere, dat ze op het scherm boven elementen zonder zo'n stacking context worden gezet. Ongeacht de volgorde waarin ze in de html staan.

Als beide elementen een stacking context vormen, geldt de volgorde in de html weer. En dat doet zich hier voor.

<span> met de waarschuwing: position: absolute;, dus een nieuwe stacking context.

teksten en dergelijke onder de aankruisvakjes: geen position of zoiets, dus geen nieuwe stacking context. De teksten en dergelijke verdwijnen dus onder de <span> met de waarschuwing. Op één uitzondering na:

de <span> met de totalen wordt met behulp van transform op de juiste hoogte gezet. Deze <span> vormt daardoor een nieuwe stacking context. En omdat deze <span> in de html na de <span> met de waarschuwing komt, zie je deze <span> door de waarschuwing heen.

Door aan de <span> met de waarschuwing een hogere z-index te geven, staat deze nu toch weer boven de <span> met het totaal.

Als je dit niet helemaal duidelijk is: heel veel websitebouwers hebben hier grote problemen mee. Vaak is simpel even uitproberen of 'n z-index werkt het makkelijkste. Het hele begrip stacking context lijkt sterk op het probleem met de tafel met één te korte of te lange poot. Als je gaat zagen, heb je voor je het weet 'n tafel zonder poten.

Op de pagina met links is onder CSS → Positioneren, z-index, display, boxmodel en marges meer over dit onderwerp te vinden.

#kleur span span::after

#kleur: het element met id="kleur". De <p> waarbinnen de gekleurde voortgangsbalk en de waarschuwing bij meer dan tien aangevinkt staan.

span: alle <span>'s binnen #kleur.

span: alle <span>'s die binnen de eerste <span> hierboven staan. Dat is er maar eentje: de <span> die wordt gebruikt om binnen de waarschuwing de counter met het aantal aangevinkte getallen weer te geven.

::after: met behulp van ::after wordt bij de <span> een pseudo-class gemaakt. Binnen dit pseudo-element wordt met behulp van content de counter voor het aantal aangevinkte getallen weergegeven.

In normale mensentaal: maak een pseudo-element aan bij de <span> die weer binnen een <span> binnen p#kleur staat.

De buitenste <span> is bij #kleur > span verborgen en wordt pas bij input:checked (...) ~ input:checked ~ #tekst #kleur span zichtbaar gemaakt met display: inline;, als meer dan tien getallen zijn aangevinkt. De binnenste <span> met het pseudo-element is een kind van de buitenste <span> en wordt daardoor ook alleen maar getoond, als meer dan tien getallen zijn aangevinkt.

content: counter(aangevinkt);

Zet met behulp van content de counter aangevinkt, de counter voor het aantal aangevinkte getallen, in het hierboven aangemaakte pseudo-element. De html voor deze <span> ziet er zo uit:

(...) En dan presteer je het om er <span></span> aan te vinken. (...)

Op het scherm verschijnt:

En dan presteer je het om er 11 aan te vinken.

(Of een ander getal natuurlijk, als er meer dan 11 zijn aangevinkt.) Deze zin is een stukje van de waarschuwing die verschijnt, als er meer dan tien getallen zijn aangevinkt. Omdat in de html voor en na <span></span> een spatie staat, staat er netjes een spatie tussen het getal en de tekst.

De css voor het weergeven van alle andere counters staat een heel eind verderop. Maar omdat deze counter binnen de <span> met de waarschuwing komt te staan, waarvoor de css iets hierboven bij #kleur > span is opgegeven, lijkt het logischer deze counter ook hier neer te zetten.

De stand van counter aangevinkt wordt pas verderop geregeld, maar dat maakt niets uit. De counter kan toch al hier worden gebruikt en geeft het juiste aantal aan.

#optellen

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.

#tekst p {margin: 3px 0;}

Het element met id="optellen". Dit is de <p>, waarin het totaal van de aangevinkte getallen staat: 'Als je alle gekozen getallen bij elkaar optelt, is het totaal ...'.

display: none;

Voor het weergeven van het totaal wordt gebruik gemaakt van zogenaamde css-variabelen (ook wel 'custom properties' genoemd). UC browser op Android en Windows 10 Mobile, Internet Explorer 11 en Opera Mini op Android ondersteunen dit niet. In deze browsers kan daarom geen totaal worden getoond.

Daarom wordt de hele <p> met de regel voor het totaal standaard verborgen. Alleen het totaal aankondigen en dit vervolgens niet tonen, lijkt wat zinloos.

Later wordt met behulp van @supports (--w-1: 1) gekeken, of een browser css-variabelen ondersteunt. Als dat zo is, wordt deze <p> zichtbaar gemaakt en wordt het totaal getoond.

Variabelen initialiseren en verwerken

In dit deel worden alle css-variabelen geïnitialiseerd (aangemaakt). De variabelen worden gebruikt om het totaal van de aangevinkte getallen te berekenen. Het bijwerken van de variabelen om die berekening te kunnen maken is ook hier ondergebracht.

#tekst

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

#tekst {background: #eee; color: black; clear: both; border-top: black solid 1px; padding: 0 3px; position: relative;}

Het element met id="tekst". In deze <div> staan de teksten onder de aankruisvakjes en getallen.

Voor div#tekst is al eerder css opgegeven, en de hieronder staande regel had ook daar kunnen staan. Maar omdat het 'n nogal lange regel is met de nodige tekst, is het hier neergezet. Hierdoor staat gelijk ook de meeste css met betrekking tot de variabelen op één plaats bij elkaar.

--w-1: 0; --w-2: 0; --w-3: 0; --w-4: 0; --w-5: 0; --w-6: 0; --w-7: 0; --w-8: 0; --w-9: 0; --w-10: 0; --w-11: 0; --w-12: 0; --w-13: 0; --w-14: 0; --w-15: 0; --w-16: 0; --w-17: 0; --w-18: 0; --w-19: 0; --w-20: 0; --w-21: 0; --w-22: 0; --w-23: 0; --w-24: 0; --w-25: 0; --w-26: 0; --w-27: 0; --w-28: 0; --w-29: 0; --w-30: 0; --w-31: 0; --w-32: 0; --w-33: 0; --w-34: 0; --w-35: 0;

Als deze regel net zo zou zijn opgemaakt als de rest van de css, zouden dit 35 aparte regels onder elkaar zijn:

--w-1: 0; --w-2: 0;

tot en met

--w-35: 0;

Het goed opmaken van de css is belangrijk, maar je kunt ook overdrijven. In dit geval heeft het weinig nut 35 van die vrijwel identieke korte regels onder elkaar te zetten. Daarom zijn ze alle 35 achter elkaar gezet. Voor de browser maakt het niets uit, want die negeert hoe dan ook nieuwe regels.

Er worden hier 35 css-variabelen aangemaakt ('geïnitialiseerd') met de namen 'w-1' tot en met 'w-35'. De twee koppeltekens voor de naam geven aan dat het hier niet om een gewone eigenschap gaat, maar om css-variabelen. De naam van de variabele mag je zelf bedenken, als er maar twee koppeltekens voor staan.

Een variabele is een soort portemonnee: je kunt er van alles in stoppen. Een woord, een getal, een zin, van alles. In dit geval wordt het getal 0 in elke variabele gestopt.

Er zijn 35 variabelen, één voor elk aankruisvakje. Zodra een bepaald aankruisvakje is aangevinkt, wordt de 0 veranderd in de waarde van het getal bij het aankruisvakje.

--w-1, de variabele die bij het eerste aankruisvakje met het getal 1 hoort, heeft hier de waarde 0 gekregen. Zodra input#i-1, het eerste aankruisvakje, is aangevinkt, wordt de waarde 0 veranderd in de waarde 1. Dat gebeurt iets hieronder bij #i-1:checked ~ #tekst met de regel --w-1: 1;.

Op dezelfde manier wordt de waarde van alle andere variabelen aangepast, als het bijbehorende aankruisvakje is aangevinkt. Als bijvoorbeeld het twintigste aankruisvakje (input#i-20) wordt aangevinkt, krijgt --w-20 de waarde 20.

Als een aankruisvakje niet wordt aangevinkt, blijft de waarde van de bijbehorende variabele gewoon 0.

Bij #optellen span span worden alle variabelen met behulp van calc() bij elkaar opgeteld en is het totaal van de aangevinkte getallen bekend. Hier kan dan verder mee aan de slag worden gegaan.

calc() kan alleen met getallen uit de voeten. Daarom moet echt met css-variabelen worden gewerkt, want alleen daarin kun je getallen opslaan. Je kunt weliswaar in een data-attribuut (een eigengemaakt attribuut dat met 'data-' begint) ook een getal opslaan, maar dat is geen echt getal, maar een string.

(Het verschil tussen een string en een getal is met behulp van huisnummers duidelijk te maken. Als ik op nummer 100 woon, wil dat niet zeggen dat ik 50 voordeuren (50 'getallen') van nummer 50 woon. Er kunnen huizen ontbreken, oneven en even huisnummers kunnen worden gescheiden, toevoegsels als 'A' kunnen worden gebruikt, enzovoort, enzovoort. De 'berekening' 100 huisnummers min 50 huisnummers, dus 50 huizen afstand is onzinnig. '100' is hier wel een getal, maar om te rekenen heb je er niets aan.

Bij getallen is 100 min 50 gewoon altijd 50. Althans: in het tientallig talstelsel, maar laat ik nou niet vervelend worden.)

Vaak worden css-variabelen bovenaan de css aangemaakt bij het element <html> of bij :root. (:root is in een html-pagina altijd hetzelfde als <html>. :root is het buitenste element, en dat is bij html per definitie het element <html>.)

Als je de variabelen allemaal bovenaan de css zet, staan ze overzichtelijk bij elkaar. Variabelen zijn ook alleen maar te gebruiken binnen het element (en de nakomelingen daarvan), waarbij ze zijn geïnitialiseerd. Als je variabelen aanmaakt bij <html>, kun je ze op de hele pagina gebruiken.

In dit geval worden de variabelen alleen binnen div#tekst gebruikt, vandaar dat ze niet bij <html> bovenin de css staan. Maar als je variabelen bijvoorbeeld voor kleuren gebruikt, is het meestal wel beter om ze bovenin te zetten bij <html>:

html {--tekstkleur: black; --achtergrondkleur: white;}

Nu kun je overal op de pagina --tekstkleur en --achtergrondkleur gebruiken:

p {color: var(--tekstkleur); background-color: var(--achtergrondkleur);}

Als je nu de kleur wilt veranderen, hoef je alleen de waarden van de variabelen bij html te veranderen. Overal waar die variabelen verder zijn gebruikt, verandert de kleur dan mee.

Maar in dit geval is het voldoende om de reikwijdte van de variabelen te beperken tot div#tekst.

Browsers die css-variabelen niet ondersteunen, negeren deze regel gewoon.

En nog maar 'ns: als je ziet, hoeveel css er bij slechts 35 getallen al nodig is, dan zal duidelijk zijn dat JavaScript geschikter is voor berekeningen zoals optellingen.

#i-1:checked ~ #tekst

#i-1: als het element met id="i-1" is aangevinkt. Dit is de eerste <input type="checkbox">, die voor de <label> met het getal 1 staat.

~: 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#i-1 voor de ~ en #tekst na de ~ hebben beide als ouder div#wrapper.

#tekst: de <div> waar de teksten onder de aankruisvakjes en getallen in zitten.

In gewone mensentaal: doe iets met div#tekst als het eerste aankruisvakje id aangevinkt.

--w-1: 1;

Bij --w-1: 0; is aan de css-variabele '--w-1' de waarde 0 gegeven. Als input#i-1, het eerste aankruisvakje, is aangevinkt, wordt die waarde hier veranderd in 1.

Bij #optellen span span worden alle 35 variabelen bij elkaar opgeteld om het totaal van de aangevinkte getallen te berekenen. Als het eerste aankruisvakje niet is aangevinkt, is de waarde van --w-1 nog steeds 0. Als het eerste aankruisvakje wel is aangevinkt, is de waarde van --w-1 1. Alleen als het eerste aankruisvakje is aangevinkt, heeft --w-1 dus invloed op het totaal van de aangevinkte getallen.

Browsers die css-variabelen niet ondersteunen, negeren deze regel gewoon.

#i-2:checked ~ #tekst
{--w-2: 2;}

tot en met

#i-35:checked ~ #tekst {--w-35: 35;}

Het verhaal voor deze 34 variabelen is precies hetzelfde als dat hierboven bij #i-1:checked ~ #tekst voor de eerste variabele. Alleen gaat het hier om het tweede tot en met 35e aankruisvakje en de tweede tot en met 35e css-variabele.

Alleen van de aangevinkte aankruisvakjes verandert de waarde van de variabele van 0 naar de waarde van het bij de <input> horende getal.

Counters en dergelijke aanpassen

In totaal zijn er zeventien counters, die elk een ander gegeven bijhouden. Deze counters zijn al eerder bij #wrapper aangemaakt met counter-reset. Standaard hebben ze allemaal als beginwaarde 0 gekregen, behalve counter nog-te-doen. Deze is met 10 begonnen, omdat deze naar omlaag aftelt.

input:nth-of-type(odd):checked

input: alle <input>'s.

:nth-of-type(odd): het element met een bepaald volgnummer. In dit geval wordt geen volgnummer gebruikt, maar een speciaal sleutelwoord: odd, 'oneven'. Deze selector selecteert alleen het eerste, derde, vijfde, enzovoort element van een bepaalde soort.

Omdat voor :nth-of-type(odd) een input staat, worden alleen <input>'s geteld.

:checked: als de <input> is aangevinkt.

In normale mensentaal: elke oneven <input> die is aangevinkt. Dat is elk aankruisvakje bij een oneven getal. Dat er achter input twee pseudo-classes staan (:nth-of-type() en :checked) maakt niets uit: de <input> moet nu gewoon aan beide eisen voldoen.

De selector of :nth-of-type() kan onverwachte bijwerkingen hebben. Alleen elementen met dezelfde ouder worden door deze selector meegeteld. In dit geval hebben alle <input>'s dezelfde ouder: div#wrapper en is het de bedoeling dat alle oneven <input>'s worden geselecteerd (als ze zijn aangevinkt).

Maar als er elders op de pagina nog een serie <input>'s aanwezig zou zijn met een andere ouder, zouden ook die <input>'s door deze selector worden geselecteerd. (Dit zou je in dit geval kunnen oplossen door het toevoegen van #wrapper >: #wrapper > input:nth-of-type(odd):checked. Nu geldt de selector alleen voor directe kinderen van div#wrapper.)

counter-increment: aangevinkt nog-te-doen -1 oneven;

De counters aangevinkt en oneven worden met 1 verhoogd. Dat is de standaardwaarde waarmee een counter verandert, als niets anders is opgegeven. Achter counter nog-te-doen staat –1, daarom wordt deze counter met -1 verlaagd.

Hiermee zijn de even getallen afgehandeld. Voor elk aangevinkt even getal zijn de counters aangevinkt en even met 1 verhoogd, en nog-te-doen is met 1 verlaagd.

In principe is dit een vrij efficiënte manier van tellen: met één regel css worden alle even getallen afgehandeld. Hier zijn er maar 35 getallen, maar deze regel werkt even goed bij duizend getallen.

Alleen zijn er nog meer counters bij te werken. Onder andere de counter voor priemgetallen moet nog worden bijgewerkt. En daar doet zich een probleem voor. Alle counters bij een bepaalde selector moeten gelijktijdig worden bijgewerkt. Stel dat je de volgende twee regels hebt:

p {counter-increment: een; color: black;} p {counter-increment: twee; color: red;}

Omdat beide selectors p evenveel specificiteit, evenveel 'gewicht', hebben (ze bevatten beide slechts één element p), 'wint' de laatste regel.

In de tweede regel is als kleur rood opgegeven, dus de kleur wordt rood. De zwarte kleur uit de eerste regel wordt genegeerd.

Precies hetzelfde geldt voor counter-increment: de counter-increment uit de eerste regel wordt genegeerd en alleen counter twee wordt verhoogd.

Tot nu toe zijn de counters aangevinkt, nog-te-doen en even afgewerkt, maar nog niet die voor priemgetallen. Die counter kan niet in deze regel worden opgenomen, want dan zou de counter voor priemgetallen bij élk even getal worden verhoogd.

Een tweede regel alleen voor priemgetallen kan ook niet, want die zou 'winnen' van de regel hierboven, en dan zou alleen de counter voor priemgetallen worden verhoogd.

Maar wat wel kan: een hele nieuwe regel voor even getallen die ook een priemgetal zijn. Die nieuwe regel 'wint' gewoon van de hierboven staande regel, waardoor de hierboven staande regel de counters niet meer verhoogt. Dus counter even voor even getallen wordt dan toch maar één keer verhoogd. Hetzelfde geldt voor aangevinkt en nog-te-doen: ook die worden alleen door de latere regel bijgewerkt.

Je voelt hem al komen: die tweede regel is inderdaad aanwezig en staat iets hieronder bij input:nth-of-type(odd)[data-priem]:checked. Die regel is precies hetzelfde als bovenstaande, behalve dat die ook de counter voor priemgetallen bijwerkt.

input:nth-of-type(even):checked
{counter-increment: aangevinkt nog-te-doen -1 even;}

Deze regel is precies hetzelfde als die iets hierboven bij input:nth-of-type(odd):checked, alleen is het sleutelwoord odd hier vervangen door het sleutelwoord even. Deze selector is bedoeld voor alle even <input>'s, de <input>'s bij de even getallen.

Ook voor deze regel staat iets hieronder een soortgelijke regel voor als het om een priemgetal gaat: input:nth-of-type(even)[data-priem]:checked.

input:nth-of-type(odd)[data-priem]:checked

Deze selector is grotendeels hetzelfde als een eerdere: input:nth-of-type(odd):checked. Met die eerdere selector werden de counters aangevinkt, nog-te-doen en oneven bijgewerkt. Maar de counter priem voor priemgetallen is nog niet bijgewerkt. Dat gebeurt hier.

Ook deze selector selecteert oneven <input>'s die zijn aangevinkt, net als de eerdere. En omdat deze selector lager in de css staat (en door de toevoeging [data-priem] iets meer specificiteit, iets meer 'gewicht', heeft) overrulet hij de eerdere selector. Daardoor worden de counters bij de eerdere selector niet bijgewerkt, als de <input>'s onder de selector hier vallen.

Er is hier een extra voorwaarde bijgekomen: [data-priem]. Tussen de teksthaken staat de naam van het attribuut dat aanwezig moet zijn bij <input>: 'data-priem'. Een attribuut dat met 'data-' begint is een eigengemaakt attribuut. Het mag elke naam hebben, als het maar met 'data-' begint. Deze selector selecteert alleen <input>'s die met dit attribuut, zoals de derde <input>:

<input id="i-3" data-priem type="checkbox">

Deze <input> is oneven én heeft het attribuut 'data-priem'. Als deze <input> ook nog is aangevinkt (:checked), valt hij onder deze selector.

Dat er achter input drie pseudo-classes staan (:nth-of-type(), [data-priem] en :checked) maakt niets uit: de <input> moet nu gewoon aan beide eisen voldoen.

(Vaak wordt aan een data-attribuut ook nog een waarde gegeven, bijvoorbeeld data-priem="priem". Met een iets ingewikkelder selector kun je dan selecteren op het aanwezig zijn van de waarde 'priem' en dergelijke. Maar dat is hier niet nodig, omdat alleen priemgetallen het attribuut 'data-priem' hebben.)

counter-increment: aangevinkt nog-te-doen -1 priem oneven;

Dezelfde counters als bij de eerdere selector input:nth-of-type(odd):checked worden verhoogd: aangevinkt en oneven. Ook nog-te-doen wordt met 1 verlaagd.

Maar hier is er een nieuwe counter bijgekomen: priem. Ook deze counter wordt met 1 verhoogd.

Deze selector is minder efficiënt dan sommige andere, omdat je bij alle <input>'s die bij een priemgetal horen in de html handmatig het data-attribuut data-priem moet toevoegen. Dat gaat nog wel bij 35 getallen, maar bij 35.000 wordt het mogelijk 'n tikkeltje problematisch. Ook hier is, zeker bij grotere aantallen getallen, JavaScript geschikter, omdat je daar de priemgetallen met behulp van een berekening eruit kunt pikken.

input:nth-of-type(even)[data-priem]:checked
{counter-increment: aangevinkt nog-te-doen -1 priem even;}

Het verhaal is hier vrijwel hetzelfde als gelijk hierboven bij input:nth-of-type(odd)[data-priem]:checked, alleen gaat het hier niet om oneven (odd), maar om even <input>'s. Hier worden de tellers voor de even priemgetallen bijgewerkt.

(Dit is natuurlijk 'n beetje rare regel. Er is maar één even priemgetal: 2. In dit geval is het natuurlijk veel simpeler om voor de tweede <input> een eigen regel te schrijven. Maar het ging er juist om dit soort mogelijkheden te laten zien, dus vandaar.)

input:nth-of-type(5n + 1):checked + label

input: alle <input>'s.

:nth-of-type: een zogenaamde pseudo-class. nth wil zeggen 'de zoveelste', of-type betekent 'van de soort'. Aangezien dit achter input staat, gaat het hier dus om één (of meer) <input>'s.

(5n + 1): tussen de haakjes staat aangegeven, om welke <input> of <input>'s het gaat.

De getallen '5' en '1' zijn gewoon getallen, daar is verder weinig geheimzinnigs aan.

De 'n' is een soort teller, die steeds met 1 wordt verhoogd. Bij de eerste keer is de 'n' 0, bij de tweede keer 0 + 1 = 1, bij de derde keer 1 + 1 = 2, enzovoort.

5n betekent, net als in de wiskunde, 5 x de waarde van 'n'. De eerste keer is dat dus 5 x 0 = 0, de tweede keer 5 x 1 = 5, de derde keer 5 x 2 = 10, enzovoort. Deze berekening levert een reeks getallen op, beginnend met 0, die steeds 5 hoger worden: 0, 5, 10, 15, 20, ...

De eerste keer is het resultaat 5 x 0 = 0. Daarachter staat nog + 1. De eerste keer is de volledige berekening dus 5 x 0 + 1 = 1. Oftewel: de eerste <input>.

De tweede keer is het resultaat 5 x 1 = 5. Daarachter staat nog + 1. De tweede keer is de volledige berekening dus 5 x 1 + 1 = 6. Oftewel: de zesde <input>.

De derde keer is het resultaat 5 x 2 = 10. Daarachter staat nog + 1. De derde keer is de volledige berekening dus 5 x 2 + 1 = 11. Oftewel: de elfde <input>.

Bij de laatste berekening is het resultaat 5 x 6 = 30. Daarachter staat nog + 1. De laatste keer is de volledige berekening dus 5 x 6 + 1 = 31. Hoger is niet zinvol, want de volgende uitkomst zou 36 zijn, er zijn maar 35 <input>'s.

In dit geval gaat het om de zesde, elfde, zestiende, enzovoort <input>, dit zijn de <input>'s in de eerste kolom.

Deze selector telt alleen de <input>'s die dezelfde ouder hebben. Alle 35 <input>'s hebben hier als ouder div#wrapper en tellen dus mee. Maar stel dat de eerste tien <input>'s in een <div> stonden, en de laatste 25 ook. In dat geval zou je twee series hebben: één van tien <input>'s, en één van 25 <input>'s.

:checked: de <input> moet aangevinkt zijn.

+: het element achter de + moet in de html direct volgen op het element voor de +. In dit geval gaat het om een <label> die gelijk op een <input> volgt.

label: alle <label>'s.

In normale mensentaal: doe iets met de <label> die gelijk op de zesde, de elfde, de zestiende, enzovoort <input> volgt, als die <input> is aangevinkt. Dat er achter input twee pseudo-classes staan (:nth-of-type() en :checked) maakt niets uit: de <input> moet nu gewoon aan beide eisen voldoen.

counter-increment: kolom-1;

Counter kolom-1 houdt bij hoeveel getallen in de eerste kolom zijn aangevinkt.

Om deze counter alleen te verhogen als een getal in de eerste kolom is aangevinkt, moet een selector worden gevonden die op een of andere manier samenhangt met die eerste kolom. Dat is het geval bij elke zesde <input>: die staat in de eerste kolom.

De selector input:nth-of-type(5n + 1):checked (zonder + label) lijkt daarvoor ook voldoende te zijn, maar dat levert problemen op.

Eerder is de selector input:nth-of-type(odd):checked gebruikt om de counters aangevinkt, nog-te-doen en even bij te werken. Die selector zou niet meer werken, omdat hij wordt overruled door deze latere selector. Daardoor zouden de counters aangevinkt, nog-te-doen en even niet meer worden bijgewerkt.

De eerder gebruikte selector input:nth-of-type(odd):checked ziet er weliswaar anders uit dan de hier gebruikte selector input:nth-of-type(5n + 1):checked, maar dat maakt niets uit. Beide selectors hebben evenveel specificiteit, evenveel 'gewicht', en dan 'wint' de laatste selector.

(Voor de specificiteit zijn hier alleen input, nth-of-type() en :checked van belang. Dat er bij de een odd en bij de ander 5n + 1 staat tussen de haakjes staat, heeft geen invloed op de specificiteit.)

En er zijn nog andere selectors die bij gebruik van alleen input:nth-of-type(5n + 1):checked niet meer zouden werken. Ook input:nth-of-type(5n + 1):checked zelf zou niet altijd werken, omdat er ook nog selectors zijn die meer specificiteit, meer 'gewicht', hebben dan deze selector. En ook al staan die eerder in de css, bij meer specificiteit gaan die toch voor.

Kortom: een grote chaos van elkaar de tent uitvechtende selectors. De kandidaatstelling van de PVV voor de gemeenteraadsverkiezingen is er niets bij.

Door aan de selector hier + label toe te voegen krijg je een totaal andere selector, waardoor ook de eerdere selector gewoon blijft werken. Nu worden alle counters wel netjes bijgewerkt.

Ook deze counters zijn redelijk efficiënt, omdat je bij vijf kolommen met vijf regels kunt volstaan. Of er nou 35 of 3500 getallen zijn. (De andere vier regels voor de tweede, derde, vierde en vijfde kolom staan gelijk hieronder.)

input:nth-of-type(5n + 2):checked + label
{counter-increment: kolom-2;} input:nth-of-type(5n + 3):checked + label {counter-increment: kolom-3;} input:nth-of-type(5n + 4):checked + label {counter-increment: kolom-4;} input:nth-of-type(5n):checked + label {counter-increment: kolom-5;}

Deze regels werken de counters voor de aangevinkte getallen in de tweede, derde, vierde en vijfde kolom bij. Ze werken precies hetzelfde als die voor de eerste kolom bij input:nth-of-type(5n + 1):checked + label.

Het gaat hier alleen om een andere counter die wordt bijgewerkt. Bovendien is de berekening iets anders: voor de tweede, derde en vierde kolom wordt er 2, 3 of 4 bij 5n opgeteld in plaats van 1.

Bij de laatste regel hierboven voor de vijfde kolom wordt er niets bij 5n opgeteld. Dat levert als resultaat 5 x 0 = 0, 5 x 1 = 5, 5 x 2 = 10, 5 x 3 = 15, enzovoort: de <input>'s in de vijfde kolom.

input:nth-of-type(-n + 5):checked + label + span

input: alle <input>'s.

:nth-of-type: een zogenaamde pseudo-class. nth wil zeggen 'de zoveelste', of-type betekent 'van de soort'. Aangezien dit achter input staat, gaat het hier dus om één (of meer) <input>'s.

(-n + 5): tussen de haakjes staat aangegeven, om welke <input> of <input>'s het gaat.

De 'n' is 'n soort teller die met 0 begint en steeds 1 hoger wordt. Bij deze selector staat geen getal voor de 'n', alleen een minteken. Als er geen getal voor de 'n' staat, staat er eigenlijk toch 'n getal: de 1. Maar die wordt weggelaten, omdat '1n' moet worden gelezen als '1 x n', en dan is de 1 een beetje overbodig.

Hier staat echter een minteken voor de 'n', en dan kan het helpen om de 1 er wel even bij te denken: -1n: Wat je dus moet lezen als -1 x n.

Bij de eerste ronde is 'n' 0. De berekening wordt dan -1 x 0 = 0.

Bij de tweede ronde is 'n' 1. De berekening wordt dan -1 x 1 = -1.

Bij de derde ronde is 'n' 2. De berekening wordt dan -1 x 2 = -2.

Enzovoort, de uitkomst is steeds 1 lager. Hoewel 'n' steeds 1 hoger wordt, wordt de uitkomst door het minteken steeds 1 lager.

Bij deze uitkomst moet 5 worden opgeteld, want er staat -n + 5 tussen de haakjes.

Bij de eerste ronde staat er -0 x 0 + 5 = 0 + 5 = 5, oftewel: de vijfde <input>.

Bij de tweede ronde staat er -1 x 1 + 5 = -1 + 5 = 4, oftewel: de vierde <input>

Bij de derde ronde staat er -1 x 2 + 5 = -2 + 5 = 3, oftewel: de derde <input>.

Bij de vierde ronde staat er -1 x 3 + 5 = -3 + 5 = 2, oftewel: de vierde <input>.

Bij de vijfde ronde staat er -1 x 4 + 5 = -4 + 5 = 1, oftewel: de eerste <input>.

Verdere berekeningen zijn niet zinvol, want die komen op 0 of lager uit, en er is geen nulde of lagere <input>.

Deze pseudo-class selecteert dus de eerste tot en met vijfde <input>.

Deze selector telt alleen de <input>'s die dezelfde ouder hebben. Alle 35 <input>'s hebben hier als ouder div#wrapper en tellen dus mee. Maar stel dat de eerste tien <input>'s in een <div> stonden, en de laatste 25 ook. In dat geval zou je twee series hebben: één van tien <input>'s, en één van 25 <input>'s.

:checked: de <input> moet aangevinkt zijn.

+: het element achter de + moet in de html direct volgen op het element voor de +. In dit geval gaat het om een <label> die gelijk op een <input> volgt.

label: alle <label>'s.

+: het element achter de + moet in de html direct volgen op het element voor de +. In dit geval gaat het om een <span> die gelijk op een <span> volgt.

span: alle <span>'s.

In normale mensentaal: doe iets met de <span> die gelijk op een <label> volgt die weer gelijk op een <input> volgt, maar alleen bij de eerste tot en met vijfde <input>, en alleen als die <input> is aangevinkt.

Dat er achter input twee pseudo-classes staan (:nth-of-type() en :checked) maakt niets uit: de <input> moet nu gewoon aan beide eisen voldoen.

Deze selector regelt het bijhouden van de counter regel-1, de counter voor het aantal aangevinkte getallen in de eerste regel.

counter-increment: regel-1;

Om deze counter alleen te verhogen als een getal in de eerste regel is aangevinkt, moet een selector worden gevonden die op een of andere manier samenhangt met die eerste kolom. Dat is het geval bij de eerste tot en met de vijfde <input>: die staan in de eerste regel.

De selector input:nth-of-type(-n + 5):checked (zonder + label + span) lijkt daarvoor ook voldoende te zijn, maar dat levert problemen op.

Eerder is de selector input:nth-of-type(odd):checked gebruikt om de counters aangevinkt, nog-te-doen en oneven bij te werken. Die selector zou niet meer werken, omdat hij wordt overruled door deze latere selector. Daardoor zouden de counters aangevinkt, nog-te-doen en even niet meer worden bijgewerkt.

Het eerder gebruikte input:nth-of-type(odd):checked ziet er weliswaar anders uit dan input:nth-of-type(-n + 5):checked, maar dat maakt niets uit. Beide selectors hebben evenveel specificiteit, evenveel 'gewicht', en dan 'wint' de laatste selector.

(Voor de specificiteit zijn hier alleen input, :nth-of-type() en :checked van belang. Dat er bij de een odd en bij de ander -n + 5 tussen de haakjes staat, heeft geen invloed op de specificiteit.)

Om dezelfde reden levert ook de selector input:nth-of-type(-n + 5) + label problemen op, want een selector met evenveel specificiteit is al gebruikt voor de kolommen, zoals voor de eerste kolom input:nth-of-type(5n + 1):checked + label. Bij de eerste tot en met de vijfde <input>, de <input>'s uit de eerste regel, zouden hierdoor de counters voor de kolommen niet meer werken.

Door aan de selector hier + span toe te voegen krijg je een totaal andere selector, en nu worden alle counters wel netjes bijgewerkt.

Om dit te doen kunnen doen is in de html achter elke <label> een lege <span> gezet. De enige functie van die <span>: het mogelijk maken van deze selector.

Het op deze manier tellen hoeveel getallen er in elke rij zijn aangepast, is niet erg efficiënt. Elke regel heeft een eigen selector nodig. In dit voorbeeld zijn zeven regels aanwezig, hieronder staan dan ook nog zes andere regels voor de andere zes regels. Bij zeven regels gaat dit nog wel, bij zevenhonderd regels wordt het wat begrotelijk. Ook hier geldt weer: JavaScript is hier geschikter voor, want daarmee kun je échte berekeningen uitvoeren, terwijl css alleen simpele selecties en dergelijke kan maken.

input:nth-of-type(n + 6):nth-of-type(-n + 10):checked + label + span

Achter de input van deze selector staan drie pseudo-classes: nth-of-type(n + 6), nth-of-type(-n + 10) en de simpeler :checked. Dat betekent alleen maar dat de <input> aan alle drie de onderdelen van de selector moet voldoen. Je kunt ze dus alle drie apart bekijken, waarmee het opeens een stuk beter te behappen is.

input: alle <input>'s.

:nth-of-type(n + 6): dit werkt precies hetzelfde als de nth-of-type(n + 6) bij input:nth-of-type(n + 6). De uitleg is daar te vinden. Dit selecteert de zesde en hogere <input>. Deze eerste pseudo-class stelt dus als voorwaarde dat het de zesde of hogere <input>, tot en met de 35e, is.

:nth-of-type(-n + 10): tussen de haakjes staat aangegeven, om welke <input> of <input>'s het gaat. De 'n' is 'n soort teller die met 0 begint en steeds 1 hoger wordt. Bij deze selector staat geen getal voor de 'n', alleen een minteken. Als er geen getal voor de 'n' staat, staat er eigenlijk toch 'n getal: de 1. Maar die wordt weggelaten, omdat '1n' moet worden gelezen als '1 x n', en dan is de 1 een beetje overbodig.

Hier staat echter een minteken voor de 'n', en dan kan het helpen om de 1 er wel even bij te denken: -1n: Wat je dus moet lezen als -1 x n.

Bij de eerste ronde is 'n' 0. De berekening wordt dan -1 x 0 = 0.

Bij de tweede ronde is 'n' 1. De berekening wordt dan -1 x 1 = -1.

Bij de derde ronde is 'n' 2. De berekening wordt dan -1 x 2 = -2.

Enzovoort, de uitkomst is steeds 1 lager. Hoewel 'n' steeds 1 hoger wordt, wordt de uitkomst door het minteken steeds 1 lager.

Bij deze uitkomst moet 10 worden opgeteld, want er staat -n + 10 tussen de haakjes.

Bij de eerste ronde staat er -1 x 0 + 10 = 0 + 10 = 10, oftewel: de tiende <input>.

Bij de tweede ronde staat er -1 x 1 + 10 = -1 + 10 = 9, oftewel: de negende <input>

Bij de derde ronde staat er -1 x 2 + 10 = -2 + 10 = 8, oftewel: de achtste <input>.

Bij de laatste ronde staat er -1 x 9 + 10 = -9 + 10 = 1, oftewel: de eerste <input>.

Verdere berekeningen zijn niet zinvol, want die komen op 0 of lager uit, en er is geen nulde of lagere <input>.

Deze pseudo-class selecteert dus de eerste tot en met tiende <input>.

:nth-of-type(n + 6):nth-of-type(-n + 10): beide pseudo-classes achter elkaar.

De <input>'s moeten binnen beide pseudo-classes vallen.

De eerste pseudo-class levert de zesde tot en met de 35e <input> op.

De tweede pseudo-class levert de eerste tot en met de tiende <input> op.

Alleen de zesde tot en met de tiende <input> zitten binnen beide pseudo-classes, dus de css van deze selector geldt alleen voor de zesde tot en met de tiende <input>. En dat zijn, niet geheel toevallig, de <input>'s die op de tweede regel staan.

:checked: er staat echter nog een derde pseudo-class: de <input> moet ook nog zijn aangevinkt.

+: het element achter de + moet in de html direct volgen op het element voor de +. In dit geval gaat het om een <label> die gelijk op een <input> volgt.

label: alle <label>'s.

+: het element achter de + moet in de html direct volgen op het element voor de +. In dit geval gaat het om een <span> die gelijk op een <span> volgt.

span: alle <span>'s.

In normale mensentaal: doe iets met de <span> die gelijk op een <label> volgt die weer gelijk op een <input> volgt, maar alleen bij de zesde tot en met tiende <input>, en alleen als die <input> is aangevinkt.

Dat er achter input drie pseudo-classes staan (twee keer :nth-of-type() en :checked) maakt niets uit: de <input> moet nu gewoon aan alle drie de eisen voldoen.

Deze selector regelt het bijhouden van de counter regel-2, de counter voor het aantal aangevinkte getallen in de tweede regel.

counter-increment: regel-2;

Verhoog counter regel-2, de counter voor de tweede regel.

Dit werkt precies hetzelfde als de counter voor de eerste regel bij counter-increment: regel-1;. Met één klein verschil: omdat in de selector hier twee keer de pseudo-class :nth-of-type() voorkomt, heeft deze selector een nog hogere specificiteit, nog meer 'gewicht', en zou deze selector nog veel meer problemen veroorzaken, als + label + span aan het eind zou worden weggelaten.

input:nth-of-type(n + 11):nth-of-type(-n + 15):checked + label + span
{counter-increment: regel-3;} input:nth-of-type(n + 16):nth-of-type(-n + 20):checked + label + span {counter-increment: regel-4;} input:nth-of-type(n + 21):nth-of-type(-n + 25):checked + label + span {counter-increment: regel-5;} input:nth-of-type(n + 26):nth-of-type(-n + 30):checked + label + span {counter-increment: regel-6;} input:nth-of-type(n + 31):nth-of-type(-n + 35):checked + label + span {counter-increment: regel-7;}

Deze regels regelen de counters voor de derde tot en met zevende (en laatste) regel. Ze werken hetzelfde als de regel voor de counter voor de tweede kolom bij input:nth-of-type(n + 6):nth-of-type(-n + 10):checked + label + span. Alleen het getal dat bij 'n' wordt opgeteld is anders, waardoor je andere uitkomsten krijgt en de <input>'s op de derde tot en met zevende regel worden geselecteerd.

Counters en dergelijke weergeven

In dit deel wordt de weergave van de gekleurde voortgangsbalk, de waarschuwing en de counters op het scherm geregeld. Het bijwerken van de counters naar de juiste waarde is eerder al helemaal afgehandeld. Nu hoeven ze alleen nog maar te worden getoond.

input:checked ~ #tekst #kleur

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.

#tekst p {margin: 3px 0;}

#kleur {height: 15px; background: orange;}

input:checked: als 'n <input> is aangevinkt.

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

De enige voorwaarde is verder dat het voor en het na de ~ staande element dezelfde ouder hebben. Dat is hier het geval: input voor de ~ en #tekst na de ~ hebben beide als ouder div#wrapper.

(Dit is ook de reden dat #tekst in de selector aanwezig is. input:checked ~ #kleur zou niet werken, want input en #kleur hebben niet dezelfde ouder.)

#tekst: het element met id = "tekst".

#kleur: het element met id="kleur".

In normale mensentaal: doe iets met p#kleur in div#tekst als 'n eerdere <input> is aangevinkt. In deze <p> zitten de gekleurde voortgangsbalk en de waarschuwing die verschijnt, als meer dan tien getallen zijn aangevinkt.

Omdat in de selector een ~ is gebruikt, maakt het niets uit welke <input> is aangevinkt. Elke <input> staat in de html voor div#tekst, of het nou de eerste of de laatste <input> is.

background: linear-gradient(to right, green 5%, orange 15%); Afbeelding 6: voortgangsbalk als 1 getal is aangevinkt

Bij openen van de pagina is de voortgangsbalk volledig oranje. Bij elke <input> die wordt aangevinkt, wordt de balk iets groener. Op de afbeelding is de balk te zien, als één <input> is aangevinkt.

Je kunt een gradiënt zelf uitvogelen, maar vaak is het makkelijker om gebruik te maken van een gradiënt-editor, zoals die op colorzilla.com. Deze levert ook gelijk de oudere vormen van de code, zodat je je daar zelf niet in hoeft te verdiepen. Je kunt zelfs code voor oudere versies van Internet Explorer laten maken. (Nachtmerrie-code die gebruik maakt van een eigen techniek van Microsoft: filters.)

(Overigens is het wel raadzaam die code op te schonen, omdat er wel heel erg veel code voor heel erg oude versies van verschillende browsers in staat.)

In dit geval is geen gradiënt-editor gebruikt, want deze gradiënt is heel eenvoudig.

linear-gradient valt in een aantal delen uiteen:

to right: de kant waar de gradiënt naartoe gaat. Omdat alleen rechts is opgegeven, loopt de gradiënt horizontaal van links naar rechts.

Na de richting staan twee keer twee waarden, gescheiden door een komma. De eerste waarde is steeds de kleur, de tweede de plaats waar die kleur staat.

green 5%: groen op 5% vanaf de linkerkant van de <p> neerzetten.

orange 15%: oranje op 15% vanaf de linkerkant van de <p>'s neerzetten.

Er zijn slechts twee plaatsen opgegeven die een bepaalde kleur moeten krijgen: 5% en 15% vanaf links. Tot 5% van links is de gradiënt helemaal groen, vanaf 15% van links is de gradiënt helemaal oranje.

Tussen 5% en 15% verandert groen geleidelijk in oranje, dat regelt de browser verder. Dit is een vrij simpele gradiënt, maar je kunt ook tig kleuren op tig plaatsen aangeven. En ook nog in allerlei richtingen.

Deze verkleuring werkt niet in Android browser, Opera Mini en UC browser op Android 4.1.4 en 4.4.2, omdat die gebruik maken van een oudere syntax van linear-gradient. In deze browsers blijft de voortgangsbalk oranje, om dan in één keer naar volledig groen te verspringen, als er tien <input>'s zijn aangevinkt. Meer hierover is te vinden bij Bekende problemen (en oplossingen).

input:checked ~ input:checked ~ #tekst #kleur

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.

#tekst p {margin: 3px 0;}

#kleur {height: 15px; background: orange;}

input:checked ~ #tekst #kleur {background: linear-gradient(to right, green 5%, orange 15%);}

Deze selector werkt precies hetzelfde als die iets hierboven bij input:checked ~ #tekst #kleur, alleen staat hier twee keer input:checked ~: er moeten twee <input>'s zijn aangevinkt. Omdat tussen de twee <input>'s weer een ~ is gebruikt, maakt het niets uit, welke twee <input>'s zijn aangevinkt. Elke <input> volgt in de <html> altijd ergens op een andere <input>, en elke <input> heeft als ouder div#wrapper.

background: linear-gradient(to right, green 15%, orange 25%);

Ook de gradiënt werkt hetzelfde als die iets hierboven bij background: linear-gradient(to right, green 5%, orange 15%);, alleen loopt groen nu door tot 15% en begint oranje op 25%.

input:checked ~ input:checked ~ input:checked ~ #tekst #kleur
{background: linear-gradient(to right, green 25%, orange 35%);} input:checked ~ input:checked ~ input:checked ~ input:checked ~ #tekst #kleur {background: linear-gradient(to right, green 35%, orange 45%);} input:checked ~ input:checked ~ input:checked ~ input:checked ~ input:checked ~ #tekst #kleur {background: linear-gradient(to right, green 45%, orange 55%);} input:checked ~ input:checked ~ input:checked ~ input:checked ~ input:checked ~ input:checked ~ #tekst #kleur {background: linear-gradient(to right, green 55%, orange 65%);} input:checked ~ input:checked ~ input:checked ~ input:checked ~ input:checked ~ input:checked ~ input:checked ~ #tekst #kleur {background: linear-gradient(to right, green 65%, orange 75%);} input:checked ~ input:checked ~ input:checked ~ input:checked ~ input:checked ~ input:checked ~ input:checked ~ input:checked ~ #tekst #kleur {background: linear-gradient(to right, green 75%, orange 85%);} input:checked ~ input:checked ~ input:checked ~ input:checked ~ input:checked ~ input:checked ~ input:checked ~ input:checked ~ input:checked ~ #tekst #kleur {background: linear-gradient(to right, green 85%, orange 95%);}

Al deze regels werken precies hetzelfde als die iets hierboven bij input:checked ~ input:checked ~ #tekst #kleur, maar steeds met één keer meer input:checked ~. De eerste regel hierboven is voor drie aangevinkte <input>'s, de tweede voor vier, enzovoort. Verder verandert het percentage groen en oranje in de gradiënt.

Het zal duidelijk zijn, waarom dit een niet erg intelligente manier is om een voortgangsbalk te maken. Bij maximaal tien aan te vinken getallen gaat het nog enigszins. Maar bij een voortgangsbalk voor bijvoorbeeld honderd getallen is het raadzaam vast een plaatsje te reserveren in een rustoord met gepantserde ramen. Het rustoord heb je vast nodig na het intypen van honderd steeds langere regels, en de gepantserde ramen zijn nodig om de woedende bezoekers van je site buiten de deur te houden. Het berekenen van gradiënts vreet energie, dus 'n site met zo'n voortgangsbalk voor honderd getallen zuigt 'n mobieltje leeg (en is mogelijk ook nog 'ns liederlijk traag).

Dit is leuk als voorbeeld van wat er kan, en met tien getallen is het ook nog wel bruikbaar, maar ook voor dit soort dingen is JavaScript veel geschikter. Daarmee kun je het aantal aangevinkte <input>'s daadwerkelijk tellen, in plaats van de domme opsomming die hier is gebruikt.

input:checked ~ input:checked ~ input:checked ~ input:checked ~ input:checked ~ input:checked ~ input:checked ~ input:checked ~ input:checked ~ input:checked ~ #tekst #kleur

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.

#tekst p {margin: 3px 0;}

#kleur {height: 15px; background: orange;}

input:checked ~ #tekst #kleur {background: linear-gradient(to right, green 5%, orange 15%);}

input:checked ~ input:checked ~ #tekst #kleur {background: linear-gradient(to right, green 15%, orange 25%);}

input:checked ~ input:checked ~ input:checked ~ #tekst #kleur {background: linear-gradient(to right, green 25%, orange 35%);}

input:checked ~ input:checked ~ input:checked ~ input:checked ~#tekst #kleur {background: linear-gradient(to right, green 35%, orange 45%);}

input:checked ~ input:checked ~ input:checked ~ input:checked ~input:checked ~#tekst #kleur {background: linear-gradient(to right, green 45%, orange 55%);}

input:checked ~ input:checked ~ input:checked ~ input:checked ~input:checked ~ input:checked ~ #tekst #kleur {background: linear-gradient(to right, green 55%, orange 65%);}

input:checked ~ input:checked ~ input:checked ~ input:checked ~input:checked ~ input:checked ~ input:checked ~ #tekst #kleur {background: linear-gradient(to right, green 65%, orange 75%);}

input:checked ~ input:checked ~ input:checked ~ input:checked ~input:checked ~ input:checked ~ input:checked ~ input:checked ~ #tekst #kleur {background: linear-gradient(to right, green 75%, orange 85%);}

input:checked ~ input:checked ~ input:checked ~ input:checked ~input:checked ~ input:checked ~ input:checked ~ input:checked ~ input:checked ~ #tekst #kleur {background: linear-gradient(to right, green 85%, orange 95%);}

Mocht je je nog afvragen, waarom dit geen erg intelligente voortgangsbalk is, dan is die vraag hopelijk beantwoord, als je het oerwoud aan eerder voor dit element opgegeven css hierboven ziet.

Het is niet zo dat al deze regels worden toegepast, maar de browser moet bij het opbouwen van de pagina wel vaststellen, wélke regel toegepast gaat worden bij welk element. En bij gebruik van een pseudo-element als :checked moet dat tussentijds worden aangepast, omdat het aantal aangepaste <input>'s wijzigt.

Ook deze selector werkt weer hetzelfde als die bij input:checked ~ input:checked ~ #tekst #kleur, maar nu is er tien keer input:checked ~ aanwezig in de selector: als er tien <input>'s zijn aangevinkt.

background: green;

Groene achtergrond. Omdat nu tien <input>'s, het maximum, zijn aangevinkt, kan de achtergrond volledig groen worden.

input:checked ~ input:checked ~ input:checked ~ input:checked ~ input:checked ~ input:checked ~ input:checked ~ input:checked ~ input:checked ~ input:checked ~ input:checked ~ #tekst #kleur span

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

#kleur > span {background: yellow; color: black; display: none; font-size: 1.1em; border: red solid 8px; padding: 4px; position: absolute; top: 0; right: 0; bottom: 0; left: 0; z-index: 10;}

Ook deze selector werkt grotendeels weer hetzelfde als die bij iets hierboven bij input:checked ~ input:checked ~ #tekst #kleur. Alleen staat hier elf keer input:checked ~ achter elkaar: er moeten elf <input>'s zijn aangevinkt.

Aan het eind van de selector is hier nog span toegevoegd. Deze selector selecteert dus niet div#kleur, maar de daarin zittende <span>'s. Dat zijn er twee: de <span> waarin de waarschuwing zit dat er meer dan tien getallen zijn aangevinkt, en de weer in die <span> zittende <span> die wordt gebruikt om de counter met het aantal aangevinkte <input>'s weer te geven.

display: inline;

Het uiterlijk van de waarschuwing is eerder al opgegeven bij #kleur > span . Daar is deze <span>, en daarmee de erin zittende waarschuwing, ook verborgen met display: none;. Hier hoeft de <span> alleen nog maar zichtbaar te worden gemaakt.

Omdat display: none; is gebruikt, lezen ook schermlezers de waarschuwing alleen voor, als meer dan tien <input>'s zijn aangevinkt.

In de buitenste <span> met de waarschuwing zit nog een lege <span>, die wordt gebruikt om bij #kleur span span::after met behulp van een pseudo-element en content de counter met het aantal aangevinkte getallen weer te geven. Op deze <span> heeft deze regel geen enkele invloed, want een <span> is van zichzelf al een inline-element.

#aangevinkt::after

#aangevinkt: het element met id="aangevinkt". De <span> die wordt gebruikt om het totaal aantal aangevinkte <input>'s weer te geven.

::after: met behulp van ::after wordt bij de <span> een pseudo-element gemaakt.

Binnen dit pseudo-element wordt met behulp van content de counter voor het aantal aangevinkte getallen weergegeven.

In normale mensentaal: maak een pseudo-element aan bij span#aangevinkt.

content: counter(aangevinkt);

Zet met behulp van content de counter aangevinkt, de counter voor het aantal aangevinkte getallen, in het hierboven aangemaakte pseudo-element. De html voor de <p>, waar span#aangevinkt in zit, ziet er zo uit:

<p id="uitslag">Je hebt er tot nu toe <span id="aangevinkt"></span> aangevinkt: <span id="oneven"></span> oneven, <span id="even"></span> even en <span id="priem"></span> Nog <span id="nog-te-doen"></span> te doen.</p>

Op het scherm verschijnt:

Je hebt er tot nu toe 3 aangevinkt: 2 oneven, 1 even en 2 priemgetallen. Nog 7 te doen.

Het getal '3' in de zin hierboven hoort bij counter aangevinkt. (De andere getallen horen bij andere counters, die hieronder aan de beurt komen.) Er zijn kennelijk drie <input>'s aangevinkt. (Kennelijk? Stiekemerd. Dat heb je zélf gedaan.)

Omdat in de html voor en na <span id="aangevinkt"></span> een spatie staat, staat er netjes een spatie tussen het getal en de tekst.

Counter aangevinkt wordt ook in de waarschuwing gebruikt, maar dat maakt niets uit. Je kunt dezelfde counter gewoon op meerdere plaatsen gebruiken.

#oneven::after
{content: counter(oneven); #even::after {content: counter(even);

Met deze twee regels worden counter oneven, de counter voor de oneven getallen, en counter even, de counter voor de even getallen, op het scherm gezet.

Dit werkt precies hetzelfde als hierboven bij aangevinkt::after voor counter aangevinkt, alleen wordt counter oneven in een pseudo-element achter span#oneven, en counter even in een pseudo-element achter span#even gezet.

#priem::after

Dit werkt precies hetzelfde als hierboven bij aangevinkt::after voor counter aangevinkt, alleen wordt counter priem in een pseudo-element achter span#priem gezet.

content: counter(priem) " priemgetallen.";

Zet met behulp van content de counter priem, de counter voor het aantal aangevinkte priemgetallen, in het hierboven aangemaakte pseudo-element.

Hier staat echter niet alleen counter(priem) binnen content. Erachter staat nog tekst tussen twee aanhalingstekens: " priemgetallen.". Tekst die binnen content tussen aanhalingstekens staat, wordt precies zo op het scherm gezet, inclusief de spatie voor het woord 'priemgetallen'.

Bij opening van de pagina zijn er 0 priemgetallen aangevinkt. Het woord 'priemgetallen' moet daarom meervoud zijn. 'Je hebt er tot nu toe 3 aangevinkt, 2 oneven, 1 even en 0 priemgetal.' ziet er niet uit.

Je had dit natuurlijk kunnen omzeilen door een iets andere zinsbouw, bijvoorbeeld: 'Je hebt er tot nu toe 3 aangevinkt. oneven: 2; even: 1, priemgetallen: 0.' Dan kun je zowel 'priemgetal' als 'priemgetallen' altijd gebruiken. Maar het gaat hier om 'n voorbeeld, dus dan hoeft het niet beslist op de simpelste manier.

(Beweer nou nog maar 'ns met droge ogen dat Nederlands een eenvoudige en logische taal is. Meervoud bij 0, enkelvoud bij 1, meervoud bij 2 en meer priemgetallen, maar in sommige situaties mag je bij 0 meervoud en bij meer priemgetallen enkelvoud gebruiken.)

Daarom wordt bij deze counter ook het woord 'priemgetallen' met behulp van content op het scherm gezet. Met behulp van datzelfde content kan het dan bij één aangevinkt priemgetal in 'priemgetal' worden veranderd, en bij meerdere aangevinkte priemgetallen weer in 'priemgetallen'.

De spatie voor 'priemgetallen' zorgt voor ruimte tussen counter priem en het woord 'priemgetallen'. De punt achter 'priemgetallen' moet ook via content worden weergegeven, want anders sluit de punt óf bij 'priemgetal', of bij 'priemgetallen' niet goed op het woord aan.

input[data-priem]:checked ~ #tekst #priem::after

input: alle <input>'s.

[data-priem]: Tussen de teksthaken staat de naam van het attribuut dat aanwezig moet zijn bij <input>: 'data-priem'. Een attribuut dat met 'data-' begint is een eigengemaakt attribuut. Het mag elke naam hebben, als het maar met 'data-' begint. Deze selector selecteert alleen <input>'s met dit attribuut, zoals de derde <input>:

<input id="i-3" data-priem type="checkbox">

Deze <input> heeft het attribuut 'data-priem'. Omdat alleen <input>'s bij priemgetallen dit attribuut hebben, selecteert deze selector alleen <input>'s bij priemgetallen.

(Vaak wordt aan een data-attribuut ook nog een waarde gegeven, bijvoorbeeld data-priem="priem". Met een iets ingewikkelder selector kun je dan selecteren op het aanwezig zijn van de waarde 'priem' en dergelijke. Maar dat is hier niet nodig, omdat alleen priemgetallen het attribuut 'data-priem' hebben.)

:checked: als de <input> is aangevinkt.

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

De enige voorwaarde is verder dat het voor en het na de ~ staande element dezelfde ouder hebben. Dat is hier het geval: input voor de ~ en #tekst na de ~ hebben beide als ouder div#wrapper.

(Dit is ook de reden dat #tekst in de selector aanwezig is. input[data-priem]:checked ~ #priem zou niet werken, want input en #priem hebben niet dezelfde ouder.)

#tekst: het element met id = "tekst".

#priem: het element met id="priem".

In normale mensentaal: doe iets met span#priem in #div#tekst, als een voor div#tekst zittende <input> met het attribuut 'data-priem' is aangevinkt. Omdat in de selector ~ is gebruikt, maakt het niet uit, welke <input> is aangevinkt. Elke <input> staat in de html voor div#tekst, dus deze selector selecteert ze allemaal.

Dat er achter input twee pseudo-classes staan ([data-priem] en :checked) maakt niets uit: de <input> moet nu gewoon aan beide eisen voldoen.

content: counter(priem) " priemgetal."

Bij opening van de pagina staat er op het scherm '0 even en 0 priemgetallen.' Zodra er één priemgetal is geselecteerd, moet dit enkelvoud worden: '0 even en 1 priemgetal.' Een uitgebreider verhaal staat iets hierboven bij #priem::after.

input[data-priem]:checked ~ input[data-priem]:checked ~ #tekst #priem::after
{content: counter(priem) " priemgetallen."}

Dit werkt precies hetzelfde als de selector gelijk hierboven input[data-priem]:checked ~ #tekst #priem::after, alleen staat hier twee keer input[data-priem]:checked ~.

Zodra er twee <input>'s bij een priemgetal zijn aangevinkt, moet 'priemgetal' weer worden veranderd in 'priemgetallen', en daar zorgt deze regel voor.

Omdat in de selector het teken ~ is gebruikt, maakt het niet uit welke priemgetallen zijn aangevinkt, want alle <input>'s hebben dezelfde ouder div#wrapper.

#nog-te-doen::after
{content: counter(nog-te-doen);}

Met deze regel wordt counter nog-te-doen, de counter voor het aantal nog aan te vinken getallen, op het scherm gezet.

Dit werkt precies hetzelfde als hierboven bij aangevinkt::after voor counter aangevinkt, alleen wordt counter nog-te-doen in een pseudo-element achter span#nog-te-doen gezet.

#optellen span span

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.

#totaal span {display: inline-block; line-height: 1rem; margin-top: 2px;}

Alle <span>'s binnen een andere <span>, die weer binnen het element met id="optellen" zit. Het gaat hier om de binnenste <span> binnen p#optellen:

<p id="optellen" aria-hidden="true">Als je alle gekozen getallen bij elkaar optelt, is het totaal <span id="totaal"> <span>0.   (...) 305.</span> </span> </p>

Binnen deze <span> staan alle 306 mogelijke uitkomsten van het totaal van tien aangevinkte getallen. Meer over deze <span> is te vinden bij <span>0.&nbsp;&nbsp; ...

De hele <p>, waar deze <span> in staat, is standaard verborgen. Pas als bij @supports (--w-1: 1) is vastgesteld dat de browser css-variabelen ondersteunt, wordt de <p> zichtbaar. Omdat deze <span> in die <p> zit, geldt dat ook voor deze <span>.

transform: translateY(calc((var(--w-1) + var(--w-2) + var(--w-3) + var(--w-4) + var(--w-5) + var(--w-6) + var(--w-7) + var(--w-8) + var(--w-9) + var(--w-10) + var(--w-11) + var(--w-12) + var(--w-13) + var(--w-14) + var(--w-15) + var(--w-16) + var(--w-17) + var(--w-18) + var(--w-19) + var(--w-20) + var(--w-21) + var(--w-22) + var(--w-23) + var(--w-24) + var(--w-25) + var(--w-26) + var(--w-27) + var(--w-28) + var(--w-29) + var(--w-30) + var(--w-31) + var(--w-32) + var(--w-33) + var(--w-34) + var(--w-35)) * -1rem));

Zie hier het primitieve optelsysteem voor het totaal in z'n volle glorie. En bij 350 getallen zou dit dus nog tien keer zo veel zijn. Met JavaScript zou je simpelweg de aangevinkte <input>'s optellen, zonder dat je ze allemaal bij naam hoeft te noemen. Ook als het er tienduizend zouden zijn. Waarna je de uitkomst uiterst simpel als getal weer zou kunnen geven, waardoor ook schermlezers het gewoon zouden kunnen voorlezen.

Maar goed, dit is geen JavaScript, maar css.

Met var() wordt aangegeven dat tussen de haakjes een css-variabele staat. De waarde van die variabele kan veranderen. Bij #tekst is aan alle variabelen, --w-1 tot en met --w-35 de waarde 0 gegeven. Als er geen enkele <input> is aangevinkt, staat hier:

transform: translateY(calc(0 + 0 + (nog 31 keer '+ 0') + 0 + 0) * -1rem);

Er worden 35 variabelen bij elkaar opgeteld. Optellen kan met css met behulp van de eigenschap calc(). Wat je wilt optellen (en andere berekeningen), komt tussen haakjes achter calc te staan. In dit geval wordt 35 keer 0 opgeteld. (Op de * ‑1rem aan het eind wordt later teruggekomen.)

Zodra een <input> is aangevinkt, wordt bij #i-1:checked ~ #tekst en verder een waarde aan de bij de <input> horende variabele gegeven. Als input#i-1 is aangevinkt, krijgt variabele --w-1 de waarde 1. Als input#i-13 is aangevinkt, krijgt variabele --w-13 de waarde 13. Zo krijgen alle 35 variabelen een andere waarde dan het oorspronkelijke 0, maar alleen als de bijbehorende <input> is aangevinkt.

Als de eerste drie <input>'s zijn aangevinkt (die horen bij de getallen 1 tot en met 3), wordt de optelling 1 + 2 + 3 + 0 + 0 plus nog dertig keer 0. Oftewel: 6.

Als je de tien hoogste getallen aanvinkt, is de uitkomst 305. Hoger kan het totaal dus nooit worden. Als je elf getallen of meer aanvinkt, verschijnt een waarschuwing en zie je de tekst met het totaal en dergelijke helemaal niet meer.

Stel dat je de getallen 3, 8 en 17 hebt aangevinkt. Het resultaat van de optelling is dan 3 + 8 + 17 (+ tig keer 0) = 28. Als je de optelling achter calc vervangt door die uitkomst, wordt het al 'n stuk overzichtelijker:

transform: translateY(calc(28) * -1rem);

De 28 staat tussen haakjes, omdat de hele optelling tussen haakjes stond:

calc( ... + ... + ... + ...) * –1rem);

Het sterretje achter het haakje betekent namelijk 'vermenigvuldigen', en een vermenigvuldiging gaat voor een optelling. Zonder haakjes zou het laatste getal van de optelling (de waarde van --w-17) eerst worden vermenigvuldigd met wat achter het sterretje staat, en pas daarna zou de optelling worden gemaakt. Door om de optelling haakjes te zetten, krijgt de optelling voorrang boven de vermenigvuldiging.

(Dit zijn trouwens precies dezelfde regels die bij gewoon rekenen gelden.)

Goed, de optelling is dus uitgevoerd. Dat levert een getal op. Het vervelende is alleen dat je in css eigenlijk helemaal niets kunt met een getal. Je kunt niet iets schrijven als top: 100;, want dan negeert de browser dat onder het uitroepen van de kreet: "Honderd wat, sukkel?"

(Alleen speciale browsers voor ontwikkelaars kunnen dat roepen, dus als jouw browser dat niet roept, moet je zo'n browser installeren. Safari is deftiger, die zegt: "Naar mijn bescheiden mening ontbreekt hier iets, geachte ontwikkelaar, waardoor ik deze regel helaas moet negeren." Logisch, want ook Apple-gebruikers zijn deftiger.)

Maar goed, we hebben 'n getal. In dit geval het getal 28. Als je dat nou vermenigvuldigt met een eenheid, zoals rem, dan heb je plotsklaps een gewone waarde en eenheid, zoals die overal in css gebruikt kan worden:

calc(28 * –1rem)

levert -28rem op. Met een min ervoor, want ook in de berekening staat een min voor de 1rem. Als de berekening calc(20 * 5px) zou zijn, zou de uitkomst 100px zijn. Enzovoort.

Terug naar de volledige regel, waarbij calc met de erachter staande berekening nu vervangen kan worden door -28rem:

transform: translateY(-28rem);

En dit is een doodgewone css-regel: verplaats de <span> met de totalen 28 rem naar boven.

Als er andere <input>'s zijn aangevinkt, veranderen andere variabelen van 0 naar de bij de <input> horende waarde en krijg je dus een andere uitkomst van de berekening. Waardoor je een grotere of kleinere verplaatsing naar boven krijgt.

In de <span> met de totalen staat op elke regel een mogelijke uitkomst. De regelhoogte van de <span> met de totalen is eerder bij #totaal span ook 1 rem gemaakt. (Daar is ook te vinden, waarom als eenheid rem is gebruikt.)

Als de uitkomst van de berekening 28 is, wordt de <span> met de totalen 28 rem naar boven geschoven. En daar staat, op de 27e regel, het getal 28.

Overigens levert dit bij zoomen of een andere lettergrootte in Dolphin op Android, Google Chrome en Opera op Linux, OS X en Windows, en UC browser op Windows problemen op: er wordt een verkeerd totaal getoond. Meer hierover is te vinden bij Bekende problemen (en oplossingen) onder het kopje Zoomen en andere lettergroottes.

Ook hieruit blijkt weer dat je beter JavaScript kunt gebruiken voor dit soort berekeningen. Bovendien is het onder elkaar zetten van 306 mogelijke uitkomsten nou ook niet direct een elegante techniek...

#kolom-1::after

#kolom-1: het element met id="kolom-1". De <span> die wordt gebruikt om het aantal in de eerste kolom aangevinkte getallen weer te geven.

::after: met behulp van ::after wordt bij de <span> een pseudo-element gemaakt.

Binnen dit pseudo-element wordt met behulp van content de counter voor het aantal in de eerste kolom aangevinkte getallen weergegeven.

In normale mensentaal: maak een pseudo-element aan bij span#kolom-1.

content: counter(kolom-1);

Zet met behulp van content de counter kolom-1, de counter voor het aantal in de eerste kolom aangevinkte getallen, in het hierboven aangemaakte pseudo-element. De html voor de <p>, waar span#kolom-1 in zit, ziet er zo uit:

<p>In de eerste kolom heb je er <span id="kolom-1"></span> aangevinkt, in de tweede <span id="kolom-2"></span>, in de derde <span id="kolom-3"></span>, in de vierde <span id="kolom-4"></span> en in de vijfde kolom <span id="kolom-5"></span>.</p>

Op het scherm verschijnt:

In de eerste kolom heb je er 2 aangevinkt, in de tweede 0, in de derde 1, in de vierde 0 en in de vijfde kolom 3.

(De getallen zullen uiteraard variëren.)

Het getal '2' in de zin hierboven hoort bij counter kolom-1. (De andere getallen horen bij andere counters, die hieronder aan de beurt komen.) Er zijn kennelijk twee <input>'s in de eerste kolom aangevinkt.

Omdat in de html voor en na <span id="kolom-1"></span> een spatie staat, staat er netjes een spatie tussen het getal en de tekst.

De <span>'s met een ander volgnummer achter 'kolom-' horen bij de andere vier kolommen.

#kolom-2::after
{content: counter(kolom-2);} #kolom-3::after {content: counter(kolom-3);} #kolom-4::after {content: counter(kolom-4);} #kolom-5::after {content: counter(kolom-5);}

Deze vier regels werken precies hetzelfde als die bij #kolom-1::after iets hierboven, alleen zijn dit de counters voor de tweede, derde, vierde en vijfde kolom. Ze worden achter respectievelijk span#kolom-2, span#kolom-3, span#kolom-4 en span#kolom-5 gezet.

#regel-1::after

#regel-1: het element met id="regel-1". De <span> die wordt gebruikt om het aantal in de eerste regel aangevinkte getallen weer te geven.

::after: met behulp van ::after wordt bij de <span> een pseudo-element gemaakt.

Binnen dit pseudo-element wordt met behulp van content de counter voor het aantal in de eerste regel aangevinkte getallen weergegeven.

In normale mensentaal: maak een pseudo-element aan bij span#regel-1.

content: counter(regel-1);

Zet met behulp van content de counter regel-1, de counter voor het aantal in de eerste regel aangevinkte getallen, in het hierboven aangemaakte pseudo-element. De html voor de <p>, waar span#regel-1 in zit, ziet er zo uit:

<p>Op de eerste regel heb je er <span id="regel-1"></span> aangevinkt, op de tweede <span id="regel-2"></span>, op de derde <span id="regel-3"></span>, op de vierde <span id="regel-4"></span>, op de vijfde <span id="regel-5"></span>, op de zesde <span id="regel-6"></span> en op de zevende regel <span id="regel-7"></span>.</p>

Op het scherm verschijnt:

Op de eerste regel heb je er 4 aangevinkt, op de tweede 3, op de derde 0, op de vierde 0, op de vijfde 2, op de zesde 0 en op de zevende regel 0.

(De getallen zullen uiteraard variëren.)

Het getal '4' in de zin hierboven hoort bij counter regel-1. (De andere getallen horen bij andere counters, die hieronder aan de beurt komen.) Er zijn kennelijk vier <input>'s in de eerste kolom aangevinkt.

Omdat in de html voor en na <span id="regel-1"></span> een spatie staat, staat er netjes een spatie tussen het getal en de tekst.

De <span>'s met een ander volgnummer achter 'regel-' horen bij de andere zes regels.

#regel-2::after
{content: counter(regel-2);} #regel-3::after {content: counter(regel-3);} #regel-4::after {content: counter(regel-4);} #regel-5::after {content: counter(regel-5);} #regel-6::after {content: counter(regel-6);} #regel-7::after {content: counter(regel-7);}

Deze zes regels werken precies hetzelfde als die bij #kolom-1::after iets hierboven, alleen zijn dit de counters voor de tweede, derde, vierde ,vijfde, zesde en zevende regel. Ze worden achter respectievelijk span#regel-2, span#regel-3, span#regel-4, span#regel-5, span#regel-6 en span#regel-7 gezet.

Alleen voor browsers die css-variabelen ondersteunen

Browsers die css-variabelen niet ondersteunen, negeren de hieronder staande css. Deze browsers zijn niet in staat het totaal van de aangevinkte getallen te tonen, daarom is bij #optellen de hele regel over het totaal verborgen. Pas als gelijk hieronder bij @supports (--w-1: 1) blijkt dat de browser css-variabelen ondersteunt, wordt deze regel weer zichtbaar gemaakt en wordt het tonen van het totaal voorbereid voor browsers die css-variabelen wel ondersteunen.

@supports (--w-1: 1)

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 css-variabelen ondersteunen. (Er bestaat geen goede Nederlandse vertaling voor 'feature query'. Het is een vraag of een bepaalde eigenschap en waarde worden ondersteund: iets als 'eigenschap vraag'.)

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

--w-1: 1: de eigenschap waarop wordt getest. Dit ziet er misschien wat vreemd uit, maar de test bestaat uit precies dezelfde css die wordt gebruikt bij de selector. Meestal zal dat iets zijn als mix-blend-mode: normal, wat iets herkenbaarder is.

In dit geval is --w-1 een css-variabele. 'w-1' is de naam van de variabele, dit had ook 'kerstman', 'waarde-1' of 'wiewatwaarwaaromwanneer' mogen zijn. De twee koppeltekens aan het begin zijn verplicht, daaraan herkent de browser dat het om een css-variabele gaat.

Bij #i-1:checked ~ #tekst wordt aan deze css-variabele met --w-1: 1; de waarde '1' aan --w-1 gegeven. Op precies dezelfde manier als daar de css-variabele een waarde krijgt, gebeurt dat hier. Als de browser dat herkent, kan de browser met css-variabelen uit de voeten.

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 (mix-blend-mode: normal) { body {color: silver;} (...) rest van de css voor deze @supports-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) moeten worden ondersteund, of dat een eigenschap juist niet mag worden ondersteund, maar dat gebeurt hier allemaal niet. Hier is de enige voorwaarde dat css-variabelen worden ondersteund.

Er wordt hier alleen getest op één van de 35 gebruikte css-variabelen (--w-1 tot en met --w-35). Maar als de browser één css-variabele ondersteunt, worden ze (uiteraard) allemaal ondersteund, want ze werken allemaal op dezelfde manier.

Alleen browsers die css-variabelen ondersteunen, voeren de css binnen deze feature query uit. Oudere browsers kennen @supports niet en voeren de css binnen deze feature query daarom niet uit. Dat kan een probleem zijn. Stel dat je op het ondersteunen van @supports (display: block) gaat testen. Elke browser ondersteunt dat, maar browsers die @supports niet ondersteunen, zullen de css voor display: block; toch niet uitvoeren.

Dit is natuurlijk een idioot voorbeeld, want je gaat niet testen op iets dat alle browsers al sinds het Stenen Tijdperk ondersteunen. Het eert alleen een potentieel probleem. In dit geval speelt dat niet, omdat elke browser die css-variabelen ondersteunt, ook @supports ondersteunt.

(Het omgekeerde is niet het geval: Internet Explorer 11 bijvoorbeeld ondersteunt @supports wel, maar css-variabelen niet.)

Hoe dan ook voeren alle browsers die css-variabelen niet ondersteunen de css binnen deze feature query niet uit. En omdat hierboven bij #optellen de hele regel over het totaal is verborgen, laten deze browsers deze regel niet zien. Want die wordt pas zichtbaar gemaakt binnen deze feature query.

#optellen

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.

#tekst p {margin: 3px 0;}

#optellen {display: none;}

Het element met id="optellen". Dit is de <p> waarin het totaal van de aangevinkte getallen staat: 'Als je alle gekozen getallen bij elkaar optelt, is het totaal ...'.

display: block;

Bij #optellen is deze <p> met display: none; verborgen, omdat browsers die css-variabelen niet ondersteunen het totaal niet weer kunnen geven. Browsers die zijn geslaagd voor de test op het ondersteunen van css-variabelen bij @supports (--w-1: 1), kunnen het totaal weergeven. Voor deze browsers wordt de <p> weer zichtbaar gemaakt.

#totaal

Het element met id="totaal". Dit is een lege <span> die alleen wordt gebruikt om 'n soort venstertje boven de <span> met de 306 mogelijke totalen te zetten, zodat alleen het juiste totaal wordt getoond.

display: inline-block;

Van zichzelf is een <span> een inline-element. Hierdoor zijn eigenschappen als breedte niet te gebruiken. Door de <span> te veranderen in een inline-block, kunnen deze eigenschappen wel worden gebruikt. Anders dan bij een blok-element wordt de <span> echter niet op een nieuwe regel gezet, het blijft een inline-element.

width: 2em;

Breedte.

Als eenheid wordt de relatieve eenheid em genomen, omdat een absolute eenheid als px niet mee verandert met de lettergrootte. Hierdoor zou de breedte bij getallen met drie cijfers te klein worden, of de breedte zou bij getallen met 1 cijfer juist te groot worden. Nu verandert de breedte mee met een eventuele andere lettergrootte.

Binnen deze <span> staat weer een <span>: de <span> met de eigenlijke totalen. Binnen deze geneste <span> staan alle 306 mogelijke totalen. In principe staan die 306 totalen gewoon achter elkaar. Maar ze moeten onder elkaar komen te staan. Door de breedte van deze span#totaal te verkleinen, wordt de breedte van de geneste <span> met de totalen ook beperkt tot deze breedte. Daardoor wordt elk getal op een nieuwe regel gezet en staan ze dus onder elkaar.

(Hoe de html voor de <span> met totalen precies in elkaar zit, is te vinden bij <span>0.&nbsp;&nbsp; ...)

height: 0.87em;

Hier gelijk boven bij width: 2em; is ervoor gezorgd dat de 306 getallen in de <span> met de totalen onder elkaar komen te staan. Dit heeft één klein nadeel: je ziet nu niet alleen het juiste totaal, maar ook de totalen daarboven en daaronder. Als je scherm hoog genoeg is, zie je alle 306 mogelijke totalen.

Daarom wordt aan deze <span> een hoogte gegeven. In combinatie met de hierboven opgegeven breedte ontstaat dan een soort venstertje voor de genest <span> met de 306 totalen, waardoor alleen het juiste totaal zichtbaar wordt. (Hiervoor is ook nog het gelijk hieronder opgegeven overflow: hidden; nodig.)

Als eenheid wordt de relatieve eenheid em genomen, omdat een absolute eenheid als px niet mee verandert met de lettergrootte. Nu verandert de hoogte mee met een eventuele andere lettergrootte.

overflow: hidden;

Standaard staat overflow op visible: als tekst en dergelijke. niet binnen het element past, wordt het toch weergegeven. Meestal is dat ook wat je wilt. Mogelijk wordt de lay-out verstoord, maar er verdwijnt in ieder geval geen tekst en dergelijke. Maar in dit geval is dit niet de bedoeling.

Hierboven is met width: 2em; en height: 0.87em; een soort venstertje gemaakt van deze <span> gemaakt, waarbinnen de <span> met de 306 totalen staat. Die <span> past echter niet binnen dat 'venstertje', want daar past maar één totaal binnen: het totaal dat getoond moet worden.

Afbeelding 7: alle totalen zichtbaar

Op de afbeelding is span#totaal, het 'venstertje', even zichtbaar gemaakt met een breed zwart kader. De binnen span#totaal zittende <span> met de onder elkaar staande totalen is ook zichtbaar gemaakt. Het totaal van de aangevinkte getallen op de afbeelding is 33. Dat is ook het getal dat binnen het 'venstertje' staat. Maar ook alle andere totalen worden getoond, want dat is standaard zo: alles wordt getoond, ook als het niet binnen het element past.

Door overflow op hidden te zetten, wordt alles, wat niet binnen span#totaal past, verborgen. Nu is alleen het juiste totaal zichtbaar: het totaal dat op de afbeelding binnen het zwarte kadertje staat.

padding-bottom: 1px;

Door deze kleine padding aan de onderkant komt het totaal iets hoger te staan, waardoor het beter aansluit op de rest van de tekst.

#totaal span

Alle <span>'s binnen het element met id="totaal". Binnen span#totaal zit maar één <span>: de <span> met alle totalen. Hoe de html voor deze <span> met totalen precies in elkaar zit, is te vinden bij <span>0.&nbsp;&nbsp; ...

display: inline-block;

Standaard is een <span> een inline-element. Daardoor kan een eigenschap als regelhoogte niet worden gebruikt. Door van de <span> een inline-block te maken, kunnen dit soort eigenschappen toch worden gebruikt. Anders dan bij een echt blok-element, wordt de <span> echter niet op een nieuwe regel gezet: het blijft een inline-element.

line-height: 1rem;

Regelhoogte.

Als eenheid wordt de rem gebruikt. Deze is ongeveer hetzelfde als de eenheid em, maar is gebaseerd op de lettergrootte van het <html>-element, van de pagina. Een lettergrootte in em wordt geërfd van voorouders, een lettergrootte in rem is altijd hetzelfde als die van <html>. De rem is dus overal op de pagina precies even groot.

In deze <span> staan alle totalen onder elkaar: 306 getallen. De regelhoogte van elk getal is 1 rem.

Bij #optellen span span wordt de <span> met de totalen met behulp van transform: translateY() een bepaalde afstand naar boven gezet. Die afstand correspondeert met het totaal van de aangevinkte getallen, waardoor het juiste totaal zichtbaar wordt. Ook die verplaatsing gebeurt met behulp van de eenheid rem. Als de <span> met de totalen 10 rem omhoog wordt gezet, zijn dat 10 regelhoogtes, oftewel: tien getallen. Dat is ook precies wat er gebeurt: als het totaal van de aangevinkte getallen 10 is, wordt de <span> 10 rem omhoog gezet, waardoor het getal 10 op de juiste hoogte komt te staan en zichtbaar wordt.

Je zou verwachten dat de eenheid em even goed zou werken als de eenheid rem. De em is gebaseerd op de lettergrootte van het element zelf, hier de <span> met de totalen. Een regelhoogte in em en een verplaatsing in em zou dan ook goed moeten werken, omdat zowel regelhoogte als verplaatsing zijn gebaseerd op de lettergrootte van de <span>. Dat blijkt echter niet zo te zijn. In Firefox bijvoorbeeld treedt bij het gebruik van em als eenheid een forse afwijking op. Bij gebruik van rem blijkt het wel te werken. Zodra de lettergrootte wordt gewijzigd, wordt de afwijking nog groter.

Overigens werkt ook dit niet feilloos. Bij een andere lettergrootte of zoomen wordt bij Dolphin op Android, Google Chrome en Opera op Linux, OS X en Windows, en UC browser op Windows een verkeerd totaal getoond. Hierover is meer te vinden bij Bekende problemen (en oplossingen) onder het kopje Zoomen en andere lettergroottes.

(Met JavaScript voorkom je al deze problemen. Voor dingen als totalen is JavaScript echt veel geschikter.)

margin-top: 2px;

De <span> 2 pixels naar beneden zetten. Hierdoor komt het totaal op gelijke hoogte met de rest van de tekst te staan.

(Ook weer zo'n eigenaardigheid: als je bij transform: translateY() de verplaatsing met 2 px vermindert, blijkt dat niet goed te werken.)

css voor vensters minimaal 760 px breed

@media (min-width: 760px)

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

De css die binnen deze media query staat, geldt alleen voor vensters die minimaal 760 px breed zijn. In deze bredere vensters worden enkele onderdelen van de pagina iets groter weergegeven.

@media: geeft aan dat het om css gaat die alleen van toepassing is, als aan bepaalde voorwaarden wordt voldaan. Al langer bestond de mogelijkheid om met behulp van zo'n @media-regel css voor bijvoorbeeld printers op te geven. css3 heeft dat uitgebreid tot bepaalde fysieke eigenschappen, zoals de breedte en hoogte van het venster van de browser.

screen: deze regel geldt alleen voor schermweergave.

and: er komt nog een voorwaarde, waaraan moet worden voldaan.

(min-width: 760px): het venster moet minimaal 760 px breed zijn. Is het venster smaller, dan wordt de css die binnen deze media-regel staat genegeerd.

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

@media screen and (min-width: 760px) { body {color: silver;} (...) rest van de css voor deze @media-regel (...) footer {color: gold;} }

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

Als je nou 'n mobieltje hebt met een resolutie van – ik roep maar wat – 1024 x 768 px, dan geldt deze media query toch niet voor dat mobieltje. Terwijl dat toch echt meer dan 760 px breed is. Een vuig complot van gewetenloze multinationals? Voordat je je gaat beklagen bij Radar, zou ik eerst even verder lezen.

Steeds meer mobiele apparaten, maar ook steeds meer gewone beeldschermen, hebben een hogere resolutiedichtheid. Dat wil zeggen dat ze kleinere pixels hebben, die dichter bij elkaar staan. Daardoor zijn foto's, tekst, en dergelijke veel scherper weer te geven. Hoe kleiner de puntjes (de pixels) zijn, waaruit een afbeelding is opgebouwd, hoe duidelijker het wordt.

Er ontstaat alleen één probleem: als je de pixels twee keer zo klein maakt, wordt ook wat je ziet twee keer zo klein. En inmiddels zijn er al apparaten met pixels die meer dan vier keer zo klein zijn. Een lijntje van 1 px breed zou op die apparaten minder dan 'n kwart van de oorspronkelijke breedte krijgen en vrijwel onzichtbaar zijn. Een normale foto zou in een thumbnail veranderen. Kolommen zouden heel smal worden. Tekst zou onleesbaar klein worden. Allemaal fantastisch scherp, maar je hebt 'n vergrootglas nodig om 't te kunnen zien.

Om dit te voorkomen wordt een verschil gemaakt tussen css-pixels en schermpixels (in het Engels 'device-pixels'). De css-pixels zijn gebaseerd op de – tot voor kort – normale beeldschermen van de desktop. 1 css-pixel is op zo'n beeldscherm 1 pixel. Het aantal schermpixels is het werkelijk op het apparaat aanwezige aantal pixels (dat is het aantal pixels, waarvoor je hebt betaald).

Dat eerder genoemde mobieltje van 1024 x 768 px heeft wel degelijk het aantal pixels, waarvoor je hebt betaald. Maar die zitten dichter bij elkaar. Op een gewoon beeldscherm zitten 96 pixels per inch, wat wordt uitgedrukt met de eenheid dpi ('dots per inch'). Als dat mobieltje een resolutie van 192 dpi heeft, 192 pixels per inch, zijn de pixels ervan twee keer zo klein als op een origineel beeldscherm. Er zijn per inch twee keer zoveel schermpixels aanwezig.

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

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

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

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

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

body

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

body {background: #ff9; color: black; font-family: Arial, Helvetica, sans-serif; margin: 0; padding: 0;}

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

font-size: 110%;

Iets groter dan standaard. '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.

#opdracht

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

#opdracht {background: #eee; color: black; box-sizing: border-box; width: 600px; max-width: 90%; text-align: center; margin: 10px auto 0; border: black solid 1px; border-bottom: none;}

Het element met id="opdracht". Binnen deze <div> staan de teksten boven de getallen.

font-size: 1.4em;

Iets grotere letter.

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

line-height: 2em;

Iets grotere regelhoogte dan standaard. Als eenheid wordt de relatieve eenheid em gebruikt, omdat bij gebruik van een absolute eenheid zoals px de regelhoogte niet mee verandert met de lettergrootte.

#opdracht p:nth-of-type(2)

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

#opdracht p:nth-of-type(2) {font-size: 0.6em; margin: 0;}

#opdracht: het element met id="opdracht".

p: alle <p>'s binnen #opdracht.

:nth-of-type(2): het element met een bepaald volgnummer. Tussen de haakjes staat het volgnummer: het gaat om de tweede <p> in div#opdracht. Omdat voor :nth-of-type() een p staat, worden alleen <p>'s geteld. Als binnen div#opdracht 327 <span>'s zitten, tellen die niet mee. Hadden ze maar 'n <p> moeten zijn.

In normale mensentaal: elke tweede <p> binnen div#opdracht. Dit is de <p> met de tweede regel tekst boven de getallen.

De selector :nth-of-type() kan onverwachte bijwerkingen hebben. In dit geval zit er maar één serie <p>'s in div#opdracht. Maar als binnen div#opdracht bijvoorbeeld nog 'n geneste <div> zou zitten, waarin ook <p>'s zouden zitten, zou deze selector ook voor die <p>'s gelden. (Dit zou je in dit geval kunnen oplossen door het toevoegen van >: #opdracht > p:nth-of-type(2). Nu geldt de selector alleen voor directe kinderen van div#opdracht.)

line-height: 1em;

Hier iets boven is bij #opdracht aan de hele div#opdracht een grotere regelhoogte gegeven. Deze <p> staat binnen div#opdracht. De regelhoogte wordt geërfd, daarom geldt die regelhoogte ook voor deze <p>. Deze <p> heeft echter 'n veel kleinere lettergrootte, daarom wordt de regelhoogte hier verkleind.

(De lettergrootte wordt normaal genomen ook geërfd, maar omdat eerder al bij #opdracht p:nth-of-type(2) een kleinere lettergrootte aan deze <p> is gegeven, wordt de lettergrootte niet geërfd van div#opdracht. Gewone css 'wint' altijd van geërfde css.)

margin-bottom: 10px;

Alleen komt nu door de gelijk hierboven opgegeven kleinere regelhoogte de onderste regel veel te dicht boven de getallen te staan. Daarom wordt aan de onderkant van de <p> een kleine marge toegevoegd.