Een icoontechniek kiezen
Icoonopslag en -weergave zijn afzonderlijke keuzes. Een svg-sprite kan bijvoorbeeld een icoon leveren aan het CSS-icoonsysteem of er kan naar worden verwezen met <use>.
- CSS-icoonsysteem: Gebruik dit als CSS moet kiezen wanneer en welk icoon moet worden getoond. Gebruik een masker voor een icoon met één kleur of een achtergrondafbeelding voor een icoon met alle kleuren.
- Svg met
<use>: Gebruik deze optie om een symbool opnieuw te gebruiken als de ingebouwde stijlopties voldoende zijn. CSS kan de content van de verwezen SVG niet willekeurig opmaken of animeren, maar overgenomen eigenschappen en aangepaste eigenschappen kunnen worden doorgegeven als het symbool is ontworpen om deze te gebruiken. Een pad kan bijvoorbeeldfill="var(--icon-accent, currentColor)"gebruiken om CSS dat deel van het icoon te laten kleuren. - Inline svg: Gebruik deze optie als je willekeurige opmaak of animatie van de svg-content nodig hebt.
<img>met CSS-filters: Vermijd dit om iconen opnieuw te kleuren. Filters kunnen niet op een betrouwbare manier een willekeurige CSS-kleurwaarde toepassen op de afbeelding. Opnieuw kleuren is daarom omslachtig en inflexibel.
Stel jezelf 2 vragen om een beslissing te nemen. Moet CSS het icoon kiezen of wisselen (componentstandaarden, statuswijzigingen, thema's)? Zo ja, gebruik dan het CSS-icoonsysteem. Moet je het icoon aanpassen, behalve de kleur? Als dat het geval is en je de bron van het icoon beheert, ontwerp je het symbool met aangepaste eigenschappen en gebruik je <use>. Als je de bron niet beheert, inline je de svg. Icoonsets van derden tonen zelden aangepaste eigenschappen, dus als je <use> gebruikt, krijg je alleen kleur en weinig anders.
Ingesloten of externe iconen
Zowel het CSS-icoonsysteem als <use> kunnen iconen ophalen uit een gegevens-URI, een afzonderlijk bestand of een sprite. Insluiten (data-URI's of inline SVG) voorkomt een verzoek en wordt gerenderd met de eerste weergave (FP), maar vergroot de CSS of HTML en kan niet afzonderlijk worden gecachet. Daarom is dit geschikt voor een kleine set iconen die op elke pagina verschijnen. Afzonderlijke bestanden worden onafhankelijk in het cachegeheugen opgeslagen en op verzoek geladen, tegen de kosten van één verzoek per bestand. Een sprite is één cachebaar verzoek voor de hele set, maar downloadt elk icoon, of het nu wordt gebruikt of niet, en <use> kan alleen verwijzen naar een sprite van dezelfde oorsprong. Gebruik liever insluiting voor een handvol kerniconen en een sprite of afzonderlijke bestanden voor grotere sets.
Als een pagina duizenden iconen tegelijk weergeeft, benchmark je de werkelijke pagina en items. In een stresstest had inline SVG een klein prestatievoordeel op die schaal, maar de resultaten zijn afhankelijk van de browser en de implementatie. Stresstest voor svg-iconen van Cloud Four
Primaire aanpak: CSS-gestuurde icoonengine
Iconen zijn ontwerptokens: elk icoon is een aangepaste eigenschap met een afbeelding. Elk element kan een icoon tonen door --icon-start of --icon-end in te stellen. De engine combineert 3 CSS-mogelijkheden:
- Geregistreerde aangepaste eigenschappen (
@property) voorkomen dat--icon-starten--icon-endworden overgenomen, zodat een icoon dat op een component is ingesteld, niet wordt overgedragen aan de onderliggende elementen. - Containerstijlquery's (
@container style(...)) nemen waar wanneer een element een icoon heeft toegewezen en geven dit weer, zonder wijzigingen in de klasse of opmaak. - CSS-maskers en
currentColorrenderen het icoon in een pseudo-element dat de tekstkleur van het element volgt. Grootte, kleur, afstand en verticale offset worden gelezen uit overneembare--icon-style-*-eigenschappen met verstandige fallbacks, zodat een hele substructuur opnieuw kan worden opgemaakt vanuit een bovenliggend element. Het voorvoegsel--icon-style-voorkomt dat deze instellingen botsen met de icoontokens zelf, die ook onder--icon-*vallen.
Ontwerptokens voor iconen definiëren
Definieer SVG-iconen en eventuele --icon-style-*-overschrijvingen als herbruikbare aangepaste eigenschappen met inline SVG-gegevens-URI's of root-relatieve URL's (bijvoorbeeld url("/icons/trash.svg")). Als een icoon wordt geladen vanuit een extern SVG-bestand, gebruik je een root-relatieve URL of een ingesloten gegevens-URI. Een relatieve URL die wordt gebruikt via een aangepaste eigenschap, kan worden omgezet ten opzichte van de stylesheet waarin het icoon wordt gebruikt, in plaats van de stylesheet waarin de aangepaste eigenschap is gedefinieerd. Als die stylesheets zich in verschillende mappen bevinden, kan de URL naar het verkeerde bestand verwijzen.
Als je een URL relatief wilt houden aan de stylesheet die de icoontoken definieert, is een geregistreerde property bedoeld om te helpen: de berekende waarde van een geregistreerde <url> of <image> wordt omgezet op basis van de stylesheet die deze heeft gedeclareerd. Vertrouw NIET op de registratie van de token als <image> met de URL in initial-value. Chrome en Safari lossen initial-value-URL's op voor het document, dus dit werkt alleen in Firefox. Chrome herleidt ook waarden die zijn gedeclareerd op :root naar het document, tenzij de syntaxis begint met <url>.
Als relatieve URL's onvermijdelijk zijn, zoals stylesheets in verschillende mappen, registreer je de token met een syntaxis die begint met <url> en declareer je de werkelijke URL op :root. Dit wordt in elke browser omgezet naar de declarerende stylesheet:
@property --icon-caret {
/* The syntax must start with <url>. A syntax that starts with <image> resolves against the document in Chrome. */
syntax: "<url> | <image> | none";
inherits: true;
initial-value: none;
}
:root {
/* Declared here, the URL resolves against this stylesheet */
--icon-caret: url("icons/caret.svg");
}
Elk icoon staat nu op 2 plaatsen en er is veel meer boilerplate, dus dit is alleen de moeite waard als een project stylesheets in verschillende mappen en een beheersbaar aantal iconen heeft. Als je meer dan een tiental iconen hebt, registreer je de --icon-*-tokens (met uitzondering van --icon-style-*) uit een script met CSS.registerProperty, met dezelfde syntaxis, in plaats van elke @property handmatig te schrijven. Geregistreerde property's zijn algemeen en werken niet in shadow roots. Dit is dus misschien niet geschikt voor elk project. Als root-relatieve URL's of gegevens-URI's een optie zijn, zijn ze eenvoudiger en werken ze overal.
:root {
/* Optional: override the built-in fallbacks (1em, currentColor, 0.3em, -0.02em) */
--icon-style-size: 1em;
--icon-style-spacing: 0.4em;
--icon-trash: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='currentColor' stroke-width='2'%3E%3Cpath d='M3 6h18M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2m3 0-1 14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2L4 6h16Z'/%3E%3C/svg%3E");
--icon-favorite: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='currentColor'%3E%3Cpath d='M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z'/%3E%3C/svg%3E");
}
Iconen renderen op basis van tokens
Een element toont een icoon door --icon-start (voorafgaand) of --icon-end (volgend) in te stellen op een van de tokens hierboven. Met 2 stukjes CSS wordt die toewijzing omgezet in een gerenderd icoon.
Registreer eerst --icon-start en --icon-end bij @property. De syntaxis "*" accepteert elke waarde en inherits: false is het belangrijkste deel: zonder dit deel zou een icoon dat op een knop is ingesteld, worden overgenomen door elke afstammeling en zou elk van hen het icoon ook weergeven.
@property --icon-start {
syntax: "*";
inherits: false;
}
@property --icon-end {
syntax: "*";
inherits: false;
}
Ten tweede detecteert een containerstijlquery wanneer de property is ingesteld voor een element en wordt het icoon gerenderd in een pseudo-element. Omdat het pseudo-element --icon-start niet overneemt (het is geregistreerd als niet-overnemend), stuurt de eerste declaratie de waarde expliciet door met inherit. De resterende declaraties bepalen de grootte, maskering, uitlijning en afstand. Omdat de opvulling standaard currentColor is, neemt het icoon de tekstkleur van het element over en volgt het de overgangen.
/* Style query for elements configuring a start icon */
@container style(--icon-start) {
::before {
--icon-start: inherit; /* Forward the value inside the query block */
content: "" / var(--icon-style-alt, "");
display: inline-block;
width: var(--icon-style-size, 1em);
height: var(--icon-style-size, 1em);
background-color: var(--icon-style-color, currentColor);
mask: var(--icon-start) no-repeat center / contain;
/* Center on the cap height, nudged slightly up for optical balance */
vertical-align: calc(0.5cap - var(--icon-style-size, 1em) / 2 + var(--icon-style-voffset, -0.02em));
flex: none;
pointer-events: none;
forced-color-adjust: none;
}
/* Automatically add spacing ONLY if the element has other text or sibling content */
:where(:not(.icon, :empty))::before {
margin-inline-end: var(--icon-style-spacing, 0.3em);
}
}
Elke declaratie heeft een eigen plek:
vertical-align:middlewordt uitgelijnd op de helft van de x-hoogte, wat voor de meeste icoonsets zichtbaar laag is. Als je centreert op0.5cap, blijft het midden van het icoon vaststaan terwijl--icon-style-sizegroeit. De kleine negatieve--icon-style-voffsetcompenseert voor het feit dat het oog de ruimte boven een icoon zwaarder weegt dan de ruimte eronder.flex: none: Een leeg pseudo-element heeft een minimale grootte van nul. In een flexknop is dit dus het eerste dat kleiner wordt als het label lang is, mogelijk tot niets. De icoongrootte is een ontwerpbeslissing, niet iets waarover de vormgeving moet onderhandelen.pointer-events: none: Voorkomt dat het icoon klikken onderschept of deevent.targetwordt.forced-color-adjust: none: In de modus voor afgedwongen kleuren vervangt de browser andersbackground-color, waardoor een gemaskeerd icoon verdwijnt. De opvulling van het masker blijft behouden encurrentColorvolgt nog steeds de geforceerde tekstkleur.content: "" / var(--icon-style-alt, ""): Met alt-attribuut voor gegenereerde content kan een bedieningselement met alleen een icoon de toegankelijke naam van het icoon overnemen via--icon-style-alt(zie Bedieningselementen met alleen een icoon hieronder). De standaard lege alt houdt decoratieve iconen verborgen voor ondersteunende technologie.
Voor een icoon aan het einde volg je hetzelfde patroon met --icon-end en ::after in plaats van --icon-start en ::before. Gebruik mask: var(--icon-end) en pas afstand toe met margin-inline-start in plaats van margin-inline-end.
Zelfstandige elementen en elementen met alleen iconen
Voor losse iconen of knoppen met alleen een icoon pas je .icon rechtstreeks toe op het lege element (de knop zelf voor een bedieningselement met alleen een icoon) en stel je het icoon in met --icon. Hier is --icon de property op een hoger niveau. De regel .icon:empty wijst deze toe aan --icon-start voor een leidend icoon:
.icon:empty {
--icon-start: var(--icon);
}
Gebruiksvoorbeelden
Standaardwaarden voor componenten in de stylesheet
Het belangrijkste voordeel van iconen als aangepaste eigenschappen is dat ze deelnemen aan de cascade. Stel een icoon in voor een kiezer en elk overeenkomend element krijgt het, zonder opmaakwijzigingen:
.button-delete {
--icon-start: var(--icon-trash);
}
a[href^="http"]:not([href*="example.com"]) {
--icon-end: var(--icon-external);
}
<button class="button-delete">Delete item</button>
Iconen wijzigen met status
Omdat het icoon slechts een eigenschap is, is een statuswijziging een gewone CSS-overschrijving. Er is geen tweede element om te tonen of te verbergen en geen klasse om aan of uit te zetten:
.button-expand {
--icon-start: var(--icon-caret-right);
}
.button-expand[aria-expanded="true"] {
--icon-start: var(--icon-caret-down);
}
Hetzelfde geldt voor de presentatie: stel --icon-style-size, --icon-style-color of --icon-style-spacing in op een container zoals een werkbalk en elk icoon daarin wordt geüpdatet.
Eenmalige iconen met inline stijlen
Een element dat niet onder een componentregel valt, kan dezelfde eigenschap inline instellen, bijvoorbeeld <button style="--icon-start: var(--icon-trash)">. Gebruik dit als nooduitgang en niet als standaard: als hetzelfde icoon meerdere keren op hetzelfde soort element verschijnt, verplaats je het naar een stylesheetregel.
Bedieningselementen met alleen iconen
Knoppen met alleen een icoon gebruiken de klasse .icon en --icon die hierboven worden beschreven. Geef de knop een toegankelijke naam met aria-label of laat het icoon er een leveren via --icon-style-alt. Zonder een van beide is het door CSS gegenereerde icoon decoratief. Voor zelfstandige informatieve iconen maak je het element .icon beschikbaar met role="img" en aria-label:
.button-close {
--icon: var(--icon-close);
--icon-style-alt: "Close";
}
<!-- Icon-only interactive control, icon and name from the stylesheet -->
<button class="icon button-close"></button>
<!-- Icon-only interactive control, named via aria-label -->
<button class="icon" style="--icon: var(--icon-trash);" aria-label="Delete item"></button>
<!-- Standalone informative icon -->
<span class="icon" style="--icon: var(--icon-favorite);" role="img" aria-label="Favorite"></span>
Rechtstreeks met svg werken
Als het CSS-icoonsysteem niet geschikt is, gebruik je inline SVG en <use> samen in plaats van als alternatieven. Zet de svg inline de eerste keer dat een icoon op een pagina verschijnt en verwijs ernaar met <use href="#id"> voor elke herhaling. De lokale verwijzing kost geen verzoek en heeft geen cross-origin-beperking. Reserveer een externe sprite (<use href="/icons.svg#id">) voor iconen die op veel pagina's worden gedeeld.
Met <use> wordt de content waarnaar wordt verwezen, gekloond naar een schaduwstructuur, zodat de pagina-css de elementen niet kan selecteren. Wat wel wordt overgenomen: color, currentColor en aangepaste eigenschappen. Naast SVG-presentatiekenmerken kan moderne CSS ook veel geometrische kenmerken wijzigen, zodat een icoon dat je beheert precies de hooks kan tonen die je nodig hebt, bijvoorbeeld stroke-width="var(--icon-stroke, 2)" of zelfs d: var(--icon-path, path(…)) op een bepaald pad.
CSS d wordt niet ondersteund in Safari. Gebruik deze aanpassing dus alleen als dat geen probleem is. Als je het icoon niet beheert, plaats je het inline.
<use> valkuilen die je moet kennen:
- De
viewBoxmoet op de<symbol>of de verwezen<svg>staan. De buitenste<svg>die<use>verpakt, haalt deze niet op uit de verwijzing. Stel daaromwidthenheightin op de wrapper en houd deviewBoxbij het symbool. - Stijlen overschrijden de grens niet in beide richtingen:
<style>binnen een externe sprite is niet van toepassing op de pagina en paginastylesheets zijn niet van toepassing binnen de verwezen boom. Alleen overgenomen eigenschappen en aangepaste eigenschappen worden doorgegeven. - Externe verwijzingen zijn alleen van dezelfde oorsprong. Cross-origin sprites mislukken zonder melding.
- Sprites die inline in het document zijn geplaatst, delen de
id-naamruimte van de pagina. Geef symbool-ID's daarom een naamruimte om conflicten te voorkomen.
Markeer decoratieve svg's met aria-hidden="true" en geef het bijbehorende bedieningselement een eigen toegankelijke naam, zoals bij het CSS-icoonsysteem.
Reservestrategieën
Geregistreerde aangepaste eigenschappen
Zonder @property gedragen --icon-start en --icon-end zich als gewone overgenomen aangepaste eigenschappen, dus een icoon dat op een element is ingesteld, wordt ook weergegeven op de onderliggende elementen. Je kunt dit voorkomen door de eigenschappen van onderliggende elementen in de stijlquery (@container style(--icon-start) { * { --icon-start: initial; } }) te resetten. Dit is dezelfde aanpak als de tijdelijke oplossing voor Safari hieronder.
Containerstijlquery's
Als je browsers zonder containerstijlquery's wilt ondersteunen, gebruik je klassen of componentselectoren als fallback-hooks. Behoud de icoonselectie in aangepaste eigenschappen. Inline-stijlkiezers alleen missen iconen die zijn toegewezen via css-regels.
:where(.icon-start, .icon:empty)::before {
content: image-set(linear-gradient(transparent 0 100%), var(--icon-start)) / var(--icon-style-alt, "");
/* icon styles */
}
:where(.icon-start)::before {
--icon-start: inherit;
}
:where(.icon:empty)::before {
--icon-start: var(--icon);
}
:where(.icon-start:not(.icon, :empty))::before {
margin-inline-end: var(--icon-style-spacing, 0.3em);
}
Gebruik voor iconen aan het einde dezelfde class-hook-fallback met .icon-end, --icon-end en ::after in plaats van .icon-start, --icon-start en ::before. Pas afstand toe op niet-lege content met margin-inline-start in plaats van margin-inline-end.
Dit image-set()-patroon maakt content ongeldig als de aangepaste eigenschap niet is ingesteld. De eigenschap valt dan terug op none en het pseudo-element verdwijnt zonder een klasse toe te voegen of te verwijderen. De andere stijlen van het pseudo-element worden niet verwijderd en conflicten met andere ::before- of ::after-regels worden niet voorkomen. Gebruik deze hooks daarom alleen als die pseudo-elementen beschikbaar zijn voor iconen.
Tijdelijke oplossingen voor de Safari-browser
Safari heeft 2 weergavefouten die van invloed zijn op iconen: WebKit #301609 kan voorkomen dat ze verschijnen tot gebruikersinteractie en #320220 heeft ook invloed op de weergave. De onderstaande tijdelijke oplossing is voor beide problemen:
@keyframes webkit-301609 {}
@supports ((-webkit-nbsp-mode: normal) or (-webkit-touch-callout: none) or (font: -apple-system-body)) and (content-visibility: auto) {
@layer webkit-301609-fix {
::before, ::after {
animation: webkit-301609 0s;
}
}
}
De query @supports is alleen bedoeld voor Safari en @layer vermindert animatieconflicten.
WebKit-bug #325900 kan in sommige gevallen voorkomen dat een directe overschrijving van --icon-start van kracht wordt, zoals het icoon wijzigen bij de muisaanwijzer. Dit heeft gevolgen voor de tijdelijke oplossing voor geregistreerde property's die niet overnemen. Registreer --icon-start voor Safari als overgenomen en reset deze voor onderliggende elementen in de stijlquery:
@supports ((-webkit-nbsp-mode: normal) or
(-webkit-touch-callout: none) or
(font: -apple-system-body)) and (content-visibility: auto) {
/* Override previous declaration */
@property --icon-start {
syntax: "*";
inherits: true;
}
@layer webkit-325900-fix {
@container style(--icon-start) {
* {
--icon-start: initial;
}
}
}
}
De reset voorkomt dat de icoonwaarde wordt doorgegeven aan onderliggende items. Als je de reset in een laag houdt, hebben niet-gelaagde icoondeclaraties voorrang op de reset.