22 dicembre 2010

Drupal + jQuery: galleria fotografica stile ‘filmstrip’

Per il sito Voilà le Bijou, che espone le creazioni di bigiotteria di Donella Menichini, ho sviluppato su Drupal 6 un semplice tema espositivo basato sui colori del logo (creato da Mauro Luccarini di Cervelli Riuniti), che focalizza tutta l’attenzione sui bijou.


Avendo deciso con Donella di suddividere i bijou in collezioni (dai nomi che richiamano il mondo dell’opera e del teatro), ho deciso di creare per ciascuna collezione un’apposita pagina con la relativa galleria fotografica, fatta in modo tale da avere esposto un singolo bijou per volta, ma con un colpo d’occhio su tutta la collezione.

In pratica, volevo avere un’immagine grande del bijou selezionato in primo piano, e sotto una filmstrip di thumbnails.


Senza andare a scovare moduli dedicati per gallerie fotografiche, ho creato questa semplice visualizzazione basandomi principalmente su Views e Imagecache.

1. Moduli necessari

  1. Views 2 (per creare le pagine contenenti la galleria)
  2. Views Custom Field (per generare del markup ad hoc)
  3. Imagecache (per creare le immagini da visualizzare)
2. Organizzazione preliminare
Il modo più razionale di organizzare il contenuto in collezioni è sfruttare la Tassonomia, creando un vocabolario “Collezioni” e aggiungendovi tutte le collezioni presenti come termini.

Ciascun nodo rappresenta un bijou, ed appartiene soltanto ad una collezione. Può avere una o più immagini allegate, ma soltanto la prima è necessaria per l’esposizione, ed è quella che prenderemo in considerazione (le altre sono visibili solo sulla pagina dedicata al bijou).

Occorre implementare due preset di Imagecache, uno per le thumbnails, e uno per l’immagine grande.
Il primo preset si chiama collezioni_thumb, e ha un’azione Scale And Crop a 80x80, poiché voglio thumbnails quadrate, create a partire dalla foto del bijou.

Il secondo preset si chiama chiama collezioni_full, e ha un’azione Scale a 400x100%, poiché voglio immagini di larghezza 400px (le foto sono tutte in formato 3:2).

3. Costruire la pagina in Views
Poiché le collezioni sono termini di un vocabolario di tassonomia, la pagina che creerò in Views andrà a costituire la visualizzazione predefinita delle pagine taxonomy/term.

Per costruirla, si può partire dalla vista che viene creata con l’installazione di Views, e che fa uso degli argomenti per poter generalizzare le pagine per ogni termine di tassonomia.


L’Argomento necessario da passare alla vista è l’ID del Termine di tassonomia (TID).

Nelle Impostazioni di pagina quindi, Path deve essere settato a taxonomy/term/% dove il ‘%’ è il segnaposto per il TID che viene passato dall’URL (ciò è indipendente dagli URL creati con Pathauto, e funziona lo stesso).

Impostazioni di base: il titolo della pagina è bene che sia settato a %1, che in questo caso indica il valore del TID (e quindi è il nome del termine, cioè della collezione, nel nostro caso).

Lo Stile riga deve essere Campi, perché abbiamo solo bisogno di alcuni dati da passare a jQuery per creare la galleria e gli effetti, e lo Stile va bene che sia una HTML list (nelle opzioni dello stile, i campi devono essere raggruppati per Taxonomy: Termine, e va bene un elenco non ordinato – questo si può settare solo dopo aver inserito il campo corrispondente, vedi oltre).

Filtri: basta che il nodo sia pubblicato (in questo caso, le collezioni sono appannaggio solo dei bijou, e non c’è pericolo che ci vada a finire una pagina o altri tipi di contenuto – altrimenti sarebbe stato opportuno filtrare per tipo di contenuto).

Criteri di ordinamento: per titolo del nodo (visto che le collezioni hanno bijou numerati progressivamente).

