Fusée CSS/jQuery

Fusée CSS/jQuery

Tutoriel publié en mai 2012 par Galdon dans la catégorie jQuery

Dans ce tutoriel, nous allons apprendre à créer un élément qui reste affiché à l'écran en permanence même lorsqu'on scroll (barre de défilement) en "suivant" le défilement de l'écran.

Tester /ressources/exemple/jquery-rocket/step5.html
Résultat final
La fusée suit le défilement de la souris

L'intérêt est d'attirer l'attention tout en gardant cet élément en dehors du contenu principal de la page, on peut par exemple l'utiliser pour afficher un lien vers les réseaux sociaux : Facebook "like", Twitter, Google +1...

Cette technique est notamment utilisée sur l'Apple Store, pour afficher le panier d'achats en permanence - ex: http://store.apple.com/fr/browse/home/shop_ipad/family/ipad/select_ipad.

Pour réaliser cet effet, on va utiliser le positionnement CSS "fixed" ainsi que jQuery.

Voilà les sources du résultat final pour les gens pressés :

jQuery Rocket Sources + bonus + fichier Photoshop du sprite CSS
Télécharger

Étape 1 : structure HTML + CSS

Avant tout, on va partir sur avec cette page de base :

<!DOCTYPE html>
<html>
<head>
	<meta charset="utf-8">
	<style type="text/css">
	#page{
		position:relative;
		margin:50px auto;
		width:250px;
	}
	</style>
</head>
<body>
	<div id="page">
		<div>
			<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit.</p>
		</div>

		<!-- Fusée -->
	</div>
</body>
</html>

Comme vous le voyez, il y a une div #page dont le rôle est de contenir tous les éléments de la page dans une bande verticale de largeur fixe (250px).

Mais cette div porte également une propriété CSS position:relative . Cela ne modifie en rien le positionnement de la div #page, mais cette propriété est très importante pour le positionnement de tous les éléments qu'elle contient.

En effet, grâce à cette propriété, tous les éléments contenus dans #page ayant un positionnement relative ou absolute seront positionnés par rapport à la div #page, et pas par rapport à toute la fenêtre.

Note: la valeur par défaut de l'attribut position est : static.

Intéressons-nous maintenant au code HTML de la fusée :

<!-- Début fusée -->
<div id="rocket_dummy">
	<div id="rocket_dock"></div>
	<div id="rocket_mobile">
		<div class="fire top"></div>
		<div class="fire bottom"></div>
		<div class="rocket_body"></div>
	</div>
</div>
<!-- Fin fusée -->

Et le code CSS qui va avec :

#rocket_dummy{
	position:absolute; left:-100px; top:20px;
	width:55px; height:55px;
	background:blue;
}
#rocket_mobile{
	position:absolute; top:0; margin:85px 0 0 -4px;
	width:60px; height:185px;
	background:rgba(255,0,0,0.5);
}
#rocket_mobile.fixed{position:fixed; top:0;}

#rocket_dock{
	position:absolute; width:28px; height:28px; top:164px; left:12px;
	background:#555;
}

#rocket_mobile .rocket_body{
	position:absolute; width:60px; height:60px; top:63px; left:0;
	background:rgba(246,168,0,0.9);
}

#rocket_mobile .fire{
	position:absolute; width:35px; height:74px;
	background:rgba(100,245,0,0.7);
}
#rocket_mobile .fire.top{
	left:13px; top:7px;
}
#rocket_mobile .fire.bottom{
	left:13px; top:104px;
}
Tester /ressources/exemple/jquery-rocket/step1.html
Code HTML/CSS
Structure HTML + code CSS de la fusée

Pour faciliter les explications j'ai appliqué aux différents éléments des couleurs flashy.

Premier élément à regarder : #rocket_dummy. Dans la version finale cette div n'est pas visible, et pourtant c'est une pièce maîtresse du système , puisqu'elle permet de positionner la fusée.

