Contenu principal
Accordéon horizontal de photos

Accordéon horizontal de photos

Aujourd’hui nous allons construire un accordéon horizontal avec jQuery, afin de faire défiler des photographies.
Balisage HTML simple, style CSS très simple, dégradation gracieuse, et la possibilité d’avoir plusieurs instances sur une page.

Le script original

En fouillant sur le net à la recherche d’un script déjà existant, je suis d’abord tombé sur Horizontal Accordion 2.0 for jQuery qui paraissait parfait pour ce que je voulais faire. Je me suis vite retrouvé confronté à un problème de taille: malgré tous mes essais, le script ne semble pas fonctionner avec jQuery 1.4. Finalement, j’ai trouvé un script d’accordéon horizontal très simple et facilement modifiable selon mes besoins.

01020304050607080910111213

$(document).ready(function(){
	lastBlock = $("#a1");
	maxWidth = 210;
	minWidth = 75;

	$("ul li a").hover(
		function(){
			$(lastBlock).animate({width: minWidth+"px"}, { queue:false, duration:400 });
			$(this).animate({width: maxWidth+"px"}, { queue:false, duration:400});
			lastBlock = this;
		}
	);
});

Durant mes recherches, j’ai remarqué que ce script est très utilisé, me laissant deviner que ne trouverais probablement pas plus évolué, alors c’est décidé, on va se baser sur celui-là.

Le balisage HTML

Déjà, je voulais quelque chose de simple à mettre en place (par exemple dans un post WordPress ;) ), laissant le script gérer tout dans son coin.

123456

<div class="slide-gallery">
	<img src="..." alt="" />
	<img src="..." alt="" />
	<img src="..." alt="" />
	<img src="..." alt="" />
</div>

Une div, à qui on a attribué une class, et nos images à l’intérieur, voilà ce que je cherche à faire. Ainsi, dans l’administration de WordPress je n’ai qu’à créer la div dans le panel « HTML » de mon post et insérer les images via la fenêtre « Media ». Donc out la liste et les liens du script original.

La mise en forme avec CSS

Pour faire simple, il va me falloir une bordure autour de mes photos, et si l’utilisateur a désactivé javascript dans son navigateur, qu’il puisse quand même voir toutes mes photos (car prévoyant de les mettre dans un post et non dans une bannière de header par exemple, cela ne gênera aucunement).

12345678

.slide-gallery {
	border-width: 6px 0;
	border-color: #000;
	border-style: solid;
	background-color: #000;
	margin-bottom: 20px;
	overflow: hidden;
}

Vous remarquerez que je met une bordure seulement en haut et en bas du conteneur. Ceci pour plusieurs raisons: premièrement il nous faudra également une bordure entre chaque image, avec un border-left par exemple, donc si on met une bordure tout autour du conteneur, on aura alors une bordure gauche 2 fois plus épaisse pour la première photo. Seconde raison, si on met une bordure à droite du conteneur, le script n’aura alors plus aucune marge de manœuvre lors des animations sur la largeur des photos, résultat: si la photo en train de s’élargir s’agrandit plus vite que celle qui est en train de « maigrir », la dernière photo du slider va passer à la ligne pendant une fraction de seconde, on aura l’impression qu’elle clignote (oui, il m’a fallut un bon moment pour trouver d’où venait ce problème :D ). Donc pas de bordure à droite, le calcul des largeurs fera le travail. On met également un fond de la même couleur que les bordures, une marge en dessous du conteneur pour ne pas que le texte situé sous le slider se retrouve collé au conteneur, et un overflow pour éviter tout débordement de « population » ;) qui, en théorie, de devrait pas arriver.

0910111213141516

.slide-gallery .item {
	float: left;
	border-left: solid 6px #000;
	-moz-box-shadow:inset 0 0 10px #000;
	-webkit-box-shadow:inset 0 0 10px #000;
	-khtml-box-shadow:inset 0 0 10px #000;
	box-shadow:inset 0 0 10px #000;
}

Nos photos se verront affublées de la class « item » via le script, d’où le sélecteur.slide-gallery .item. Comme prévu, on trouve la bordure gauche, et on rajoute un effet CSS 3 bien sympa: box-shadow, mais avec la valeur inset afin de créer une ombre « à l’intérieur » des photos au lieu de l’extérieur.