Veniamo quindi ai Campi necessari, uno per uno nel loro ordine:
  • Taxonomy: Termine. Ci serve solo per poter raggruppare i campi nelle opzioni dello stile. Escluso dalla visualizzazione, perché verrà già visualizzato come campo di raggruppamento.
  • Contenuto: Foto. Sono le foto piccole di ciascun bijou; deve avere le seguenti proprietà:
    • Collega questo campo al suo nodo (ci serve perché se jQuery fosse disattivato, l’utente comunque potrebbe navigare alla pagina del bijou)
    • Raggruppa valori multipli, show 1 value from 0 (poiché deve essere visualizzata solo la prima immagine del nodo)
    • Etichetta: nessuno
    • Format: collezioni_thumb image (l’immagine piccola, larghezza 80px, che costituisce la thumbnail su cui cliccare per vedere l’immagine grande)
  • Contenuto: Foto. Un altro inserimento dello stesso campo di cui sopra, perché abbiamo bisogno di inserire nella vista due volte la stessa immagine: la prima come thumbnail, la seconda (questa) come foto grande. Ecco le proprietà necessarie:
    • Escludi dalla visualizzazione (perché useremo Customfield e jQuery per visualizzare l’immagine dove, quando e come ci pare!)
    • Raggruppa valori multipli, show 1 value from 0 
    • Etichetta: nessuno
    • Format: collezioni_full file path (l’indirizzo dell’immagine grande, larghezza 400px, che useremo come parametro per jQuery)
  • Nodo: Titolo. Il nome de bijou che ci servirà per un’ulteriore formattazione della galleria. Escluso dalla visualizzazione, per passarlo a jQuery.
  • Customfield: markup. Di per sé il campo consente di inserire del markup a piacere nella vista. Noi lo usiamo per passare dei dati a jQuery, tramite un tag span creato ad hoc. Questo campo deve essere per forza l’ultimo dell’elenco, per poter avere a disposizione tutti i dati estratti dal database grazie alle query fatte coi campi che lo precedono. Ecco come lo formattiamo:
    • Rewrite the output of this field. Questa proprietà mette a disposizione i dati dei campi precedenti: inseriamo il titolo del nodo e il path dell’immagine grande in uno span:
      • Testo: <span class="img-link" title="[title]|[field_bijou_foto_fid_1]"></span>
      • Li mettiamo nell’attributo title di uno span vuoto in modo che l’apparenza della pagina non cambi, ma siano comunque accessibili da jQuery. Tra i due mettiamo un “|” per separare i campi. Tornerà utile più tardi.
Strettamente parlando, il campo customfield (e quindi il modulo Views Custom Field) non sono necessari, poiché l'opzione "Rewrite the output of this field" è disponibile con ogni campo, e quindi potevamo sfruttare quella dell'ultimo tra i campi precedenti. Però in questo modo l'output è più pulito e si separa contenuto da funzionalità aggiuntiva, consentendo una migliore intelligibilità del tutto. Poi fate voi...

Se andiamo a vedere la pagina creata con questa vista, osserveremo soltanto una lista di thumbnails che inviano al relativo nodo.

4. Creazione della galleria con jQuery
4.1 Markup: analisi
Adesso passiamo al codice HTML/CSS: nella cartella del tema, si clona il file page.tpl.php e lo si rinomina in page-taxonomy-term.tpl.php per poter customizzare il codice delle pagine che utilizzeranno la vista appena creata.

L’architettura della lista di thumbnails creata da Views è una lista non ordinata, di cui ciascun elemento è fatto così (questo è il primo):

<li class="views-row views-row-1 views-row-odd views-row-first">  
 <div class="views-field-field-bijou-foto-fid">
  <div class="field-content"><a href="/articoli/callas-01"><img src="http://voilalebijou.com/sites/default/files/imagecache/collezioni_thumb/foto/callas-01/callas1.jpg" alt="" title="Girocollo Callas 01"  class="imagecache imagecache-collezioni_thumb imagecache-default imagecache-collezioni_thumb_default" width="80" height="80" /></a></div>
 </div>

 <div class="views-field-markup">
  <span class="field-content"><span class="img-link" title="Callas 01|sites/default/files/imagecache/collezioni_full/foto/callas-01/callas1.jpg"></span></span>
 </div>