En effet, cette div est positionnée en absolute (par rapport à #page), et tous les autres éléments de la fusée sont positionnés par rapport à #rocket_dummy.

#rocket_mobile est utilisé comme conteneur de la fusée, c'est cet élément et tout son contenu qui va suivre le défilement.

Ensuite il reste .rocket_body qui correspond au corps de la fusée (le rond noir), et aussi les 2 flammes en haut et en bas : .fire.top et .fire.bottom. Rien de bien compliqué concernant ces 3 éléments, ils sont simplement positionnés en absolute (par rapport à leur parent : #rocket_mobile).

Étape 2 : images d'arrière-plan

Maintenant on va remplacer toutes ces couleurs flashy par des images d'arrière-plan en CSS.

Pour cela, je vais vous fournir l'image qui contient l'arrière plan des différents éléments de la fusée, téléchargez cette image :

CSS sprite
Sprite CSS

Comme vous pouvez le voir, il n'y a qu'une seule image qui regroupe tous les éléments, au lieu d'avoir une image par élément.

C'est ce qu'on appelle un sprite CSS. CSS permet de positionner les images d'arrière-plan, de la "décaller" pour afficher la zone souhaitée.

Cette technique offre plusieurs avantages :

  • elle limite le nombre d'images à charger, et donc le nombre de requêtes HTTP envoyées au serveur
  • elle accélère aussi le chargement des pages

Voici la syntaxe de la propriété CSS qui permet de définir une image d'arrière plan et de la positionner :

background: url('rocket-sprite.png') -60px 10px no-repeat;

La partie en rouge permet d'indiquer le chemin de l'image. Vous pouvez soit y mettre l'URL absolue (http://.../rocket-sprite.png) soit le chemin relatif par rapport au fichier qui contient le CSS. En ce qui me concerne j'ai placé l'image dans le même dossier que ma page html.

La partie en bleu correspond à la position de l'image d'arrière plan : le premier chiffre correspond au décalage horizontal, et le second au décalage vertical. La seule "difficulté" est que les axes sont inversés : l'origine (0px 0px) correspond au coin en haut à gauche de l'image.

Le no-repeat à la fin indique que l'image ne doit pas être répétée comme une mosaïque lorsqu'elle n'est pas assez grande pour couvrir tout l'arrière plan de l'élément. Ici ça n'est pas le cas mais il faut quand même donner une valeur pour cette propriété.

Voici donc les cotes des différentes parties de l'image, elles vont nous être très utiles pour positionner les différents background :

Cotes

Prenons l'exemple de .rocket_body, la partie de l'image qui nous intéresse est le rond noir. Cette partie est un carré de 60px de côté. De plus, on sait qu'il y a une bande horizontale de 28px en haut qui ne nous intéresse pas, on va donc devoir décaler l'image de 28px vers le haut, et comme les axes sont inversés on écrit -28px :

.rocket_body{
   width:60px; height:60px;
   background: url('rocket-sprite.png') -28px 0px no-repeat;
}

Bien, maintenant que vous connaissez la technique, je ne vais pas vous laisser positionner tous les éléments un par un, je vais vous fournir la nouvelle version du CSS ça ira plus vite ^^ :

#rocket_dummy{
	position:absolute; left:-100px; top:20px;
	width:55px; height:55px;
}
#rocket_mobile{
	position:absolute; top:0; margin:85px 0 0 -4px;
	width:60px; height:185px;
}
#rocket_mobile.fixed{position:fixed; top:0;}

#rocket_dock{
	position:absolute; width:28px; height:28px; top:164px; left:12px;
	background:url('rocket-sprite.png') 0 0 no-repeat;
}

#rocket_mobile .rocket_body{
	position:absolute; width:60px; height:60px; top:63px; left:0;
	background:url('rocket-sprite.png') 0 -28px no-repeat;
}

#rocket_mobile .fire{
	position:absolute; width:35px; height:78px;
}
#rocket_mobile .fire.top{
	left:13px; top:3px;
	background:url('rocket-sprite.png') -60px 0 no-repeat;
}
#rocket_mobile .fire.top.on{background-position:-95px 0;}
#rocket_mobile .fire.bottom{
	left:13px; top:104px;
	background:url('rocket-sprite.png') -60px -78px no-repeat;
}
#rocket_mobile .fire.bottom.on{background-position:-95px -78px;}