Le script jQuery

Première étape, modifier le script original. On part sur les valeurs suivantes: l’accordéon va faire 560px de large, on a 4 photos qui font 410px de large et les onglets feront 40px, le tout en tenant compte des bordures de 6px. Tant qu’on y est, on rentre la classe de l’accordéon dans une variable.

010203040506070809101112

jQuery(document).ready(function($){
	slideClass = 'slide-gallery';				// Classe du slider
	minWidth = 40;						// Largeur mini d'1 image en px
	maxWidth = 410;						// Largeur max d'1 image par slider
	lastBlock = $('.'+slideClass+' img:first');

	$('.'+slideClass+' img').hover( function(){
		$(lastBlock).stop(true,false).animate({'width': minWidth+"px"}, { queue:false, duration:300 });
		$(this).stop(true,false).animate({'width': maxWidth+"px"}, { queue:false, duration:300});
		lastBlock = this;
	});
});

Au passage, on a rajouté un stop() devant chaque animation.
Bien, c’est un bon début, mais on est loin du résultat final. A ce stade, on n’a même pas l’état de départ de l’accordéon. Voyons donc cela, en même temps on va s’occuper de l’opacité des items et rajouter un « petit truc en plus ». Regardez donc les commentaires pour comprendre ce qui se passe :

01020304050607080910111213141516171819202122232425262728293031

jQuery(document).ready(function($){
	slideClass = 'slide-gallery';
	itemClass = 'item';					// Classe des items
	minWidth = 40;
	maxWidth = 410;
	minOp = .3;						// Opacité des onglets

	var bg = new Array;					// Tableau des backgrounds de nos items
	var ht = new Array;					// Tableau des hauteurs de nos items

	$('.'+slideClass+' img').each(function(index){		// Pour chaque image
		bg[index] = $(this).attr('src');		// On enregistre l'adresse de l'image
		ht[index] = $(this).height();			// On enregistre sa hauteur
		$(this).wrap('<div class="'+itemClass+'" />')	// On l'englobe dans une div.item
			.parent('.'+itemClass)
			.css({'background-image': 'url('+bg[index]+')', 'height': ''+ht[index]+'px', 'opacity': minOp})
			.html('')				// On met la photo en background de la div, on fixe sa hauteur et son opacité, puis on supprime la photo
			.width(minWidth)			// On met l'item à la largeur mini
			.parent('.'+slideClass)
			.height(ht[0]);				// On fixe la hauteur de l'accordéon à la hauteur de la 1ère photo
	});

	lastBlock = $('.'+slideClass+' .'+itemClass+':first');	// On remplace le sélecteur par nos div
	$(lastBlock).css({'width': maxWidth,'opacity': 1});	// On met l'opacité à 1 sur l'item affiché

	$('.'+slideClass+' .'+itemClass).hover( function(){	// On remplace le sélecteur par nos div
		$(lastBlock).stop(true,false).animate({'width': minWidth+"px", 'opacity': minOp}, { queue:false, duration:300 });
		$(this).stop(true,false).animate({'width': maxWidth+"px", 'opacity': 1}, { queue:false, duration:300});
		lastBlock = this;				// On gère l'opacité de nos items sur les 2 lignes ci-dessus
	});
});

Qu’est ce qui s’est passé là-dedans? Pourquoi avoir remplacé les images par des div?
Pour deux raisons, la première étant d’ordre esthétique, et la seconde d’ordre pratique.
Pour l’esthétique:
Déjà, si on avait laissé les images sans même les englober dans une div, et en animant leur largeur, nos onglets auraient été tout simplement horribles. Une image dont la largeur passe à 10% ne va pas donner un résultat génial (et c’est pas propre). Donc on est obligé de « wraper » chaque image dans une div, et d’animer la largeur de la div. En fixant en CSS un overflow: hidden; sur la div ça fonctionnerait très bien. Mais j’ai voulu aller plus loin. Normalement, la partie la plus intéressante d’une photo est au centre, pas sur les côtés. Or, quand l’onglet est rétrécit à 40px de large, on ne verra que le bord gauche de la photo, ce qui est quand même bien léger pour se faire une idée de son contenu. Donc en mettant l’image en background de la div et en fixant sa position à 50% de sa largeur on verra son centre.
Pour le côté pratique:
Nous ne sommes plus obligés d’avoir des photos de 410px de large. Si elles font 40px de plus, il nous manquera juste 20px de chaque côté. Et par la même occasion, nous ne sommes plus bloqués sur le nombre de photos, et donc d’onglets, la largeur des onglets, etc.