</li>

Cioè, gli <li> sono tutti di classe .views-row e contengono due campi (<div>):
  1. div.views-field-field-bijou-foto-fid, che ha la thumbnail con il link al nodo
  2. div.views-field-markup, che contiene lo <span> con le informazioni sul titolo del bijou e il path della sua immagine grande, che abbiamo creato prima.
Il markup del customfield ha poco senso, mi rendo conto. Purtroppo di base Views non consente una customizzazione dell’output dei vari campi, il cui contenuto è sempre inserito in uno span.field-content, anche quando questo potrebbe creare problemi di validazione (ad esempio se uno volesse inserirvi uno heading o altri elementi block level…). La soluzione più veloce è usare il modulo Semantic Views, che ci consente di scegliere e customizzare i tag che costituiscono i campi visualizzati. Ma qui non è proprio necessario.

Per avere una film strip orizzontale (e non un elenco-lista verticale) ci serve del CSS per gli <li>, nel template della pagina (page-taxonomy-term.tpl.php):

.views-row{
 display: inline-table;
 width: 100px;
}

La proprietà inline-table ci consente di avere ogni thumbnail accanto all’altra in fila, mentre la larghezza ci dà maggiore spaziatura orizzontale.

4.2 Visualizzazione dell’immagine ingrandita con jQuery
Il piano è questo:
  1. inserire un <div> tra il titolo della pagina <h3> e la lista <ul>, dove visualizzare l’immagine grande con il suo titolo, collegati al bijou rappresentato
  2. caricare subito l’ingrandimento della prima thumbnail
  3. collegare il click su ogni thumbnail con l’aggiornamento dell’immagine, sostituendo quella corrente con quella relativa alla thumbnail cliccata.
Finalmente si passa a jQuery, e si procede in questo modo: prima di tutto si devono salvare i dati che ci servono, che sono:
  1. il link al nodo di destinazione
  2. il path dell’immagine grande da caricare
  3. il titolo del nodo da visualizzare insieme all’immagine grande.
Si parte con il primo elemento della lista, perché vogliamo che il suo ingrandimento sia visualizzato al caricamento della pagina. Basta accedere al suo attributo href:

var item = $('.views-row-first a:first').attr('href');

Gli altri due parametri li avevamo inseriti nel title dello <span> creato con il customfield, ma siccome è una sola stringa con due attributi, prima di poterli utilizzare vanno separati. Ecco come si fa:

var rel = $('.views-row-first').find('.img-link').attr('title');
var split = rel.split("|");
var title = split[0];
var imglink = split[1];

Il metodo Javascript split, data una stringa, restituisce un array con i risultati della suddivisione. Il carattere “|” ci è servito come indicatore del punto di suddivisione.

A questo punto basta inserire il contenitore dove caricare l’immagine, che chiamiamo “zoom”:

$('h3').after('<div id="zoom"><a href="' + item + '"><img class="zoom" src="/' + imglink + '" alt="" /></a><a class="desc" href="' + item + '">' + title + '</a></div>');
Il div#zoom lo formattiamo con questo CSS:

#zoom{
 width: 400px; /* la larghezza esatta dell’immagine grande data da Imagecache */
 height: 270px;
 margin: 5px auto;
 position: relative;
}

Al suo interno abbiamo aggiunto due tag <a>: il primo contiene l’immagine, il secondo (.desc) il suo titolo, che formatteremo con un’etichetta, così:

.desc{
 background: url(<?php print $base_path.$directory; ?>/images/fold.png) top left no-repeat; color: #d00000;
 font: 1em Monika, 'Monika italic', "trebuchet ms", sans-serif;
 text-align: center;
 text-decoration: none;
 position: absolute;
 bottom: 50px; right: -3em;
 padding: 1em;
}

Il php nella proprietà background serve per stampare il percorso fino alla cartella del tema, dove c’è l’immagine di sfondo con la “piega” dell’etichetta.

La font “Monika” è un carattere calligrafico molto in sintonia con l’argomento: “Monika italic” è la font che ho creato con Font Squirrel per importarla con il metodo @font-face, mentre il primo “Monika” è il nome standard della font (così se qualcuno ce l’ha già installata sul proprio computer, utilizziamo quella, che di solito viene renderizzata meglio).

Il posizionamento assoluto è relativamente al contenitore div#zoom, che abbiamo dichiarato appositamente come position: relative.

Adesso la pagina visualizza appena caricata l’ingrandimento della prima thumbnail, con un’etichetta col titolo. Ambedue linkano al nodo visualizzato.

Per estendere la funzione d’ingrandimento anche alle altre thumbnail, basta replicare il codice al click di ogni thumb, con qualche piccola modifica:

$('.views-row a').click(function(){
 var itemlink = $(this).attr('href');
 var rel = $(this).parents('.views-row').find('.img-link').attr('title');
 var split = rel.split("|");
 var imglink = split[1];
 var title = split[0];
 $('.zoom').fadeOut(250);
 $('#zoom').html('<a href="' + itemlink + '"><img class="zoom" src="/' + imglink + '" alt="" /></a><a class="desc" href="' + itemlink + '">' + title + '</a>');
 $('.zoom').hide().fadeIn(500);
 return false;
});

Come transizione tra due fotografie, abbiamo inserito un fade out/fade in (in pratica: si nasconde l’immagine facendola dissolvere nel nulla; mentre è nascosta cambiamo l’immagine visualizzata e poi la facciamo ricomparire bella nuova); per il resto lo script è identico nel suo funzionamento.

Return false; serve per impedire al browser di seguire il link alla pagina del nodo: le thumbnail servono solo per ingrandire l’immagine.

Ora la pagina ha tutto ciò che le serve per il funzionamento di base.


4.3 Effetti per la film strip
Aggiungiamo adesso qualche semplice effetto di hover e selezione per le thumbnail. Ecco cosa implementerò:
  1. Le thumbnail, da un’opacità dell’80%, diventeranno completamente opache al passaggio del mouse
  2. Contemporaneamente, si creerà sfumando un bordo rosso
  3. La thumbnail dell’immagine correntemente visualizzata avrà queste due caratteristiche fisse su di sé.
Innanzitutto, un po’ di CSS per l’aspetto di default delle thumbs:

.views-row a img{
 opacity: .8;
 -moz-opacity: .8;
 filter: alpha(opacity=80);
 padding: 1px;
}
.views-row a{
 outline: none;
}

Qualche dichiarazione cross-browser per l’opacità e un padding per creare il “bordo”, che per far prima sarà solo un cambio di background-color. Ho tolto l’outline dai link delle thumb perché è antiestetico quando un link non fa lasciare la pagina.

Passiamo al comportamento delle thumb quando il puntatore ci passa sopra. Ecco il codice jQuery, basta anteporlo a quello che abbiamo scritto prima per la visualizzazione dell’immagine ingrandita:

$('.views-row a').hover(
 function(){
  $(this).children('img').animate({
   opacity: 1,
   backgroundColor: "#d00000"
  }, 300);
 },
 function(){
  $(this).children('img').animate({
   opacity: .8,
   backgroundColor: "#000"
  }, 300);
 }
);

L’unica cosa da notare qui è che per far funzionare l’animazione del background-color (che grazie al padding di cui sopra ci dà l’illusione di creare un bordo attorno all’immagine) è necessario inserire il plugin jquery.color, ad esempio inserendo nel file .info del tema Drupal la seguente dichiarazione (in questo modo però verrà caricato in ogni pagina – cosa che nel mio progetto è sicuramente desiderabile):