Vous remarquez au passage que j'ai ajouté une règle qui modifie la position de l'arrière plan pour les éléments qui ont la classe 'fire' et la classe 'on'. Ça permet tout simplement d'afficher ou de masquer la flamme !

Voici le résultat :

Tester /ressources/exemple/jquery-rocket/step2.html
Fusée habillée
Vous pouvez tester la classe 'on' en cliquant sur les boutons Allumer / Éteindre

Étape 3 : suivre le scroll avec position:fixed

En en a terminé avec la partie HTML et CSS, maintenant il reste le plus difficile : le suivi du scroll en javascript, à l'aide de jQuery.

Tout l'enjeu va être de jouer sur le positionnement de #rocket_mobile en grés des variations du défilement. Regardons les propriétés CSS de cet élément de plus près :

#rocket_mobile{
	position:absolute; top:0; margin:85px 0 0 -4px;
	width:60px; height:185px;
}
#rocket_mobile.fixed{position:fixed; top:0;}

Par défaut, #rocket_mobile est positionné en absolute par rapport à son parent : #rocket_dummy. Pour le moment il disparait lorsqu'on scroll.

Mais il y a moyen de faire en sorte qu'il soit affiché en permanence, en utilisation la propriété position:fixed.

Cet attribut est un peu particulier, puisqu'il positionne l'élément par rapport à la fenêtre du navigateur, et pas par rapport au document. Et il se trouve que lorsqu'on scroll c'est le document qui défile, mais la fenêtre du navigateur, elle, ne bouge absolument pas. Du coup on se retrouve avec notre élément qui "flotte" au dessus du document et qui reste toujours au même endroit, il est fixe.

L'idée c'est donc d'attribuer ou de retirer position:fixed en fonction du scroll :

  • lorsqu'on est en haut de la page, on reste en position absolute
  • dès qu'on scroll suffisamment pour que #rocket_mobile s'apprête à disparaitre en haut de l'écran, on le passe en position:fixed pour figer sa position

Voici le code javascript qui permet de gérer ce changement de position :

<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js"></script>
<script type="text/javascript">
/**
 * Animation de la fusée avec Javascript
**/
jQuery(document).ready(function($){
	// Stockage des références des différents éléments dans des variables
	rocket     = $('#rocket_mobile');
	firetop    = $('#rocket_mobile .fire.top');
	firebottom = $('#rocket_mobile .fire.bottom');

	// Calcul de la marge entre le haut du document et #rocket_mobile
	fixedLimit = rocket.offset().top - parseFloat(rocket.css('marginTop').replace(/auto/,0));

	// On déclenche un événement scroll pour mettre à jour le positionnement au chargement de la page
	$(window).trigger('scroll');

	$(window).scroll(function(event){
		// Valeur de défilement lors du chargement de la page
		windowScroll = $(window).scrollTop();

		// Mise à jour du positionnement en fonction du scroll
		if( windowScroll >= fixedLimit ){
			rocket.addClass('fixed');
		} else {
			rocket.removeClass('fixed');
		}
	});
});
</script>

Explication:

On commence par récupérer des références vers les éléments qu'on va manipuler dans des variables. Vous vous demandez peut-être pourquoi faire ça, on pourrait simplement utiliser le sélecteur jQuery quand on souhaite manipuler ces éléments...

