Questa è la traduzione dell'articolo CSS Sprites2 - It's JavaScript Time di Dave Sheapubblicato originariamente su A List Apart il 26 Agosto 2008. La traduzione viene qui presentata con il consenso dell'editore e dell'autore.
Il movimento è spesso ciò che spesso fa la differenza tra i siti basati su Flash e quelli standard. Le interfacce in Flash hanno sempre dato l'impressione di essere più vive, rispondendo alle interazioni dell'utente in un modo dinamico che i siti web basati sugli standard non sono in grado di replicare.
In seguito tutto ciò è però cambiato con l'avvento massiccio di effetti dinamici nelle interfacce resi possibili dall'utilizzo di librerie che rendono semplice la loro implementazione. Parlo di librerie come Prototype, Scriptaculous, MooTools, YUI, MochiKit (e potrei andare avanti). È dunque venuto il momento di rivisitare la tecnica dei CSS Sprites risalente a quattro anni fa ormai e vedere se non possiamo inserire un po' di movimento.
Gli esempi qui sotto mostrano i CSS Sprites2 in azione, è la tecnica che affronteremo in questo articolo (nota del traduttore: in questa traduzione è riportato solo uno screenshot dell'esempio; potete vederlo in azione facendo riferimento all'articolo originale):
Ecco jQuery
Eccoci dunque al primo avviso: ci appoggeremo a jQuery per ottenere il risultato desiderato. jQuery è una libreria matura che fa tutte le cose interessanti che fanno anche le altre librerie ed ha un vantaggio addizionale che si presta molto bene per estendere come vogliamo la tecnica degli Sprites CSS: jQuery ci consente di selezionare gli elementi presenti sulla pagina usando una sintassi simile a quella dei CSS che conosciamo già.
Dobbiamo ovviamente accennare alla questione non triviale dei kilobyte extra che la libreria aggiungerà al peso della pagina al momento del suo caricamento. Un file Javascript esterno è naturalmente conservato nella cache del browser, così si tratta di scaricarlo una sola volta nel momento in cui un utente giunge per la prima volta sul vostro sito. La versione più compatta di jQuery pesa 15kb. È una cosa di cui tenere inevitabilmente conto. Se state già usando jQuery sul vostro sito per altre ragioni, allora la preoccupazione non sussiste. Se siete interessati ad aggiungerla solo per sfruttare questa tecnica, allora considerate il peso del file e decidete se ne vale la pena (dato che Google sta ora ospitando e servendo jQuery dai suoi server, potreste linkare la loro versione della libreria, come è stato fatto negli esempi che accompagnano questi articoli, e sperare che molti dei vostri utenti hanno già quell'URL nella cache dei loro browser).
E per le altre librerie Javascript? Non c'è assolutamente ragione per cui non possiate o dobbiate usarle; considerate questo articolo come una sorta di invito a portare i risultati di questa tecnica nella vostra libreria preferita.
Setup HTML e CSS di base
La prima cosa che vogliamo fare è creare uno di stato dei default, senza script per gli utenti che navigano senza Javascript (conosciamo l'articolo di Jeremy Keith da un po' di anni e siamo ora grandi fan del Javascript non intrusivo, ovviamente).
Abbiamo già un metodo basato sui soli CSS per il rollover, così iniziamo a costruire la nostra navigazione perché funzioni con un setup di base di Sprites CSS. E dato che siamo pigri, non rifaremo i rollover una seconda volta, riuseremo questo schema di base più tardi e aggiungeremo ad esso l'interazione con jQuery. Ci arriveremo presto.
Lascerò da parte i come e i perché dei CSS Sprites rimandandovi per essi all'articolo originale, ma ci sono alcune cose da chiarire. Iniziamo con il codice HTML. Fate attenzione alla struttura, ci torneremo spesso:
<ul class="nav current-about">
<li class="home"><a href="#">Home</a></li>
<li class="about"><a href="#">About</a></li>
<li class="services"><a href="#">Services</a></li>
<li class="contact"><a href="#">Contact</a></li>
</ul>
Ogni classe serve per uno scopo ben preciso: l'elemento ul
contenitore ha una classe .nav
che ci consente di usarla come target nel nostro CSS (e più tardi nel nostro Javascript) e una seconda classe .current-about
che useremo per indicare, all'interno della navigazione, quale pagina o sezione del sito stiamo vedendo in quel momento. Ciascun li
ha la sua specifica classe che useremo anche come target.
Bene. Il markup della nostra navigazione è una semplice e accessibile lista HTML, e abbiamo abbastanza classi per iniziare a lavorare sui nostri Sprites:
.nav {
width: 401px;
height: 48px;
background: url(../i/blue-nav.gif) no-repeat;
position: absolute;
top: 100px;
left: 100px;
}
Abbiamo impostato il valore per position su absolute
per cambiare lo spostamento del posizionamento sul li
invece che sull'elemento body
. Avremmo potuto usare il valore relative
per ottenere lo stesso risultato lasciando l'elemento con classe .nav
all'interno del flusso del documento. Ci sono buone ragioni per usare entrambe le soluzioni, ma per il momento useremo absolute
. Sulla questione absolute/relative si veda questo articolo di Douglas Bowman sull'argomento.
Il cuore della nostra tecnica per gli Sprites consiste nell'applicare un'immagine di sfondo a ciascuno degli item della navigazione e nel posizionarli assolutamente rispetto all'elemento ul
che li contiene:
.nav li a:link, .nav li a:visited {
position: absolute;
top: 0;
height: 48px;
text-indent: -9000px;
overflow: hidden;
}
.nav .home a:link, .nav .home a:visited {
left: 23px;
width: 76px;
}
.nav .home a:hover, .nav .home a:focus {
background: url(../i/blue-nav.gif) no-repeat -23px -49px;
}
.nav .home a:active {
background: url(../i/blue-nav.gif) no-repeat -23px -98px;
}
Andremo un po' avanti rispetto all'articolo originale definendo anche gli stati per :focus
e :active
. La prima è un'aggiunta minore per attivare l'immagine di hover quando un ancora è soggetta sia allo stato :hover
sia allo stato :focus
. La seconda aggiunge un nuovo stato quando l'utente clicca su un item della navigazione. Nessuna delle due aggiunte è essenziale, sebbene sia una buona idea definirle entrambe. La regola overflow: hidden;
è anch'essa nuova, è usata per impedire che alcuni browser estendano una cornice a puntini dalla posizione dell'elemento fino alla parte sinistra dello schermo dove si trova il testo indentato con un valore negativo.
Ecco il primo esempio, con il setup CSS di base per gli Sprites.
Siamo quindi al punto iniziale, un menu di navigazione basato sugli Sprites e funzionante, completo di segnalazione dell'item corrente. Ora estendiamolo.
Inizializzare jQuery
Notate che tutto ciò che segue qui sotto sarà inserito all'interno di una funzione jQuery che assicura che il codice venga eseguito solo quando l'intero documento è stato caricato:
$(document).ready(function(){
// everything goes here
});
Dal momento che il menu con i soli Sprites è la soluzione da offrire agli utenti quando Javascript è disabilitato, facciamo meglio a liberarci di quelle immagini di sfondo applicate nel CSS sull'hover dal momento che andremo a creare le nostre direttamente nello script qui sotto:
$(".nav").children("li").each(function() {
$(this).children("a").css({backgroundImage:"none"});
});
Con la prima riga di codice andiamo a rintracciare tutti gli elementi che hanno una classe .nav
e aggiungiamo una nuova funzione a tutti gli elementi li
figli che contengono. Quella funzione è definita nella seconda riga: rintraccia l'oggetto this
per tutti gli elementi a
figli. Se li trova, imposta la proprietà CSS background-image
sul valore none
. Dato il contesto, this
indica gli elementi li
su cui viene eseguita la funzione.
Ecco l'esempio 2, in cui disabilitiamo l'hover tramite CSS con jQuery.
Funziona, ma nel processo perdiamo anche il nostro item attualmente selezionato. Abbiamo così bisogno di effettuare un controllo per verificare quale item abbiamo identificato con la classe current-qualunquecosa
che abbiamo applicato all'elemento ul
parente e toglierlo dal processo di eliminazione dell'immagine di sfondo. Ecco allora che il precedente snippet di codice ha bisogno di qualche piccola aggiunta:
$(".nav").children("li").each(function() {
var current = "nav current-" + ($(this).attr("class"));
var parentClass = $(".nav").attr("class");
if (parentClass != current) {
$(this).children("a").css({backgroundImage:"none"});
}
});
Nella seconda riga si crea ora una variabile current
che usa ciascuna classe dei li
in sequenza per creare una stringa che dovrebbe corrispondere alle classi dell'elemento ul
se quel particolare li
rappresenta l'item attualmente selezionato. La terza riga crea una seconda variabile che legge il valore attuale direttamente dall'elemento ul
. Infine, la quarta riga confronta le due variabili. Se non corrispondono, solo allora impostiamo la proprietà background-image
dell'elemento a
. Ciò evita la perdita dell'evidenziazione dell'item attualmente selezionato.
Aggiungere gli eventi
Ora abbiamo bisogno di aggiungere una funzione a ciascun elemento li
per ogni evento di interazione che vogliamo modificare a livello di stile. Creiamo allora una funzione chiamata attachNavEvents
:
function attachNavEvents(parent, myClass) {
$(parent + " ." + myClass).mouseover(function() {
// do things here
}).mouseout(function() {
// do things here
}).mousedown(function() {
// do things here
}).mouseup(function() {
// do things here
});
}
Questa funzione prende due argomenti. Il primo è una stringa che contiene la classe dell'elemento parent, completa del punto iniziale. Il secondo è una stringa che contiene la classe del li
particolare a cui attaccare gli eventi. Combineremo entrambi nella prima riga della funzione per creare un selettore che ha come target lo stesso elemento del selettore discendente CSS di, per esempio, .nav
, .home
, etc.
Dal momento che jQuery ci consente di 'concatenare' più funzioni in un singolo oggetto, siamo in grado di creare tutte le funzioni attivate dagli eventi nello stesso tempo. Quello di 'concatenazione' è un concetto esclusivo di jQuery. È un po' particolare adattarvisi mentalmente e non è essenziale capire perché funziona, così se siete confusi vi basti sapere che funziona.
Ora attaccheremo queste funzioni a ciascun item della nostra navigazione. Lo snippet di codice che segue è un po' verboso, lo ottimizzeremo dopo, ma per ora eseguiamo la funzione su ogni li
. Come argomenti passeremo l'elemento parent di ognuno, così come la classe propria di ciascun li
:
attachNavEvents(".nav", "home");
attachNavEvents(".nav", "about");
attachNavEvents(".nav", "services");
attachNavEvents(".nav", "contact");
Tutto ciò non fa ancora molto, ma lo aggiusteremo.
Eccoci all'esempio 3: script con il setup di base per gli eventi.
La teoria
Spiegherò ora cosa accade. Seguitemi, è importante capire cosa accade in questo contesto perché avremo poi bisogno di modificare gli stili degli elementi che stiamo manipolando.
Per ciascuno dei link creeremo un nuovo elemento div
all'interno del li
che stiamo usando come target, div
che useremo per i nostri effetti con jQuery. Applicheremo l'immagine nav
a questo nuovo div usando la stessa regola per background-image
vista in precedenza per l'elemento a
all'interno dell'elemento li
condiviso come parent. Posizioneremo assolutamente il div all'interno dell'elemento parent. È più o meno un duplicato dell'elemento a
esistente visto nel setup dei nostri Sprites CSS. Attraverso una serie di prove, ho scoperto che la creazione di questo nuovo div risulta meno problematica dell'applicare direttamente l'effetto jQuery agli elementi esistenti, dunque è un passo necessario.
Lo stile per questo div deve essere definito nel CSS. Creeremo una nuova classe per questo li
(.nav-home
) basata sulla classe dell'elemento li
usato come target (così non avremo conflitti con quanto già creato), e aggiungeremo lo stile:
.nav-home {
position: absolute;
top: 0;
left: 23px;
width: 76px;
height: 48px;
background: url(../i/blue-nav.gif) no-repeat -23px -49px;
}
La pratica
Ora è tempo di aggiungere gli effetti. Quando scatta l'evento mouseover
, creeremo l'elemento div
e gli assegneremo la classe menzionata in precedenza. Dobbiamo far sì che esso sia inizialmente invisibile prima dell'effetto fade con cui apparirà, così useremo la funzione css
di jQuery per impostare un valore uguale a none
per la proprietà display
. Infine useremo la funzione jQuery fadeIn
per far apparire il div; passeremo un argomento pari a 200 per quella funzione per specificare la durata di questa animazione in millisecondi:
function attachNavEvents(parent, myClass) {
$(parent + " ." + myClass).mouseover(function() {
$(this).before('<div class="nav-' + myClass + »
'"></div>');
$("div.nav-" + myClass).css({display:"none"}) »
.fadeIn(200);
});
}
Quindi, andremo a fare la stessa cosa al contrario quando scatta l'evento mouseout
, facendo scomparire il div. Appena sarà scomparso, puliremo tutto rimuovendolo dal DOM. Ecco come dovrebbe essere la nostra funzione attachNavEvents
:
function attachNavEvents(parent, myClass) {
$(parent + " ." + myClass).mouseover(function() {
$(this).before('<div class="nav-' + myClass + »
'"></div>');
$("div.nav-" + myClass).css({display:"none"}) »
.fadeIn(200);
}).mouseout(function() {
// fade out & destroy pseudo-link
$("div.nav-" + myClass).fadeOut(200, function() {
$(this).remove();
});
});
}
Esempio 4: script per gli eventi hover.
Avremmo fatto meglio a fare anche qualcosa per gli eventi mousedown
e mouseup
se in precedenza avessimo definito un cambiamento per lo stato :active
nel CSS. Avremo bisogno di una classe diversa rispetto agli hover in modo che possa essere usata come unico target nel CSS. Cambiamo dunque la classe all'evento mousedown
. Vogliamo anche far tornare tutto com'era al mouseup
riportando alla luce lo stato :hover
dal momento che l'utente potrebbe anche non aver mosso il mouse dall'item di navigazione. Ecco come appare la funzione attachNavEvents
così modificata:
function attachNavEvents(parent, myClass) {
$(parent + " ." + myClass).mouseover(function() {
$(this).before('<div class="nav-' + myClass + »
'"></div>');
$("div.nav-" + myClass).css({display:"none"})»
.fadeIn(200);
}).mouseout(function() {
$("div.nav-" + myClass).fadeOut(200, function() {
$(this).remove();
});
}).mousedown(function() {
$("div.nav-" + myClass).attr("class", "nav-" »
+ myClass + "-click");
}).mouseup(function() {
$("div.nav-" + myClass + "-click").attr("class", »
"nav-" + myClass);
});
}
Possiamo riusare lo stile del div usato come hover modificando leggeremente la posizione dello sfondo per aggiustare la parte del nostro Sprite principale che viene mostrata al click:
.nav-home, .nav-home-click {
position: absolute;
top: 0;
left: 23px;
width: 76px;
height: 48px;
background: url(../i/blue-nav.gif) no-repeat -23px -49px;
}
.nav-home-click {
background: url(../i/blue-nav.gif) no-repeat -23px -98px;
}
Ora abbiamo gli hover, l'item attualmente selezionato e gli eventi attivati al click.
Esempio 5: mettere tutto insieme.
Altre considerazioni
Non siamo limitati al solo effetto fade. jQuery comprende anche una funzione slideUp/slideDown
che possiamo usare (è mostrata nel secondo esempio allegato a questo articolo). Oppure possiamo sbizzarrirci e creare animazioni personalizate basate sui CSS con la funzione animate
(come mostrato nel terzo esempio). Attenzione comunque ad animate
: i risultati non sono sempre perfetti come avrete potuto notare nell'esempio.
Per quel che concerne la funzionalità cross-browser, jQuery funziona sulla maggior parte dei browser moderni. Tutto quelle che vedete funziona in IE6+, Firefox, Safari, Opera, etc. Abbiamo anche tenuto conto di diversi scenari in termini di graceful degradation. Se l'utente ha Javascript disabilitato ottiene la versione di base con gli Sprites CSS. Se ha disabilitato sia Javascript sia i CSS ottiene una lista di base HTML. E otteniamo anche gli altri benefici di base degli Sprites CSS, dal momento che stiamo ancora usando una singola immagine per tutti i vari stati della navigazione e per gli effetti.
Sebbene non sia richiesto, vi suggerisco di usare alcuni accorgimenti. Una velocità delle animazioni di più di qualche centinaio di millisecondi può essere divertente come inizio, ma presto la cosa potrebbe stare sui nervi a qualcuno. Piuttosto, quindi, rallentate.
Un altro potenziale inconveniente in cui potreste incorrere è quando altro testo presente sulla pagina sembri 'lampeggiare' durante l'animazione. È una questione complicata che ha che fare con il rendering sub-pixel comune nei moderni sistemi operativi. La correzione migliore sembra consistere nell'applicare un valore della proprietà opacity
per forzare un particolare rendering del testo. Se aggiungete tutto ciò al vostro CSS il lampeggiamento dovrebbe scomparire:
p {
opacity 0.9999;
}
Questo codice è applicato nelle demo ai paragrafi, ma si può applicare a qualunque elemento.
Pacchetto pronto per l'uso
Non avete bisogno di ricordare alcuno script presente in questo articolo dal momento che è disponibile la funzione completa e pronta per l'uso che trovate nell'esempio finale. Usando il Javascript nel file HTML come riferimento avrete solo bisogno di modificare una piccola riga di Javascript per applicare la tecnica Sprites2 al vostro sito:
$(document).ready(function(){
generateSprites(".nav", "current-", true, 150, "slide");
});
La funzione generateSprites
prende cinque argomenti:
- La classe primaria dell'elemento
ul
parent, incluso il punto iniziale. - Il prefisso che state usando per gli item selezionati: per esempio, per una classe
selected-about
, usateselected-
come valore. - Un comando per indicare se state usando gli stili sullo stato
:active
. Impostatelo atrue
se avete definito lo stato:active
nei vostri CSS, altrimenti impostatelo afalse
. - La velocità dell'animazione.
- Lo stile dell'animazione che preferite sotto forma di stringa. Impostatelo su "slide" o "fade" (che è il valore di default).
Ecco l'esempio 6, con una semplice riga di Javascript da modificare grazie alla funzione generateSprites
.
Avrete ancora bisogno di posizionare e modificare con gli stili i vari elementi nel CSS: usate pure come base di partenza i file CSS presenti in questo articolo.
Conclusione
Durante la scrittura di questo articolo è stata presentata una tecnica simile, sebbene non abbia come la nostra la versione che usa come fallback gli Sprites CSS. Abbiamo anche scoperto un menu animato basato su jQuery molto diverso ma che potrete trovare comunque utile.