scripts[] = scripts/jquery.color.min.js

(ovviamente dopo aver messo il file dichiarato nella sottocartella indicata della directory del tema, che nel mio caso è “scripts”).

La versione del plugin che ho indicato è ottimale perché funziona anche con Safari (altre non vanno).
Adesso, per cambiare le proprietà della thumbnail relativa all’immagine corrente, creiamo una classe CSS che appiopperemo di volta in volta alla thumb cliccata:

.current img{
 opacity: 1 !important;
 -moz-opacity: 1 !important;
 filter: alpha(opacity=100) !important;
 background-color: #d00000 !important;
}

Le dichiarazioni !important ci servono per passar sopra ai settaggi imposti da altre operazioni dello script jQuery.

Per attaccarla/staccarla dalle thumb man mano che vengono cliccate usiamo i metodi jQuery addClass/removeClass. Per prima cosa si attribuisce alla prima thumbnail, quella dell’immagine che è visualizzata di default:

$('.views-row-first a').addClass('current');
var item = $('.views-row-first a').attr('href');
/* [...] */

e poi nell’evento click di ciascun thumb:

.click(function(){
 $('.views-row').find('.current').removeClass('current');
 $(this).addClass('current');
 var itemlink = $(this).attr('href');
/* [...] */

Con questo abbiamo finito!

5 Script completo
Ecco quindi lo script completo che abbiamo costruito passo passo:

$(document).ready(function(){

$('.views-row-first a').addClass('current');
var item = $('.views-row-first a').attr('href');
var rel = $('.views-row:first').find('.img-link').attr('title');
var split = rel.split("|");
var imglink = split[1];
var title = split[0];
$('h3').after('<div id="zoom"><a href="' + item + '"><img class="zoom" src="/' + imglink + '" alt="" /></a><a class="desc" href="' + item + '">' + title + '</a></div>');

$('.views-row a').hover(
 function(){
  $(this).children('img').animate({
   opacity: 1,
   backgroundColor: "#d00000"
  }, 300);
 },
 function(){
  $(this).children('img').animate({
   opacity: .8,
   backgroundColor: "#000"
  }, 300);
 }
).click(function(){
 $('.views-row').find('.current').removeClass('current');
 $(this).addClass('current');
 var itemlink = $(this).attr('href');
 var rel = $(this).parents('.views-row').find('.img-link').attr('title');
 var split = rel.split("|");
 var imglink = split[1];
 var title = split[0];
 $('.zoom').fadeOut(250);
 $('#zoom').html('<a href="' + itemlink + '"><img class="zoom" src="/' + imglink + '" alt="" /></a><a class="desc" href="' + itemlink + '">' + title + '</a>');
 $('.zoom').hide().fadeIn(500);
 return false;
});

});
Ed ecco anche il CSS:
.views-row{
 display: inline-table;
 width: 100px;
}
.views-row a{
 outline: none;
}
.views-row a img{
 opacity: .8;
 -moz-opacity: .8;
 filter: alpha(opacity=80);
 padding: 1px;
}

#zoom{
 width: 400px;
 height: 270px;
 margin: 5px auto;
 position: relative;
}
.desc{
 background: url(<?php print $base_path.$directory; ?>/images/fold.png) top left no-repeat; color: #d00000;
 font: 1em Monika, 'Monika italic', "trebuchet ms", sans-serif;
 text-align: center;
 text-decoration: none;
 position: absolute;
 bottom: 50px; right: -3em;
 padding: 1em;
}

.current img{
 opacity: 1 !important;
 -moz-opacity: 1 !important;
 filter: alpha(opacity=100) !important;
 background-color: #d00000 !important;
}

17 dicembre 2010

beloved snow


Oggi ho passato il mio non-pomeriggio intrappolato in auto cercando di andare da A a B.