Vous avez raison, mais comme on va très souvent y accéder, c'est mieux de les sélectionner une seule fois au chargement de la page et de les stocker plutôt que de les sélectionner à chaque fois, car c'est beaucoup plus rapide en terme de performance (et donc de fluidité de l'animation).

Ensuite on calcul le nombre de pixels qui sépare le haut de la page du haut de #rocket_mobile. Pour cela on utilise la fonction jQuery .offset(), qui retourne la position de l'élément par rapport au document. Seulement, cette méthode offset ne prend pas on compte les marges (margin et padding) de l'élément, donc on retire cette marge, que l'on récupère via parseFloat(rocket.css('marginTop').replace(/auto/,0)).

Enfin, on défini une fonction qui va être exécutée à chaque fois qu'on scroll, dont le rôle et d'ajouter ou de retirer la classe .fixed (et donc le position:fixed) à #rocket_mobile en fonction du défilement.

Tester /ressources/exemple/jquery-rocket/step3.html
position:fixed automatique
La fusée reste affichée à l'écran en permanence

Étape 4 : animation des moteurs

Dans cette étape on va gérer l'affichage et des flammes jaunes qui s'allument lorsqu'on scroll.

On ne doit afficher qu'une seule flamme en fonction du sens du scroll :

  • lorsqu'on scroll vers le bas on affiche la flamme du haut
  • lorsqu'on scroll vers le haut on affiche la flamme du bas

Et ça va un peu se corser, parce que jQuery ne permet pas de connaître le sens du scroll. Du coup il va falloir trouver une astuce pour avoir cette information.

Pour ça, on va créer une nouvelle variable LAST_SCROLL_OFFSET qui va être mises à jour à chaque événement scroll. Du coup, il suffit de comparer la valeur de LAST_SCROLL_OFFSET avec la valeur actuelle du scroll (donnée par $(window).scrollTop();) :

  • si le scroll a augmenté, cela signifie qu'on a scrollé vers le bas
  • sinon, c'est qu'on a scrollé vers le haut
jQuery(document).ready(function($){
	// Stockage des références des différents éléments dans des variables
	rocket     = $('#rocket_mobile');
	firetop    = $('#rocket_mobile .fire.top');
	firebottom = $('#rocket_mobile .fire.bottom');
	LAST_SCROLL_OFFSET = $(window).scrollTop();

	// Calcul de la marge entre le haut du document et #rocket_mobile
	fixedLimit = rocket.offset().top - parseFloat(rocket.css('marginTop').replace(/auto/,0));

	// On déclenche un événement scroll pour mettre à jour le positionnement au chargement de la page
	$(window).trigger('scroll');

	$(window).scroll(function(event){
		// Valeur de défilement lors du chargement de la page
		windowScroll = $(window).scrollTop();

		// Mise à jour du positionnement en fonction du scroll
		if( windowScroll >= fixedLimit ){
			rocket.addClass('fixed');
		} else {
			rocket.removeClass('fixed');
		}

		// Animation flammes
		// Allumage
		if( rocket.hasClass('fixed') ){
			if( windowScroll > LAST_SCROLL_OFFSET ){
				// DOWN
				firetop.addClass('on');
				firebottom.removeClass('on');
			} else {
				// UP
				firetop.removeClass('on');
				firebottom.addClass('on');
			}
		}

		// Mise à jour
		LAST_SCROLL_OFFSET = windowScroll;
	});
});

Allumer le feu c'est bien, mais il faut aussi pouvoir l'éteindre dès qu'on arrête de scroller. Or il n'existe pas d'événement "scrollstop" en JavaScript, donc il va falloir une fois de plus improviser.

Le plus facile est de déclencher une fonction après chaque scroll avec un petit délai (de 70 millisecondes). Le rôle de cette fonction va tout simplement être d'éteindre les moteurs (en supprimant la classe 'on').

Mais attention, il y a une autre condition :pour éteindre les moteurs il faut que le scroll soit terminé. La seule façon de le savoir c'est de mémoriser l'heure précise à chaque événement scroll. À partir de là il ne reste plus qu'à vérifier que le dernier scroll est suffisamment "ancien" pour éteindre les moteurs.

Voilà le javascript final :

jQuery(document).ready(function($){
	// Stockage des références des différents éléments dans des variables
	rocket     = $('#rocket_mobile');
	firetop    = $('#rocket_mobile .fire.top');
	firebottom = $('#rocket_mobile .fire.bottom');
	LAST_SCROLL_OFFSET = $(window).scrollTop();
	LAST_SCROLL_TIME   = new Date().getTime();

	// Calcul de la marge entre le haut du document et #rocket_mobile
	fixedLimit = rocket.offset().top - parseFloat(rocket.css('marginTop').replace(/auto/,0));

	// On déclenche un événement scroll pour mettre à jour le positionnement au chargement de la page
	$(window).trigger('scroll');

	$(window).scroll(function(event){
		// Valeur de défilement lors du chargement de la page
		windowScroll = $(window).scrollTop();

		// Mise à jour du positionnement en fonction du scroll
		if( windowScroll >= fixedLimit ){
			rocket.addClass('fixed');
		} else {
			rocket.removeClass('fixed');
		}

		// Animation flammes
		// Allumage
		if( rocket.hasClass('fixed') ){
			if( windowScroll > LAST_SCROLL_OFFSET ){
				// DOWN
				firetop.addClass('on');
				firebottom.removeClass('on');
			} else {
				// UP
				firetop.removeClass('on');
				firebottom.addClass('on');
			}
		}

		// Arrêt
		setTimeout(function(){
			if( new Date().getTime() - LAST_SCROLL_TIME > 50 ){
				firetop.removeClass('on');
				firebottom.removeClass('on');
			}
		},70);

		// Mise à jour variables
		LAST_SCROLL_OFFSET = windowScroll;
		LAST_SCROLL_TIME   = new Date().getTime();
	});
});
Tester /ressources/exemple/jquery-rocket/step4.html
Animation des moteurs
La fusée s'allume et s'éteint

Ce tutoriel jQuery est maintenant terminé. Dans le premier exemple il y a un petit effet d'inertie lorsque la fusée s'arrête, je n'ai pas expliqué ce point dans cette formation mais sachez que ce "bonus" est fourni dans le projet que vous pouvez télécharger sur stockmotion :

jQuery Rocket Sources + bonus + fichier Photoshop du sprite CSS
Télécharger

Ce projet contient :

  • Les sources HTML/CSS/Javascript de ce tutoriel
  • En bonus une version spéciale avec un effet d'inertie
  • Le fichier Photoshop du sprite CSS, vous permettant de modifier les couleurs ou la forme de la fusée

Allez donc jeter un oeil sur cette page : démon d'halloween.

6 commentaires :
commentaire n°2340 par JulianBaker1993
JulianBaker1993 jeudi 6 septembre 2012, 08:43
Thank you!
commentaire n°2356 par Edner
Edner mardi 18 septembre 2012, 01:52
Bonjour Galdon. Je suis le tuto et j'ai realise la fusee. Puis-je avoir les fichiers de l'element qui affiche les reseaux sociaux a droite de votre page?
commentaire n°2415 par Jguiss
Jguiss lundi 19 novembre 2012, 10:15
Super cet effet ! Je vais voir si je peux l'intégrer dans une future version de mon site !

PS : Faut suppr mon com précedent, merci :)
commentaire n°2452 par Clément____a
Clément____a samedi 29 décembre 2012, 14:54
Bonjour,

