jQuery zoom plugin

Visitando il sito Apple Store è possibile vedere all’opera un interessante funzionalità che permette lo zoom delle foto direttamente nel loro spazio, senza aprire fastidiosi popup.

Grazie a jQuery e la sua estensione jQuery UI è possibile realizzare il medesimo effetto in maniera semplice. In particolare andremo ad utilizzare il plugin "draggable" di jQuery UI.

Demo

L’idea di base è quella di prevedere un DIV contenente l’immagine a bassa risoluzione. Al click su questa immagine, questa viene ingrandita fino alle dimensioni dell’immagine a grande risoluzione e successivamente scambiata con quest’ultima in modo da simulare uno zoom. Tramite la funzionalità "draggable" renderemo trascinabile questa immagine, in modo da poter visualizzare tutte le parti dell’ingrandimento. Il nostro riquadro originale che contiene la foto sarà la "finestra" attraverso cui si vedrà l’immagine ad alta risoluzione sottostante. Sarà inoltre necessario impostare posizionamenti e limiti di trascinamento per garantire che l’immagine non esca dal riquadro.
Ho realizzato una piccola animazione che rappresenta l’idea:

Animazione Zoom

Il riquadro rosso è l’area originale dell’immagine piccola (non rappresentata). L’immagine grande (la foto che vedete nell’animazione) potrà essere trascinata in tutte le direzioni e soltanto la parte all’interno del riquadro rosso sarà visibile, dando l’impressione di un’immagine zoomata su cui effettuiamo il panning. Il riquadro grigio invece rappresenta i limiti entro i quali l’immagine può essere trascinata senza uscire dal riquadro rosso. Vedremo dopo come realizzare tutto ciò.

Ma procediamo con ordine. Per prima cosa realizziamo l’html necessario. Come abbiamo detto realizziamo un DIV contenente l’immagine piccola. Per garantire l’accessibilità anche a chi non ha javascript abilitato, l’inseriamo all’interno di un link che punta all’immagine grande e che ci servirà per identificare il path all’immagine ad alta risoluzione:

<div class="image-zoom">
<a href="img1-big.jpg">
<img src="img1-small.jpg" alt="img1" />
</a>
</div>

Ora che l’html è pronto possiamo procedere con gli opportuni stili css:

.image-zoom { overflow:hidden; width:300px; height:300px; position:relative }
.image-zoom img { position:absolute; }

Come potete vedere ho impostato l’overflow hidden per confinare l’immagine all’interno del riquadro quando viene zoomata (come se fosse una finestra) e le dimensioni coincidenti con la dimensione dell’immagine contenuta. Ho poi aggiunto il posizionamento relative al contenitore e absolute all’immagine, in modo da poterla muovere liberamente nel nostro div.

Ora che abbiamo tutto pronto possiamo procedere all’aggiunta del javascript necessario, il tutto grazie a jQuery e jQuery UI. Andiamo quindi a realizzare la struttura base per realizzare un plugin in jQuery:

(function($){
$.fn.zoom = function(params) {
   var defaults = {
      duration: 200,
      small_width:300,
      small_height:300,
      big_width:1000,
      big_height:1000
   };
   var options = $.extend(defaults, params);
   return this.each(function() {
      //istruzioni del plugin...
   });
}
})(jQuery)

La prima riga e l’ultima riga servono soltanto a garantirci di poter utilizzare la scorciatoia "$" al posto di "jQuery", mentre tramite $.fn.zoom = function(params) { ... } andiamo a realizzare il plugin vero e proprio. La variabile params è l’insieme di opzioni che possiamo impostare all’inizializzazione del plugin, mentre defaults sono i valori di default che il plugin andrà ad utilizzare. Infine uniamo il tutto tramite il metodo $.extend nell’oggetto options, il quale che conterrà i valori di default eventualmente sovrascritti dai parametri passati in params. I parametri di default sono in particolare la durata delle animazioni di zoom-in e zoom-out e le dimensioni dell’immagine a bassa e alta risoluzione (per semplicità ipotizziamo di avere immagini di dimensioni fisse).
Le istruzioni del plugin invece andranno inserite all’interno del ciclo return this.each(function() {...});. Tramite each infatti possiamo inizializzare la funzionalità del plugin a più elementi contemporaneamente.

Il motivo per cui ho deciso di realizzare un plugin anziché semplici istruzioni è che permette di avere un codice riutilizzabile facilmente, permette di scrivere codice molto più ordinato e di estenderne le funzionalità senza dover mettere mano a tutto il flusso di lavoro.

Ma vediamo in dettaglio come sarà strutturato il plugin. In generale prevediamo quattro parti: una prima parte dove andremo a preparare tutte le variabili che ci servono, una seconda parte dove manipolare css e html per preparare le funzionalità del plugin, una terza parte dove andremo a gestire gli eventi dell’utente, richiamando gli opportuni metodi, ed infine un’ultima parte dove andremo ad implementare le varie funzionalità (zoom in, zoom out, drag etc.).