Maintenant qu’on y pense, ça serait pas mal de ne pas avoir à calculer la largeur max des images en fonction du nombre d’onglets, de leur largeur, et des bordures :)
Mais aussi, on a oublié un truc important: là où on en est on ne peut avoir qu’un seul accordéon par page.
Bien, réglons tout ça d’un coup!

010203040506070809101112131415161718192021222324252627282930313233343536373839

jQuery(document).ready(function($){
	slideClass = 'slide-gallery';						// Classe des sliders
	itemClass = 'item';							// Classe des items
	slideWidth = 560;							// Largeur totale des sliders en px, bordures incluses
	brd = 6;								// Largeur border en px
	minWidth = 40;								// Largeur mini d'1 image en px
	minOp = .3;								// Opacité des onglets

	var maxWidth = new Array;						// Largeur max d'1 image par slider
	var nbrimg = new Array;							// Nombre d'images par slider
	var lastBlock = new Array;						// Block actif par slider
	$('.'+slideClass).each(function(i){
		nbrimg[i] = 0;
		$(this).addClass('slide-'+i)
			.children('img')
			.each(function(){nbrimg[i]++;});			// Nombre d'images
		maxWidth[i] = slideWidth-(2*brd)-((minWidth+brd)*(nbrimg[i]-1));// Largeur max d'1 image
		var bg = new Array;
		var ht = new Array;
		$(this).children('img').each(function(index){
			bg[index] = $(this).attr('src');
			ht[index] = $(this).height();
			$(this).wrap('<div class="'+itemClass+'" />')
				.parent('.'+itemClass)
				.css({'background-image': 'url('+bg[index]+')', 'height': ''+ht[index]+'px', 'opacity': minOp})
				.html('')
				.width(minWidth)
				.parent('.'+slideClass)
				.height(ht[0]);
		});
		lastBlock[i] = $(this).children('.'+itemClass+':first');
		$(lastBlock[i]).css({'width': maxWidth[i],'opacity': 1});
		$(this).children('.'+itemClass).hover( function(){
			$(lastBlock[i]).stop(true,false).animate({'width': minWidth+"px", 'opacity': minOp}, { queue:false, duration:300 });
			$(this).stop(true,false).animate({'width': maxWidth[i]+"px", 'opacity': 1}, { queue:false, duration:300});
			lastBlock[i] = this;
		});
	});
});

Voilà, maintenant nous pouvons mettre plusieurs accordéons par page, et tout ce que nous devrons indiquer au script ce sont la largeur totale de l’accordéon (comme ça il pourra être plus petit que notre zone de texte si on le désire), la taille des bordures (selon ce que l’on a déclaré dans le style CSS), la largeur des onglets et leur opacité. C’est tout! Le script se chargera de calculer le nombre d’images dans l’accordéon, puis il en tiendra compte pour calculer la largeur de la « grande div ».

Un dernier coup de pinceau

Dernière chose, il nous faut rajouter le positionnement des backgrounds dans notre déclaration CSS. Ce qui nous donne au final:

01020304050607080910111213141516171819

.slide-gallery {
	border-width: 6px 0;
	border-color: #000;
	border-style: solid;
	margin-bottom: 20px;
	background-color: #000;
	overflow: hidden;
}
.slide-gallery .item {
	float: left;
	border-left: solid 6px #000;
	background-color: transparent;
	background-repeat: no-repeat;
	background-position: 50% 0;
	-moz-box-shadow:inset 0 0 10px #000;
	-webkit-box-shadow:inset 0 0 10px #000;
	-khtml-box-shadow:inset 0 0 10px #000;
	box-shadow:inset 0 0 10px #000;
}

J’espère que ce tutoriel vous a plu. N’hésitez pas à laisser un commentaire pour me dire ce que vous en pensez, ou si vous avez des améliorations à apporter :)