merci pour ce code très intéressant, seulement j'essaye de l'appliquer sur des éléments (paniers ou blocs d'options recherche) qui ont une hauteur de 500px par ex, donc quand je scroll en bas de la page, les roquettes rentrent en collision avec le footer !

comment peut on faire pour demander qu'il s'arrête à une distance x du bas de la page ? j'ai essayé mais suis novice en JS ! merci d'avance
commentaire n°2725 par jodou
jodou jeudi 23 janvier 2014, 22:36
Bonjour,

Merci pour le tuto. Sur votre site il y a un bandeau en pied de navigateur qui reste fixe (<div id="excel_forum_promo">). Cette effet correspond tout à fait à ce que je souhaite avoir sur mon site à savoir ; mon footer toujours visible quelque soit la longueur de la page et l'état du scroll.

Est-ce que vous pouvez me dire comment vous l'avez réalisé?

Merci par avance.
commentaire n°2732 par Galdon
Galdon jeudi 30 janvier 2014, 16:42

Très simplement en utilisation la propriété CSS postiion:fixed :

#excel_forum_promo {position:fixed; bottom:0; left:0; width:100%; height:80px; background-color:#176f41;}
facultatif
Facebook Twitter RSS Email
Forum Excel
Venez découvrir le nouveau forum excel question/réponse à la stackoverflow.com !
Forum Excel
hit parade n'en a rien a foutre du W3C Positionnement et Statistiques Gratuites Vincent Paré