Prima parte

//caching...
var $container = $(this);
var $link = $container.find('a');
var $image = $link.find('img');

//reading some parameters...
var small_path = $image.attr('src');
var big_path   = $link.attr('href');

//status variables...
var dragging = false;
var zoomed   = false;

//calculate correct positioning...
var a_width  = (options.big_width  - options.small_width)  * 2 + options.small_width;
var a_height = (options.big_height - options.small_height) * 2 + options.small_height;

var a_top  = options.big_height - options.small_height;
var a_left = options.big_width  - options.small_width;

La prima sezione serve per fare il "caching" degli elementi utilizzati in modo da semplificarne e velocizzarne l’utilizzo nel resto del plugin. Salviamo poi i path delle immagini a bassa e alta risoluzione che ci serviranno nelle operazioni di zoom-in e zoom-out. Le variabili dragging e zoomed, che impostiamo all’inizio a false, servono per salvare lo stato corrente delle immagini, in particolare se le stiamo trascinando e se sono zoomate o rimpicciolite. Nell’ultima sezione infine calcoliamo i posizionamenti e le larghezze che ci serviranno quando andremo a manipolare il css dell’html.

Seconda parte

$link.css({
    display:'block',
    position:'absolute',
    width: a_width   + 'px',
    height: a_height + 'px',
    top:  '-' + a_top  + 'px',
    left: '-' + a_left + 'px'
});

/* Removing default link behaviour... */
$link.removeAttr('href');

$image.css({
    display:'block',
    position:'absolute',
    width:  options.small_width + 'px',
    height: options.small_height + 'px',
    top: a_top + 'px',
    left: a_left + 'px'
});

Nella seconda parte andiamo a cambiare i css di link e immagine per predisporre la funzionalità di zoom. In particolare il link diventerà il blocco che conterrà lo spostamento dell’immagine grande (il rettangolo grigio nell’animazione). A questo andremo poi a togliere l’attributo href, in modo da eliminare il comportamento standard del link che interferirebbe con il nostro plugin. L’immagine infine viene riposizionata all’interno del tag a in modo da trovarsi in corrispondenza della finestra originale (il rettangolo rosso). Infatti mentre il link è spostato a sinistra e in alto tramite margini negativi, l’immagine viene spostata degli stessi valori (a_top, a_left) ma verso destra e in basso, in modo da trovarsi nel medesimo punto rispetto alla finestra originale. In questo modo avremo già pronti i posizionamenti assoluti per l’animazione.

Terza parte

$image.mouseup(function() {
    if (!dragging) {
        if (zoomed) {
	        zoomOut();
        } else {
    	    zoomIn();
        }
    }
});

L’unico evento che ci interessa gestire è il rilascio del pulsante del mouse. Utilizzando infatti l’evento "mouseup" anziché l’evento "click" possiamo controllare lo stato prima di decidere che operazione compiere. Al rilascio del mouse controllo se sto effettuando il "dragging". Se non sto trascinando, e quindi è un semplice click, effettuo lo zoom-in o lo zoom-out in base allo stato "zoomed", richiamando i due metodi zoomOut() e zoomIn().

Quarta parte

Nella quarta parte andremo ad implementare i metodi zoomOut() e zoomIn() richiamati precedentemente, oltre a quelli che si renderanno necessari successivamente:

function zoomOut() {
    $image.animate({
        width:options.small_width + 'px',
        height:options.small_height + 'px',
        top:  a_top  + 'px',
        left: a_left + 'px'
    }, options.duration, swapWithSmall);
	zoomed = false;
}

function zoomIn() {
    $image.animate({
        width:options.big_width + 'px',
        height:options.big_height + 'px',
        top:  a_top / 2  + 'px',
        left: a_left / 2 + 'px'
    }, options.duration, swapWithBig);
    zoomed = true;
}



Come vedete ogni metodo va ad animare dimensioni e posizionamenti css secondo i valori calcolati nella prima parte. Alla fine dell’animazione vengono richiamati i due metodi per scambiare l’immagine, rispettivamente "swapWithBig" e swapWithSmall":

function swapWithBig() {
	$image.attr('src', big_path);
	$image.draggable({
		containment: 'parent',
		start: handleStartDrag,
		stop: handleStopDrag
	});
}

function swapWithSmall() {
	$image.attr('src', small_path);
	$image.draggable('disable');
}

function handleStartDrag() {
	dragging = true;
}

function handleStopDrag() {
	dragging = false;
}

Questi non fanno altro che cambiare l’attributo src e ad abilitare (nel caso dello zoom-in) o disabilitare (nel caso dello zoom-out) la funzionalità "draggable" di jQuery UI. Come vedete a draggable vengono impostati alcuni parametri:

containment: serve ad impostare i limiti di trascinamento. Nel nostro caso è impostato a parent, e cioè al tag html che contiene l’elemento e quindi il tag a che abbiamo appositamente posizionato e dimensionato proprio per questo scopo.
start: è la funzione che viene eseguita quando inizia il trascinamento. Nel nostro caso è la funzione handleStartDrag che non fa altro che aggiornare la variabile di stato corrispondente (dragging).
stop: è la funzione che viene eseguita alla fine del trascinamento e richiama la funzione handleStopDrag, che aggiorna la variabile dragging.

Il plugin ora è completo, non ci resta che richiamarlo nella nostra pagina tramite la sintassi solita di jQuery:

$(document).ready(function() {
	$('.image-zoom').zoom();
});

Alcuni metodi possono sembrare superflui, ma ho preferito spezzettare il plugin in più funzioni esplicite, ciascuna con funzionalità ben definite, piuttosto che annidare funzioni su funzioni come jQuery a volte induce a fare. Questo semplifica la lettura del codice e soprattutto le successive manipolazioni e revisioni.

Ecco il codice completo del plugin, mentre qui potete vederlo in azione:

(function($) {
$.fn.zoom = function(params) {
	/*
	Zoom jQuery Plugin
	Copyright: (C) 3009 - Francesco Paggin
	Parameters:
		(int) duration: the duration of the zooming animaton
		(int) small_width, small_height: the dimensions of the small thumbnails
		(int) big_width, big_height: the dimensions of the zoomed images
	*/
	var defaults = {
		duration: 200,
		small_width:300,
		small_height:300,
		big_width:1000,
		big_height:1000
	};
	var options = $.extend(defaults, params);

	return this.each(function() {
		//caching...
		var $container = $(this);
		var $link = $container.find('a');
		var $image = $link.find('img');

		//reading some parameters...
		var small_path = $image.attr('src');
		var big_path   = $link.attr('href');

		//status variables...
		var dragging = false;
		var zoomed   = false;

		//calculate correct positioning...
		var a_width  = (options.big_width  - options.small_width)  * 2 + options.small_width;
		var a_height = (options.big_height - options.small_height) * 2 + options.small_height;

		var a_top  = options.big_height - options.small_height;
		var a_left = options.big_width  - options.small_width;

		/*
			INITIALIZATION...
		*/
		$link.css({
			display:'block',
			position:'absolute',
			width: a_width   + 'px',
			height: a_height + 'px',
			top:  '-' + a_top  + 'px',
			left: '-' + a_left + 'px'
		});

		/* Removing default link behaviour... */
		$link.removeAttr('href');

		$image.css({
			display:'block',
			position:'absolute',
			width:  options.small_width + 'px',
			height: options.small_height + 'px',
			top: a_top + 'px',
			left: a_left + 'px'
		});

		/*
			HANDLE EVENTS...
		*/
		$image.mouseup(function() {
			if (!dragging) {
				if (zoomed) {
					zoomOut();
				} else {
					zoomIn();
				}
			}
		});

		/*
			METHODS...
		*/
		function zoomOut() {
			$image.animate({
				width:options.small_width + 'px',
				height:options.small_height + 'px',
				top:  a_top  + 'px',
				left: a_left + 'px'
			}, options.duration, swapWithSmall);
			zoomed = false;
		}

		function zoomIn() {
			$image.animate({
				width:options.big_width + 'px',
				height:options.big_height + 'px',
				top:  a_top / 2  + 'px',
				left: a_left / 2 + 'px'
			}, options.duration, swapWithBig);
			zoomed = true;
		}

		function swapWithBig() {
			$image.attr('src', big_path);
			$image.draggable({
				containment: 'parent',
				start: handleStartDrag,
				stop: handleStopDrag
			});
		}

		function swapWithSmall() {
			$image.attr('src', small_path);
			$image.draggable('disable');
		}

		function handleStartDrag() {
			dragging = true;
		}

		function handleStopDrag() {
			dragging = false;
		}
	});
}
})(jQuery)

3 Comments so far

  1. yaka on gennaio 12th, 2009

    Bellissimo plugin, complimenti!
    unica cosa .. aggiungerei cursor:hand per renderlo completo al 100%.
    Bel lavoro! ;)

  2. Massimiliano on maggio 20th, 2009

    Complimenti veramente.
    Ho un problema pero’.
    Io vorrei poter settare la z-index a positiva o negativa a seconda dello ZoomIn o ZoomOut.
    Quindi ho modificato il tuo codice nella seguente maniera:
    if (!dragging) {
    if (zoomed) {
    $image.css({ z-index:’100′ });
    zoomOut();
    } else {
    zoomIn();
    $image.css({ z-index:’-100′ });
    }
    }

    E’ solo che pare che il trattino di z-index dia fastidio. Come posso fare per settare tale proprieta’?

  3. Massimiliano on maggio 20th, 2009

    Fatto :-D

    $image.css(’z-index’, ‘100′);

Leave a reply