Una panoramica di base su come creare un componente delle impostazioni di cursori e caselle di controllo.
In questo post voglio condividere alcune idee sulla creazione di un componente Impostazioni per il web che sia responsive, supporti più input di dispositivi e funzioni su più browser. Prova la demo.
Se preferisci i video o vuoi un'anteprima dell'interfaccia utente/dell'esperienza utente di ciò che stiamo creando, guarda questo breve tutorial su YouTube:
Panoramica
Ho suddiviso gli aspetti di questo componente nelle seguenti sezioni:
- Layout
- Colore
- Inserimento intervallo personalizzato
- Input di caselle di controllo personalizzate
- Considerazioni sull'accessibilità
- JavaScript
Layout
Questa è la prima demo della sfida GUI completamente basata su CSS Grid. Ecco ogni griglia evidenziata con Chrome DevTools per la griglia:
Solo per lacune
Il layout più comune:
foo {
display: grid;
gap: var(--something);
}
Chiedo a questo layout "solo per spaziatura" perché utilizza la griglia solo per aggiungere spazi tra i blocchi.
Cinque layout utilizzano questa strategia. Eccoli tutti:
L'elemento fieldset
, che contiene ogni gruppo di input (.fieldset-item
), utilizza gap: 1px
per creare i bordi sottili tra gli elementi. Nessuna soluzione complicata per i confini.
.grid { display: grid; gap: 1px; background: var(--bg-surface-1); & > .fieldset-item { background: var(--bg-surface-2); } }
.grid { display: grid; & > .fieldset-item { background: var(--bg-surface-2); &:not(:last-child) { border-bottom: 1px solid var(--bg-surface-1); } } }
Aggiunta di righe aggiuntive alla griglia naturale
Il layout più complesso è risultato essere il layout macro, il sistema di layout logico tra <main>
e <form>
.
Centrare i contenuti con a capo
Sia Flexbox che la griglia offrono la possibilità di align-items
o
align-content
e, quando si tratta di elementi con a capo, gli allineamenti del layout
content
distribuiranno lo spazio tra gli elementi secondari come gruppo.
main {
display: grid;
gap: var(--space-xl);
place-content: center;
}
L'elemento principale utilizza la scrittura abbreviata per l'allineamento place-content: center
in modo che gli elementi secondari siano centrati verticalmente e orizzontalmente nei layout sia a una che a due colonne.
Guarda il video qui sopra per scoprire come i "contenuti" rimangono al centro, anche se si verifica il wrapping.
Ripeti ridimensionamento automatico minmax
<form>
utilizza un layout della griglia adattivo per ogni sezione.
Questo layout passa da una a due colonne in base allo spazio disponibile.
form {
display: grid;
gap: var(--space-xl) var(--space-xxl);
grid-template-columns: repeat(auto-fit, minmax(min(10ch, 100%), 35ch));
align-items: flex-start;
max-width: 89vw;
}
Questa griglia ha un valore diverso per row-gap
(--space-xl) rispetto a column-gap
(--space-xxl) per apportare un tocco personalizzato al layout responsive. Quando le colonne sono impilate, vogliamo un intervallo ampio, ma non così ampio come se fossimo su uno schermo ampio.
La proprietà grid-template-columns
utilizza tre funzioni CSS: repeat()
, minmax()
e
min()
. Una Kravets ha scritto un ottimo post del blog sul layout in merito, chiamandolo
RAM.
Se lo confronti con quello di Una, il nostro layout presenta tre aggiunte speciali:
- Passiamo un'altra funzione
min()
. - Specifichiamo
align-items: flex-start
. - Esiste uno stile
max-width: 89vw
.
La funzione min()
aggiuntiva è descritta bene da Evan Minto nel suo blog nel post Intrinsically Responsive CSS Grid with minmax() and
min().
Ti consiglio di leggerlo. La correzione dell'allineamento flex-start
serve per rimuovere l'effetto di allungamento predefinito, in modo che gli elementi secondari di questo layout non debbano avere altezze uguali, ma possano avere altezze naturali e intrinseche. Nel
video di YouTube viene spiegato brevemente come funziona questa aggiunta all'allineamento.
max-width: 89vw
merita una piccola spiegazione in questo post.
Ecco il layout con e senza lo stile applicato:
Che cosa succede? Quando viene specificato max-width
, viene fornito il contesto, le dimensioni esplicite o le dimensioni predefinite per consentire all'algoritmo di layout auto-fit
di sapere quante ripetizioni possono essere inserite nello spazio. Sebbene sembri ovvio che lo spazio sia "a tutta larghezza", in base alle specifiche della griglia CSS, è necessario fornire una dimensione o una dimensione massima definite. Ho fornito una dimensione massima.
Perché 89vw
? Perché "funziona" per il mio layout.
Io e un paio di altri membri del team di Chrome stiamo cercando di capire perché un valore più ragionevole, come 100vw
, non sia sufficiente e se si tratta di un bug.
Spaziatura
La maggior parte dell'armonia di questo layout è data da una tavolozza limitata di spaziature, 7 per l'esattezza.
:root {
--space-xxs: .25rem;
--space-xs: .5rem;
--space-sm: 1rem;
--space-md: 1.5rem;
--space-lg: 2rem;
--space-xl: 3rem;
--space-xxl: 6rem;
}
L'utilizzo di questi flussi è molto piacevole con la griglia, CSS @nest e la sintassi di livello 5
di @media. Ecco un esempio, l'insieme di stili di layout completamente <main>
.
main {
display: grid;
gap: var(--space-xl);
place-content: center;
padding: var(--space-sm);
@media (width >= 540px) {
& {
padding: var(--space-lg);
}
}
@media (width >= 800px) {
& {
padding: var(--space-xl);
}
}
}
Una griglia con contenuti al centro, con spaziatura moderata per impostazione predefinita (come sui dispositivi mobili). Tuttavia, man mano che lo spazio del viewport diventa disponibile, si espande aumentando il padding. Il CSS 2021 sembra molto buono.
Ricordi il layout precedente, "solo per il vuoto"? Ecco una versione più completa di come appaiono in questo componente:
header {
display: grid;
gap: var(--space-xxs);
}
section {
display: grid;
gap: var(--space-md);
}
Colore
L'uso controllato del colore ha contribuito a rendere questo design espressivo e allo stesso tempo minimale. Io lo faccio così:
:root {
--surface1: lch(10 0 0);
--surface2: lch(15 0 0);
--surface3: lch(20 0 0);
--surface4: lch(25 0 0);
--text1: lch(95 0 0);
--text2: lch(75 0 0);
}
Assegno i nomi ai colori di sfondo e del testo con numeri anziché con nomi come
surface-dark
e surface-darker
perché in una query sui contenuti multimediali li capovolgerò
e chiaro e scuro non avranno significato.
Li inverto in una query sui contenuti multimediali delle preferenze come questa:
:root {
...
@media (prefers-color-scheme: light) {
& {
--surface1: lch(90 0 0);
--surface2: lch(100 0 0);
--surface3: lch(98 0 0);
--surface4: lch(85 0 0);
--text1: lch(20 0 0);
--text2: lch(40 0 0);
}
}
}
È importante dare un'occhiata veloce al quadro generale e alla strategia prima di approfondire i dettagli della sintassi dei colori. Dato che ho anticipato un po', ricominciamo da capo.
LCH?
Senza addentrarci troppo nella teoria del colore, LCH è una sintassi orientata agli esseri umani, che si basa su come percepiamo il colore, non su come lo misuriamo con la matematica (come 255). Questo offre un vantaggio distinto in quanto gli esseri umani possono scriverlo più facilmente e altri esseri umani saranno in sintonia con questi aggiustamenti.
Per oggi, in questa demo, concentriamoci sulla sintassi e sui valori che sto modificando per impostare i colori chiaro e scuro. Diamo un'occhiata a una superficie e a un colore di testo:
:root {
--surface1: lch(10 0 0);
--text1: lch(95 0 0);
@media (prefers-color-scheme: light) {
& {
--surface1: lch(90 0 0);
--text1: lch(40 0 0);
}
}
}
--surface1: lch(10 0 0)
si traduce in luminosità 10%
, crominanza 0 e tonalità 0: un grigio senza colore molto scuro. Poi, nella query sui media per la modalità Luce, la luminosità viene impostata su 90%
con --surface1: lch(90 0 0);
. Questo è l'essenziale della strategia. Inizia cambiando solo la luminosità tra i due temi, mantenendo i rapporti di contrasto richiesti dal design o quelli che possono mantenere l'accessibilità.
Il vantaggio di lch()
è che la leggerezza è orientata alle persone e possiamo sentirci bene per un cambiamento di lch()
, che sarà percepito in modo coerente e costante come %
diverso.%
Ad esempio, hsl()
non è così affidabile.
Se ti interessa, scopri di più sugli spazi di colore e su lch()
. È in arrivo.
Al momento, CSS non può accedere a questi colori. Lascia che ripeta: non abbiamo accesso a un terzo dei colori della maggior parte dei monitor moderni. Non si tratta di colori qualsiasi, ma dei colori più vivaci che lo schermo può mostrare. I nostri siti web sono sbiaditi perché l'hardware dei monitor si è evoluto più rapidamente delle specifiche CSS e delle implementazioni dei browser.
Lea Verou
Controlli di modulo adattivi con color-scheme
Molti browser includono controlli per il tema scuro, attualmente Safari e Chromium, ma devi specificare in CSS o HTML che il tuo design li utilizza.
L'esempio riportato sopra mostra l'effetto della proprietà nel riquadro Stili di DevTools. La demo utilizza il tag HTML, che a mio avviso è in genere una posizione migliore:
<meta name="color-scheme" content="dark light">
Scopri di più in questo color-scheme
articolo di Thomas
Steiner. Puoi ottenere molto di più
degli input di casella di controllo scuri.
CSS accent-color
Di recente è stata registrata attività intorno a accent-color
negli elementi dei moduli, ovvero un singolo stile CSS che può modificare il colore della tinta utilizzato nell'elemento di input dei browser. Scopri di più qui su
GitHub. L'ho incluso nei miei stili per questo componente. Poiché i browser lo supportano, le mie caselle di controllo saranno più in tema con i colori rosa e viola.
input[type="checkbox"] {
accent-color: var(--brand);
}
Colori brillanti con sfumature fisse e messa a fuoco interna
I colori risaltano maggiormente se usati con parsimonia e uno dei modi che mi piace usare per ottenere questo risultato è tramite interazioni con l'interfaccia utente colorate.
Nel video qui sopra sono presenti molti livelli di feedback e interazione con l'interfaccia utente, che contribuiscono a dare personalità all'interazione:
- Mettere in evidenza il contesto.
- Fornire un feedback della UI relativo al "grado di completezza" del valore nell'intervallo.
- Fornire feedback sull'interfaccia utente che indica che un campo accetta input.
Per fornire un feedback quando si interagisce con un elemento, il CSS utilizza la pseudo classe
:focus-within
per modificare l'aspetto di vari elementi. Analizziamo la
.fieldset-item
, è molto interessante:
.fieldset-item {
...
&:focus-within {
background: var(--surface2);
& svg {
fill: white;
}
& picture {
clip-path: circle(50%);
background: var(--brand-bg-gradient) fixed;
}
}
}
Quando uno degli elementi secondari di questo elemento ha lo stato attivo:
- Allo sfondo
.fieldset-item
viene assegnato un colore di superficie con un contrasto più elevato. - Il
svg
nidificato è riempito di bianco per un maggiore contrasto. - Il
<picture>
clip-path
nidificato si espande fino a formare un cerchio completo e lo sfondo viene riempito con il gradiente fisso luminoso.
Intervallo personalizzato
Dato il seguente elemento input HTML, ti mostrerò come ho personalizzato il suo aspetto:
<input type="range">
Questo elemento è composto da tre parti che dobbiamo personalizzare:
Stili degli elementi dell'intervallo
input[type="range"] {
/* style setting variables */
--track-height: .5ex;
--track-fill: 0%;
--thumb-size: 3ex;
--thumb-offset: -1.25ex;
--thumb-highlight-size: 0px;
appearance: none; /* clear styles, make way for mine */
display: block;
inline-size: 100%; /* fill container */
margin: 1ex 0; /* ensure thumb isn't colliding with sibling content */
background: transparent; /* bg is in the track */
outline-offset: 5px; /* focus styles have space */
}
Le prime righe di CSS sono le parti personalizzate degli stili e spero che la loro etichettatura chiara ti sia utile. Il resto degli stili è costituito principalmente da stili di reimpostazione per fornire una base coerente per la creazione delle parti più complesse del componente.
Stili di traccia
input[type="range"]::-webkit-slider-runnable-track {
appearance: none; /* clear styles, make way for mine */
block-size: var(--track-height);
border-radius: 5ex;
background:
/* hard stop gradient:
- half transparent (where colorful fill we be)
- half dark track fill
- 1st background image is on top
*/
linear-gradient(
to right,
transparent var(--track-fill),
var(--surface1) 0%
),
/* colorful fill effect, behind track surface fill */
var(--brand-bg-gradient) fixed;
}
Il trucco è "rivelare" il colore di riempimento vivace. Questo viene fatto con il gradiente di interruzione netta in alto. La sfumatura è trasparente fino alla percentuale di riempimento, dopodiché viene utilizzato il colore della superficie della traccia non riempita. Dietro questa superficie non riempita, c'è un colore di larghezza intera, in attesa che la trasparenza lo riveli.
Stile di riempimento del percorso
Il mio design richiede JavaScript per mantenere lo stile di riempimento. Esistono strategie solo CSS, ma richiedono che l'elemento miniatura abbia la stessa altezza della traccia e non sono riuscito a trovare un'armonia entro questi limiti.
/* grab sliders on page */
const sliders = document.querySelectorAll('input[type="range"]')
/* take a slider element, return a percentage string for use in CSS */
const rangeToPercent = slider => {
const max = slider.getAttribute('max') || 10;
const percent = slider.value / max * 100;
return `${parseInt(percent)}%`;
};
/* on page load, set the fill amount */
sliders.forEach(slider => {
slider.style.setProperty('--track-fill', rangeToPercent(slider));
/* when a slider changes, update the fill prop */
slider.addEventListener('input', e => {
e.target.style.setProperty('--track-fill', rangeToPercent(e.target));
})
})
Penso che sia un buon upgrade visivo. Il cursore funziona perfettamente senza JavaScript, la proprietà --track-fill
non è obbligatoria, semplicemente non avrà uno stile di riempimento se non è presente. Se JavaScript è disponibile, compila la proprietà personalizzata osservando anche le eventuali modifiche apportate dall'utente, sincronizzando la proprietà personalizzata con il valore.
Ecco un ottimo
post su
CSS-Tricks di Ana
Tudor, che mostra una soluzione solo CSS per preenchimento
della traccia. Ho trovato molto stimolante anche questo
elemento range
.
Stili miniature
input[type="range"]::-webkit-slider-thumb {
appearance: none; /* clear styles, make way for mine */
cursor: ew-resize; /* cursor style to support drag direction */
border: 3px solid var(--surface3);
block-size: var(--thumb-size);
inline-size: var(--thumb-size);
margin-top: var(--thumb-offset);
border-radius: 50%;
background: var(--brand-bg-gradient) fixed;
}
La maggior parte di questi stili serve a creare un bel cerchio.
Anche in questo caso, viene visualizzato il gradiente di sfondo fisso che unifica i colori dinamici delle miniature, delle tracce e degli elementi SVG associati.
Ho separato gli stili per l'interazione per contribuire a isolare la box-shadow
tecnica utilizzata per l'evidenziazione al passaggio del mouse:
@custom-media --motionOK (prefers-reduced-motion: no-preference);
::-webkit-slider-thumb {
…
/* shadow spread is initally 0 */
box-shadow: 0 0 0 var(--thumb-highlight-size) var(--thumb-highlight-color);
/* if motion is OK, transition the box-shadow change */
@media (--motionOK) {
& {
transition: box-shadow .1s ease;
}
}
/* on hover/active state of parent, increase size prop */
@nest input[type="range"]:is(:hover,:active) & {
--thumb-highlight-size: 10px;
}
}
L'obiettivo era creare un'evidenziazione visiva animata e facile da gestire per i feedback degli utenti. Utilizzando un'ombra esterna, posso evitare di attivare il layout con l'effetto. Per farlo, creo un'ombra non sfocata che corrisponde alla forma circolare dell'elemento miniatura. Poi modifico e faccio la transizione delle dimensioni dell'espansione al passaggio del mouse.
Se solo l'effetto di evidenziazione fosse così facile sulle caselle di controllo…
Selettori cross-browser
Ho scoperto di aver bisogno di questi selettori -webkit-
e -moz-
per ottenere coerenza tra i browser:
input[type="range"] {
&::-webkit-slider-runnable-track {}
&::-moz-range-track {}
&::-webkit-slider-thumb {}
&::-moz-range-thumb {}
}
Casella di controllo personalizzata
Dato il seguente elemento input HTML, ti mostrerò come ho personalizzato il suo aspetto:
<input type="checkbox">
Questo elemento è composto da tre parti che dobbiamo personalizzare:
Elemento casella di controllo
input[type="checkbox"] {
inline-size: var(--space-sm); /* increase width */
block-size: var(--space-sm); /* increase height */
outline-offset: 5px; /* focus style enhancement */
accent-color: var(--brand); /* tint the input */
position: relative; /* prepare for an absolute pseudo element */
transform-style: preserve-3d; /* create a 3d z-space stacking context */
margin: 0;
cursor: pointer;
}
Gli stili transform-style
e position
preparano lo pseudo-elemento che introdurremo più avanti per applicare uno stile al Video Ricordo. Per il resto, si tratta principalmente di piccole opinioni personali sullo stile. Mi piace che il cursore sia un cursore, mi piacciono gli offset dell'outline, le caselle di controllo predefinite sono troppo piccole e, se accent-color
è supportato, inserisci queste caselle di controllo nella combinazione di colori del brand.
Etichette delle caselle di controllo
È importante fornire etichette per le caselle di controllo per due motivi. La prima è rappresentare la finalità del valore della casella di controllo, per rispondere alla domanda "On o off per cosa?". In secondo luogo, per l'esperienza utente, gli utenti web si sono abituati a interagire con le caselle di controllo tramite le etichette associate.
<input type="checkbox" id="text-notifications" name="text-notifications" >
<label for="text-notifications"> <h3>Text Messages</h3> <small>Get notified about all text messages sent to your device</small> </label>
Nell'etichetta, inserisci un attributo for
che rimandi a una casella di controllo tramite ID: <label for="text-notifications">
. Nella casella di controllo, inserisci due volte il nome e l'ID per assicurarti che venga trovata con vari strumenti e tecnologie, come un mouse o uno screen reader:
<input type="checkbox" id="text-notifications" name="text-notifications">
.
:hover
, :active
e altri sono inclusi senza costi con la connessione, aumentando le modalità di interazione con il tuo modulo.
Evidenziazione casella di controllo
Voglio mantenere le mie interfacce coerenti e l'elemento del cursore ha un'ottima opzione di evidenziazione della miniatura che vorrei utilizzare con la casella di controllo. La miniatura è stata in grado di utilizzare box-shadow
e la sua proprietà spread
per aumentare e diminuire l'ombreggiatura. Tuttavia, questo effetto non funziona qui perché le nostre caselle di controllo sono e devono essere quadrate.
Ho potuto ottenere lo stesso effetto visivo con uno pseudo elemento e un quantità sfortunata di CSS complicato:
@custom-media --motionOK (prefers-reduced-motion: no-preference);
input[type="checkbox"]::before {
--thumb-scale: .01; /* initial scale of highlight */
--thumb-highlight-size: var(--space-xl);
content: "";
inline-size: var(--thumb-highlight-size);
block-size: var(--thumb-highlight-size);
clip-path: circle(50%); /* circle shape */
position: absolute; /* this is why position relative on parent */
top: 50%; /* pop and plop technique (https://web.dev/centering-in-css#5-pop-and-plop) */
left: 50%;
background: var(--thumb-highlight-color);
transform-origin: center center; /* goal is a centered scaling circle */
transform: /* order here matters!! */
translateX(-50%) /* counter balances left: 50% */
translateY(-50%) /* counter balances top: 50% */
translateZ(-1px) /* PUTS IT BEHIND THE CHECKBOX */
scale(var(--thumb-scale)) /* value we toggle for animation */
;
will-change: transform;
@media (--motionOK) { /* transition only if motion is OK */
& {
transition: transform .2s ease;
}
}
}
/* on hover, set scale custom property to "in" state */
input[type="checkbox"]:hover::before {
--thumb-scale: 1;
}
Creare un pseudo-elemento cerchio è un'operazione semplice, ma posizionarlo dietro l'elemento a cui è associato è stato più difficile. Ecco le immagini prima e dopo la correzione:
Si tratta di una microinterazione, ma per me è importante mantenere la coerenza visiva. La tecnica di ridimensionamento delle animazioni è la stessa che utilizziamo in altri luoghi. Impostiamo una proprietà personalizzata su un nuovo valore e lasciamo che il CSS la trasformi in base alle preferenze di movimento. La funzionalità principale è translateZ(-1px)
. L'elemento
padre ha creato uno spazio 3D e questo pseudo-elemento secondario vi ha avuto accesso collocandosi leggermente più indietro nello spazio Z.
Accessibilità
Il video di YouTube mostra una grande dimostrazione delle interazioni con il mouse, la tastiera e lo screen reader per questo componente delle impostazioni. Ti fornirò alcuni dettagli.
Scelte di elementi HTML
<form>
<header>
<fieldset>
<picture>
<label>
<input>
Ognuno di questi contiene suggerimenti per lo strumento di navigazione dell'utente. Alcuni elementi forniscono suggerimenti di interazione, altri collegano l'interattività e altri contribuiscono a definire la struttura ad albero dell'accessibilità in cui naviga uno screen reader.
Attributi HTML
Possiamo nascondere gli elementi non necessari per gli screen reader, in questo caso l'icona accanto al cursore:
<picture aria-hidden="true">
Il video qui sopra mostra il flusso dello screen reader su Mac OS. Nota come lo stato attivo si sposta direttamente da un cursore all'altro. Questo perché abbiamo nascosto l'icona che potrebbe essere stata una fermata sulla strada per il cursore successivo. Senza questo attributo, un utente dovrebbe interrompersi, ascoltare e passare oltre l'immagine che potrebbe non essere in grado di vedere.
L'SVG è un insieme di operazioni matematiche, aggiungiamo un elemento <title>
per un titolo libero del passaggio del mouse e un commento leggibile da una persona su ciò che viene creato dalle operazioni matematiche:
<svg viewBox="0 0 24 24">
<title>A note icon</title>
<path d="M12 3v10.55c-.59-.34-1.27-.55-2-.55-2.21 0-4 1.79-4 4s1.79 4 4 4 4-1.79 4-4V7h4V3h-6z"/>
</svg>
A parte questo, abbiamo utilizzato HTML sufficientemente contrassegnato in modo che il modulo venga testato molto bene su mouse, tastiera, controller per videogiochi e screen reader.
JavaScript
Ho già spiegato come il colore di riempimento del canale veniva gestito da JavaScript, quindi ora diamo un'occhiata al codice JavaScript relativo a <form>
:
const form = document.querySelector('form');
form.addEventListener('input', event => {
const formData = Object.fromEntries(new FormData(form));
console.table(formData);
})
Ogni volta che viene interagito con il modulo e modificato, la console lo registra come oggetto in una tabella per facilitarne la revisione prima dell'invio a un server.
Conclusione
Ora che sai come ho fatto, come faresti? Si tratta di un'architettura di componenti molto interessante. Chi realizzerà la prima versione con gli slot nel suo framework preferito? 🙂
Diversifichiamo i nostri approcci e impariamo tutti i modi per creare sul web. Crea una demo, twittami i link e io la aggiungerò alla sezione Remix della community di seguito.