Una panoramica di base su come creare un componente delle schede simile a quelli presenti nelle app per iOS e Android.
In questo post voglio condividere alcune idee sulla creazione di un componente Tabs per il web che sia reattivo, supporti più input di dispositivi e funzioni su tutti i browser. Prova la demo.
Se preferisci i video, ecco una versione di questo post su YouTube:
Panoramica
Le schede sono un componente comune dei sistemi di progettazione, ma possono assumere molte forme. In precedenza, le schede desktop erano basate sull'elemento <frame>
, ma ora abbiamo componenti per dispositivi mobili fluidi che animano i contenuti in base alle proprietà fisiche.
Tutti cercano di fare la stessa cosa: risparmiare spazio.
Oggi, gli elementi essenziali di un'esperienza utente con schede sono un'area di navigazione con pulsanti che attiva/disattiva la visibilità dei contenuti in un riquadro di visualizzazione. Molte diverse area di contenuti condividono lo stesso spazio, ma vengono presentate in modo condizionale in base al pulsante selezionato nella navigazione.
Tattiche sul web
Nel complesso, ho trovato questo componente abbastanza semplice da creare, grazie a alcune funzionalità fondamentali della piattaforma web:
scroll-snap-points
per interazioni eleganti con scorrimento e tastiera con posizioni di arresto dello scorrimento appropriate- Link diretti tramite hash dell'URL per supportare la condivisione e l'ancoraggio dello scorrimento in-page gestito dal browser
- Supporto per screen reader con markup degli elementi
<a>
eid="#hash"
prefers-reduced-motion
per attivare le transizioni con dissolvenza incrociata e lo scorrimento istantaneo all'interno della pagina- La funzionalità web
@scroll-timeline
in bozza per sottolineare e cambiare dinamicamente il colore della scheda selezionata
Il codice HTML
Fondamentalmente, l'esperienza utente è la seguente: fai clic su un link, l'URL rappresenta lo stato della pagina nidificata e poi vedi l'aggiornamento dell'area dei contenuti quando il browser scorre fino all'elemento corrispondente.
Sono presenti alcuni elementi di contenuti strutturali: link e :target
. Abbiamo bisogno di un elenco di link, per il quale è ideale un <nav>
, e di un elenco di elementi <article>
, per il quale è ideale un <section>
. Ogni hash del link corrisponderà a una sezione,
consentendo al browser di scorrere i contenuti tramite l'ancoraggio.
Ad esempio, facendo clic su un link, l'articolo :target
viene automaticamente messo a fuoco in Chrome 89, senza bisogno di JS. L'utente può quindi scorrere i contenuti dell'articolo con il suo dispositivo di input come sempre. Si tratta di contenuti aggiuntivi, come indicato nel markup.
Ho utilizzato il seguente markup per organizzare le schede:
<snap-tabs>
<header>
<nav>
<a></a>
<a></a>
<a></a>
<a></a>
</nav>
</header>
<section>
<article></article>
<article></article>
<article></article>
<article></article>
</section>
</snap-tabs>
Posso stabilire connessioni tra gli elementi <a>
e <article>
con le proprietà href
e id
come segue:
<snap-tabs>
<header>
<nav>
<a href="#responsive"></a>
<a href="#accessible"></a>
<a href="#overscroll"></a>
<a href="#more"></a>
</nav>
</header>
<section>
<article id="responsive"></article>
<article id="accessible"></article>
<article id="overscroll"></article>
<article id="more"></article>
</section>
</snap-tabs>
Poi ho riempito gli articoli con quantità diverse di Lorem e i link con titoli di lunghezza e immagini diverse. Con i contenuti su cui lavorare, possiamo iniziare il layout.
Layout scorrevoli
Questo componente contiene tre diversi tipi di aree di scorrimento:
- La barra di navigazione (rosa) è scorrevole orizzontalmente
- L'area dei contenuti (blu) è scorrevole orizzontalmente
- Ogni articolo (verde) è scorrevole verticalmente.
Esistono due tipi di elementi coinvolti nello scorrimento:
- Una finestra
Una casella con dimensioni definite che ha lo stile della proprietàoverflow
. - Una superficie di grandi dimensioni
In questo layout, si tratta dei contenitori dell'elenco: link di navigazione, articoli della sezione e contenuti degli articoli.
Layout <snap-tabs>
Il layout di primo livello che ho scelto era flessibile (Flexbox). Ho impostato la direzione su
column
, quindi l'intestazione e la sezione sono ordinate verticalmente. Questa è la nostra prima finestra di scorrimento e nasconde tutto con overflow nascosto. A breve, l'intestazione e la sezione utilizzeranno lo scorrimento oltre il limite, come singole zone.
<snap-tabs> <header></header> <section></section> </snap-tabs>
snap-tabs { display: flex; flex-direction: column; /* establish primary containing box */ overflow: hidden; position: relative; & > section { /* be pushy about consuming all space */ block-size: 100%; } & > header { /* defend againstneeding 100% */ flex-shrink: 0; /* fixes cross browser quarks */ min-block-size: fit-content; } }
Torna al diagramma colorato con tre scorrimenti:
<header>
è ora pronto per essere il contenitore scorrevole (rosa).<section>
è pronto per essere il contenitore scorrevole (blu).
I frame che ho evidenziato di seguito con VisBug ci aiutano a vedere le finestre create dai contenitori di scorrimento.
Layout <header>
schede
Il layout successivo è quasi lo stesso: utilizzo Flex per creare un ordinamento verticale.
<snap-tabs> <header> <nav></nav> <span class="snap-indicator"></span> </header> <section></section> </snap-tabs>
header { display: flex; flex-direction: column; }
.snap-indicator
deve spostarsi in orizzontale insieme al gruppo di link. Questo layout dell'intestazione consente di impostare lo scenario. Nessun elemento con posizionamento assoluto.
Poi, gli stili di scorrimento. A quanto pare possiamo condividere gli stili di scorrimento tra le due aree di scorrimento orizzontali (intestazione e sezione), quindi ho creato una classe di utilità, .scroll-snap-x
.
.scroll-snap-x {
/* browser decide if x is ok to scroll and show bars on, y hidden */
overflow: auto hidden;
/* prevent scroll chaining on x scroll */
overscroll-behavior-x: contain;
/* scrolling should snap children on x */
scroll-snap-type: x mandatory;
@media (hover: none) {
scrollbar-width: none;
&::-webkit-scrollbar {
width: 0;
height: 0;
}
}
}
Ognuno richiede uno spazio aggiuntivo sull'asse x, un contenimento dello scorrimento per bloccare lo scorrimento eccessivo, barre di scorrimento nascoste per i dispositivi touch e infine lo snap dello scorrimento per bloccare le aree di presentazione dei contenuti. L'ordine delle schede della tastiera è accessibile e tutte le interazioni consentono di concentrarsi in modo naturale. I contenitori con scorrimento automatico offrono anche un'interazione di tipo carosello con la tastiera.
Layout intestazione schede <nav>
I link di navigazione devono essere disposti in una riga, senza interruzioni di riga, centrati verticalmente e ogni elemento del link deve essere agganciato al contenitore di scorrimento. Lavori rapidamente per il CSS 2021!
<nav> <a></a> <a></a> <a></a> <a></a> </nav>
nav { display: flex; & a { scroll-snap-align: start; display: inline-flex; align-items: center; white-space: nowrap; } }
Ogni link ha una dimensione e uno stile specifico, quindi il layout di navigazione deve solo specificare la direzione e il flusso. Le larghezze uniche negli elementi di navigazione rendono divertente la transizione tra le schede in quanto l'indicatore regola la propria larghezza in base al nuovo target. A seconda del numero di elementi, il browser mostrerà o meno una barra di scorrimento.
Layout schede <section>
Questa sezione è un articolo flessibile e deve essere il consumatore dominante di spazio. Deve inoltre creare colonne in cui inserire gli articoli. Ancora una volta, un rapido lavoro
per CSS 2021. block-size: 100%
estende questo elemento in modo da riempire
il più possibile l'elemento principale; quindi, per il proprio layout, crea una serie di
colonne che hanno una larghezza pari a 100%
rispetto a quella principale. Le percentuali sono perfette in questo caso
perché abbiamo scritto vincoli rigidi per l'elemento principale.
<section> <article></article> <article></article> <article></article> <article></article> </section>
section { block-size: 100%; display: grid; grid-auto-flow: column; grid-auto-columns: 100%; }
È come se dicessimo "espandi verticalmente il più possibile, in modo aggressivo"
(ricorda l'intestazione impostata su flex-shrink: 0
: è una difesa contro questa
spinta all'espansione), che imposta l'altezza della riga per un insieme di colonne a altezza intera. Lo stile auto-flow
indica alla griglia di disporre sempre gli elementi secondari in una riga orizzontale, senza a capo, esattamente ciò che vogliamo; per andare oltre la finestra principale.
A volte faccio fatica ad arrossire la testa. Questo elemento della sezione si inserisce in una casella, ma ha anche creato un insieme di caselle. Spero che le immagini e le spiegazioni ti siano utili.
Layout <article>
schede
L'utente deve essere in grado di scorrere i contenuti dell'articolo e le barre di scorrimento devono essere visualizzate solo in caso di overflow. Questi elementi dell'articolo sono in una posizione ordinata. Sono contemporaneamente elementi principali e secondari dello scorrimento. Il browser sta gestendo alcune interazioni complesse con tocco, mouse e tastiera.
<article> <h2></h2> <p></p> <p></p> <h2></h2> <p></p> <p></p> ... </article>
article { scroll-snap-align: start; overflow-y: auto; overscroll-behavior-y: contain; }
Ho scelto di far scattare gli articoli all'interno dello scorrevole principale. Mi piace molto il modo in cui gli elementi dei link di navigazione e gli elementi degli articoli si agganciano all'avvio in linea dei rispettivi contenitori di scorrimento. È come un rapporto armonioso.
L'articolo è un elemento secondario della griglia e le sue dimensioni sono predeterminate come l'area dell'area visibile che vogliamo fornire l'esperienza utente di scorrimento. Ciò significa che non ho bisogno di stili di altezza o larghezza, devo solo definire come si verifica il superamento. Ho impostato overflow-y su auto, e poi ho intrappolato anche le interazioni con lo scorrimento con la pratica proprietà overscroll-behavior.
Riepilogo delle 3 aree di scorrimento
Di seguito ho scelto l'opzione "Mostra sempre le barre di scorrimento" nelle impostazioni di sistema. Penso che sia molto importante che il layout funzioni con questa impostazione attivata, poiché spetta a me esaminare il layout e l'orchestrazione dello scorrimento.
Penso che la visualizzazione della barra laterale in questo componente aiuti a mostrare chiaramente dove si trovano le aree di scorrimento, la direzione supportata e il modo in cui interagiscono tra di loro. Valuta in che modo ciascuno di questi frame della finestra di scorrimento è anche un elemento principale flex o grid di un layout.
DevTools può aiutarci a visualizzare quanto segue:
I layout di scorrimento sono completi: con aggancio, collegabili tramite link diretti e accessibili da tastiera. Una base solida per miglioramenti dell'esperienza utente, stile e soddisfazione.
Funzionalità in evidenza
I riquadri secondari a scorrimento bloccati mantengono la posizione bloccata durante il ridimensionamento. Ciò significa che JavaScript non dovrà visualizzare nulla durante la rotazione dei dispositivi o le dimensioni del browser. Prova la funzionalità nella Modalità dispositivo di Chromium DevTools selezionando una modalità diversa da Adattabile e ridimensionando il riquadro del dispositivo. Nota che l'elemento rimane visibile e bloccato con i suoi contenuti. Questa funzionalità è stata disponibile da quando Chromium ha aggiornato la propria implementazione in modo da renderla conforme alle specifiche. Ecco un post del blog in merito.
Animazione
L'obiettivo dell'animazione è collegare chiaramente le interazioni con il feedback della UI. In questo modo, l'utente può trovare (si spera) senza problemi tutti i contenuti. aggiungerò il movimento con uno scopo e condizionale. Ora gli utenti possono specificare le proprie preferenze di movimento nel sistema operativo e mi piace molto rispondere alle loro preferenze nelle mie interfacce.
Collegherò un'evidenziazione della scheda alla posizione di scorrimento dell'articolo. L'aggancio non è solo un allineamento estetico, ma serve anche ad ancorare l'inizio e la fine di un'animazione.
In questo modo, <nav>
, che funge da
mini-mappa, rimane collegato ai contenuti.
Controlleremo la preferenza di movimento dell'utente sia da CSS che da JS. Esistono alcuni posti fantastici da tenere in considerazione.
Comportamento di scorrimento
C'è un'opportunità per migliorare il comportamento dei movimenti sia di :target
sia di
element.scrollIntoView()
. Per impostazione predefinita, è istantaneo. Il browser imposta solo la posizione di scorrimento. E se volessimo passare a quella posizione di scorrimento, instead of blink there?
@media (prefers-reduced-motion: no-preference) {
.scroll-snap-x {
scroll-behavior: smooth;
}
}
Poiché stiamo introducendo il movimento, e un movimento che l'utente non controlla (come lo scorrimento), applichiamo questo stile solo se l'utente non ha preferenze nel suo sistema operativo in merito al movimento ridotto. In questo modo, introduciamo lo scorrimento di scorrimento solo per le persone che ne sono d'accordo.
Indicatore delle schede
Lo scopo di questa animazione è aiutare ad associare l'indicatore allo stato
degli elementi. Ho deciso di applicare stili di transizione sfumata border-bottom
a colori per gli utenti che preferiscono ridurre il movimento e un'animazione di scorrimento con transizione sfumata e scorrimento collegato per gli utenti che non hanno problemi con il movimento.
In Chromium DevTools, posso attivare/disattivare la preferenza e mostrare i 2 diversi stili di transizione. Mi sono divertito molto a realizzarlo.
@media (prefers-reduced-motion: reduce) {
snap-tabs > header a {
border-block-end: var(--indicator-size) solid hsl(var(--accent) / 0%);
transition: color .7s ease, border-color .5s ease;
&:is(:target,:active,[active]) {
color: var(--text-active-color);
border-block-end-color: hsl(var(--accent));
}
}
snap-tabs .snap-indicator {
visibility: hidden;
}
}
Nascondo il pulsante .snap-indicator
quando l'utente preferisce la modalità Riduzione movimento, poiché non mi serve più. Poi lo sostituisco con gli stili border-block-end
e un
transition
. Inoltre, nell'interazione con le schede, tieni presente che l'elemento di navigazione attivo non solo ha un'evidenziazione del brand in sottolineato, ma anche il colore del testo è più scuro. L'elemento attivo ha un contrasto del colore del testo più elevato e un'illuminazione sottoscocca brillante.
Bastano alcune righe di CSS aggiuntive per far sentire una persona vista (nel senso che rispettiamo attentamente le sue preferenze di movimento). Mi piace un sacco.
@scroll-timeline
Nella sezione precedente ti ho mostrato come gestisco gli stili di transizione graduale con movimento ridotto e in questa sezione ti mostrerò come ho collegato l'indicatore e un'area di scorrimento. Ecco alcune funzionalità sperimentali divertenti che verranno implementate a breve. Spero che tu sia tanto entusiasta quanto me.
const { matches:motionOK } = window.matchMedia(
'(prefers-reduced-motion: no-preference)'
);
Per prima cosa controllo la preferenza di movimento dell'utente da JavaScript. Se il risultato è false
, che indica che l'utente preferisce la riduzione del movimento, non verranno eseguiti effetti di movimento che collegano gli elementi di scorrimento.
if (motionOK) {
// motion based animation code
}
Al momento della stesura di questo documento, il supporto dei browser per
@scroll-timeline
non è disponibile. Si tratta di una
bozza di specifica con solo
implementazioni sperimentali. Tuttavia, ha un polyfill che utilizzo in questa demo.
ScrollTimeline
Sebbene sia CSS sia JavaScript possano creare schemi di scorrimento, ho attivato JavaScript per poter utilizzare le misurazioni degli elementi in tempo reale nell'animazione.
const sectionScrollTimeline = new ScrollTimeline({
scrollSource: tabsection, // snap-tabs > section
orientation: 'inline', // scroll in the direction letters flow
fill: 'both', // bi-directional linking
});
Voglio che un elemento segua la posizione di scorrimento di un altro e, creando un
ScrollTimeline
, definisco il gestore del link di scorrimento, ovvero il scrollSource
.
In genere, un'animazione sul web viene eseguita in base a un intervallo di tempo globale, ma con un sectionScrollTimeline
personalizzato in memoria posso cambiare tutto.
tabindicator.animate({
transform: ...,
width: ...,
}, {
duration: 1000,
fill: 'both',
timeline: sectionScrollTimeline,
}
);
Prima di passare ai fotogrammi chiave dell'animazione, ritengo sia importante sottolineare che il cursore dello scorrimento, tabindicator
, verrà animato in base a una sequenza temporale personalizzata, ovvero lo scorrimento della nostra sezione. Il collegamento viene completato, ma
manca l'ingrediente finale, ovvero i punti stateful tra cui animarsi, noti anche come
frame chiave.
Fotogrammi chiave dinamici
Esiste un modo molto efficace per creare animazioni con CSS puramente dichiarativo
@scroll-timeline
, ma l'animazione che ho scelto era troppo dinamica. Non è possibile eseguire la transizione tra le larghezze auto
e non è possibile creare dinamicamente un numero di keyframe in base alla durata dei figli.
JavaScript sa come ottenere queste informazioni, quindi eseguiremo l'iterazione sui figli e acquisiremo i valori calcolati in fase di esecuzione:
tabindicator.animate({
transform: [...tabnavitems].map(({offsetLeft}) =>
`translateX(${offsetLeft}px)`),
width: [...tabnavitems].map(({offsetWidth}) =>
`${offsetWidth}px`)
}, {
duration: 1000,
fill: 'both',
timeline: sectionScrollTimeline,
}
);
Per ogni tabnavitem
, destruttura la posizione offsetLeft
e restituisci una stringa
che la utilizza come valore translateX
. Vengono creati 4 fotogrammi chiave di trasformazione per l'animazione. Lo stesso vale per la larghezza: a ciascuna viene chiesto qual è la larghezza dinamica
e poi viene utilizzata come valore del fotogramma chiave.
Ecco un esempio di output, in base ai miei caratteri e alle preferenze del browser:
Fotogrammi chiave TranslateX:
[...tabnavitems].map(({offsetLeft}) =>
`translateX(${offsetLeft}px)`)
// results in 4 array items, which represent 4 keyframe states
// ["translateX(0px)", "translateX(121px)", "translateX(238px)", "translateX(464px)"]
Fotogrammi chiave della larghezza:
[...tabnavitems].map(({offsetWidth}) =>
`${offsetWidth}px`)
// results in 4 array items, which represent 4 keyframe states
// ["121px", "117px", "226px", "67px"]
Per riepilogare la strategia, l'indicatore della scheda ora verrà animato su quattro fotogrammi chiave, a seconda della posizione di snap dello scorrimento dello scorrevole della sezione. I punti di aggancio creano una chiara demarcazione tra i fotogrammi chiave e contribuiscono notevolmente all'effetto sincronizzato dell'animazione.
L'utente avvia l'animazione durante l'interazione, vedendo la larghezza e la posizione dell'indicatore cambiare da una sezione all'altra, monitorando perfettamente con lo scorrimento.
Potresti non averlo notato, ma sono molto orgoglioso della transizione di colore quando l'elemento di navigazione evidenziato viene selezionato.
Il grigio più chiaro non selezionato appare ancora più arretrato quando l'elemento evidenziato ha un maggiore contrasto. È comune usare il colore di transizione per il testo, ad esempio al passaggio del mouse e quando viene selezionato, ma è necessario passare tale colore allo scorrimento, sincronizzato con l'indicatore di sottolineatura.
Ecco come ho fatto:
tabnavitems.forEach(navitem => {
navitem.animate({
color: [...tabnavitems].map(item =>
item === navitem
? `var(--text-active-color)`
: `var(--text-color)`)
}, {
duration: 1000,
fill: 'both',
timeline: sectionScrollTimeline,
}
);
});
Ogni link di navigazione tra le schede ha bisogno di questa nuova animazione di colore, che segue la stessa sequenza temporale di scorrimento dell'indicatore di sottolineatura. Io uso la stessa sequenza temporale di prima: dal momento che il suo ruolo è emettere un segno di spunta sulla rotellina, possiamo usarlo in qualsiasi tipo di animazione. Come ho fatto prima, creo 4 fotogrammi chiave nel loop e restituisco i colori.
[...tabnavitems].map(item =>
item === navitem
? `var(--text-active-color)`
: `var(--text-color)`)
// results in 4 array items, which represent 4 keyframe states
// [
"var(--text-active-color)",
"var(--text-color)",
"var(--text-color)",
"var(--text-color)",
]
Il fotogramma chiave con il colore var(--text-active-color)
evidenzia il link,
altrimenti è un colore di testo standard. Il loop nidificato in questo punto rende il tutto relativamente semplice, dato che il loop esterno è ogni elemento di navigazione e il loop interno è i fotogrammi chiave personali di ogni elemento di navigazione. Controllo se l'elemento loop esterno è uguale
al loop interno e lo uso per sapere quando è selezionato.
Mi sono divertito molto a scriverlo. Tantissimo.
Ulteriori miglioramenti a JavaScript
Vale la pena ricordare che la parte principale di ciò che ti mostro qui funziona senza JavaScript. Detto questo, vediamo come possiamo migliorarlo quando JS è disponibile.
Link diretti
I link diretti sono più comunemente utilizzati per dispositivi mobili, ma credo che il loro scopo sia
illustrato qui perché puoi condividere un URL con i contenuti di una scheda. Il browser in-page raggiungerà l'ID corrispondente nell'hash dell'URL. Ho scoperto che questo gestore onload
ha applicato l'effetto su più piattaforme.
window.onload = () => {
if (location.hash) {
tabsection.scrollLeft = document
.querySelector(location.hash)
.offsetLeft;
}
}
Sincronizzazione della fine dello scorrimento
I nostri utenti non fanno sempre clic o non usano sempre una tastiera, a volte semplicemente scorrono liberamente, come dovrebbero poter fare. Quando lo scorrimento della sezione si interrompe, la posizione in cui si ferma deve corrispondere a quella nella barra di navigazione in alto.
Ecco come attendo il termine dello scorrimento:
js
tabsection.addEventListener('scroll', () => {
clearTimeout(tabsection.scrollEndTimer);
tabsection.scrollEndTimer = setTimeout(determineActiveTabSection, 100);
});
Ogni volta che viene eseguito lo scorrimento delle sezioni, elimina il relativo timeout, se esistente, e avviane uno nuovo. Quando non viene più eseguito lo scorrimento delle sezioni, non cancellare il timeout e attivalo 100 ms dopo l'interruzione. Quando viene attivata, chiama la funzione che cerca di capire dove è stato interrotto l'utente.
const determineActiveTabSection = () => {
const i = tabsection.scrollLeft / tabsection.clientWidth;
const matchingNavItem = tabnavitems[i];
matchingNavItem && setActiveTab(matchingNavItem);
};
Supponendo che la scorrimento sia agganciata, la divisione della posizione di scorrimento attuale dalla larghezza dell'area di scorrimento dovrebbe restituire un numero intero e non un decimale. Poi provo a recuperare un navitem dalla nostra cache tramite questo indice calcolato e, se ne trova uno, invio la corrispondenza da impostare come attiva.
const setActiveTab = tabbtn => {
tabnav
.querySelector(':scope a[active]')
.removeAttribute('active');
tabbtn.setAttribute('active', '');
tabbtn.scrollIntoView();
};
Per impostare la scheda attiva, inizia eliminando qualsiasi scheda attualmente attiva, quindi assegna all'elemento di navigazione in arrivo l'attributo dello stato attivo. La chiamata a scrollIntoView()
ha un'interazione divertente con il CSS che vale la pena di notare.
.scroll-snap-x {
overflow: auto hidden;
overscroll-behavior-x: contain;
scroll-snap-type: x mandatory;
@media (prefers-reduced-motion: no-preference) {
scroll-behavior: smooth;
}
}
Nel CSS dell'utilità di scorrimento orizzontale, abbiamo nidificato una query multimediale che applica lo scorrimento smooth
se l'utente è resistente al movimento. JavaScript può richiamare liberamente
gli elementi di scorrimento per visualizzarli, mentre il CSS può gestire la UX in modo dichiarativo.
Un bel fiammifero che a volte fanno.
Conclusione
Ora che sai come ho fatto, come faresti? Ciò rende l'architettura dei componenti divertente. Chi creerà la prima versione con gli slot nel suo framework preferito? 🙂
Diversificaamo i nostri approcci e impariamo tutti i modi per creare sul web. Crea un Glitch e twittami la tua versione e la aggiungerò alla sezione Remix della community di seguito.
Remix della community
- @devnook, @rob_dodson e @DasSurma con Web Components: articolo.
- @jhvanderschee con i pulsanti: Codepen.