Met gerichte focusnavigatie (met de pijltoetsen of D-pads) kunnen gebruikers die alleen een toetsenbord of een alternatieve invoer gebruiken, door interactieve items navigeren op basis van de visuele 2D-indeling. Dit is erg handig voor ruimtelijke gebruikersinterfaces (zoals mediarails, dashboardvensters of geneste catalogi), maar moet zorgvuldig worden geïmplementeerd om te voorkomen dat systeemeigen browsergedrag, zoals scrollen in de viewport, wordt verbroken.
In deze handleiding wordt uitgelegd hoe je goed presterende, toegankelijke 2D-navigatie maakt in samengestelde widgets.
Kernprincipes voor architectuur
Houd je aan deze basisregels als je aangepaste ruimtelijke focus met de pijltoetsen implementeert:
- Beperken tot samengestelde widgets: Overschrijf de pijltoetsen niet in de normale tekststroom van documenten of eenvoudige verticale lijstindelingen waar standaard sequentiële tabnavigatie wordt verwacht. Ga naar Coderingsrichtlijnen voor toegankelijkheid voor algemene richtlijnen voor de documentstroom.
- Systeemeigen scrollen bij grenzen behouden: Als er geen focusbaar element is in de ingedrukte richting, voorkom je het standaardgedrag van de browser niet. Laat de gebeurtenis bubbelen, zodat de browser de viewport native kan scrollen. Als je de pijltoetsen overschrijft, wordt de toegankelijkheid onvoorwaardelijk verbroken (WCAG 1.4.10).
- Zorg voor focuszichtbaarheid en -uitlijning: Als de focus verandert, zorg je dat het nieuwe element met focus in beeld wordt gescrold (bijvoorbeeld met
scrollIntoView({ block: 'nearest', inline: 'nearest' })) zodat het volledig zichtbaar blijft. - Coördineren met het lay-outsysteem: Bepaal de posities van focuskandidaten op basis van live begrenzingsgeometrieën (
getBoundingClientRect()) terwijl je elke toetsaanslag verwerkt. Ga er niet van uit dat de DOM-volgorde of een vaste rasterstructuur de visuele lay-out vertegenwoordigt. Ga naar CSS-indelingen en responsief ontwerp voor aanbevelingen voor moderne indelingen.
Toegankelijk focusbeheer
De ruimtelijke container moet één tabstop tonen voor opeenvolgende paginatabnavigatie. Beheer de toetsenbordfocus in de widget met het Roving Tabindex Pattern:
- Het actieve item heeft
tabindex="0". - Alle inactieve items hebben
tabindex="-1". - Als je navigeert, verplaats je de focus programmatisch door het kenmerk
tabindex="0"te verschuiven enfocus()aan te roepen.
Visuele kandidaatselectie
In asymmetrische, omloop- of rasterindelingen mislukt indexgebaseerde toewijzing. Leid in plaats daarvan kandidaten af van de live visuele geometrie voor de huidige toetsaanslag:
- Kandidaten: Gebruik de focusbare afstammelingen van de widgetcontainer die
checkVisibility()doorgeven. Verborgen elementen kunnen een rechthoek van grootte nul hebben op(0, 0)en mogen niet per ongelukup- ofleft-doelen worden. - Geometrie: Lees
getBoundingClientRect()voor het element waarop de focus ligt en elke kandidaat terwijl de toetsaanslag wordt verwerkt. Sla deze rechthoeken niet globaal op in het cachegeheugen: de vormgeving kan om verschillende redenen veranderen en een klein aantal geometrie-uitlezingen is goedkoop bij de verwerking van een belangrijke gebeurtenis in het tempo van de gebruiker. - Richtingfilter: Een kandidaat bevindt zich alleen in de ingedrukte richting als de dichtstbijzijnde rand zich op of voorbij de verste rand van het huidige element op die as bevindt, waardoor een kleine tolerantie voor subpixel-afronding mogelijk is. Een kandidaat voor
rightmoet bijvoorbeeldcandidate.left >= current.right - tolerancehebben. Vergelijk geen middens om te bepalen of een kandidaat in de richting is. - Uitgelijnde kandidaten eerst: Geef onder kandidaten die het richtingsfilter doorstaan de voorkeur aan kandidaten waarvan de omvang op de loodrechte as de omvang van het huidige element overlapt. Kies de kleinste opening op de navigatieas en verbreek de banden met de kleinste loodrechte middenoffset. Zo blijft de navigatie in de aangrenzende visuele rij of kolom, zelfs als een item ongebruikelijk breed is.
- Fallback-score: Als geen enkele kandidaat overlapt op de loodrechte as, geef je de resterende kandidaten de score
gap + w * perpendicularCentreOffseten kies je het minimum. Gebruik een gewicht vanw >= 2als startpunt en pas dit aan voor de vormgeving. Een groter gewicht geeft de voorkeur aan kandidaten die beter zijn uitgelijnd. - Geen kandidaat: retourneer niets. De gebeurtenishandler mag
preventDefault()niet aanroepen, zodat de browser native kan scrollen.
De exacte implementatie kan variëren, maar houd de richtingtest op basis van de rand en pas de overlapvoorkeur toe vóór de gewogen fallback. Dit is de kern van het algoritme. Presenteer een cache of een vast gewicht niet als universeel correct.
Integratie van toetsaanslagen en samenwerking bij scrollen
Je gebeurtenisrouter mag standaard scrollen in het weergavegebied alleen voorkomen als er een geldige overgang van de ruimtelijke focus plaatsvindt:
- Onderschep de gebeurtenis niet als een modificatietoets wordt ingedrukt, als
e.defaultPreventedal waar is of als het binnenste gebeurtenisdoel zelf pijltoetsen gebruikt. Gebruike.composedPath()[0]in plaats van alleene.target, omdate.targetopnieuw kan worden getarget op een schaduwhost. Een praktische bewerkbare/controlecheck istarget.matches(':read-write, select, input[type="range"], [role="slider"]'). Gesloten schaduwwortels kunnen niet worden geïnspecteerd. - Beperk de listener tot de widgetcontainer, zodat meerdere ruimtelijke widgets naast elkaar op een pagina kunnen bestaan.
- Alleen actie ondernemen als het element waarop de focus is geplaatst zelf een kandidaat is. Als items hun eigen focusbare bedieningselementen bevatten, maak je die bedieningselementen ook kandidaten of laat je ze met rust. Los een bovenliggend element niet op met
closest(). - Als er een kandidaat is gevonden, roep je
preventDefault()aan, update je de zwervendetabindex, roep jefocus()aan en gebruik jescrollIntoView({ block: 'nearest', inline: 'nearest' }). - Als er geen kandidaat wordt gevonden, retourneer dan zonder
preventDefault()aan te roepen, zodat native scrollen doorgaat.
Voeg geen sneltoetsen met één teken toe, zoals h/j/k/l of W/A/S/D voor ruimtelijke navigatie. Pijltoetsen zijn geen tekentoetsen. Als je de systeemeigen scrollfunctie behoudt als er geen kandidaat is, voorkom je dat de beweging van de viewport onnodig wordt geblokkeerd.