L’effet de scroll fluide est déjà intégré, en fonction de vos paramètres utilisateur, sur certains navigateurs. Il se manifeste par un défilement fluide de la page web lorsque vous descendez ou montez grâce à la molette de votre souris, ou les touches de votre clavier. Il est possible de générer cet effet au clic sur une ancre grâce à JavaScript.

Il y a quelques temps de cela (peut-être trois ans maintenant) j’avais trouvé un script JS pour ajouter cet effet, mais celui-ci ne prenait en compte que les ancres dotées de l’attribut name, sous cette forme :

<a href="#cible">Aller à "cible"</a>
<!-- plus loin dans la page -->
<a name="cible"></a>

Or la création d’un élément anchor dans le seul but de créer une ancre ne me semblait pas la méthode la plus propre, mais comme j’étais une quiche en JavaScript (ça n’a pas trop changé d’ailleurs, puisque j’utilise principalement jQuery pour me faciliter l’existence), j’ai fait avec ce code jusqu’à ce que je découvre jQuery et le plugin jQuery.scrollTo().
Plugin plutôt efficace, mais très lourd pour la petite utilisation que j’en faisais.

C’est pourquoi aujourd’hui je vous propose de créer votre propre script jQuery de smoothscroll.
Voici une petite démonstration sommaire :
Effet smoothscroll.

Solution de base

NB : les codes JavaScript qui vont suivre sont à placer après l’appel à la bibliothèque jQuery, fait sous cette forme, par exemple :

<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.6.2/jquery.min.js" type="text/javascript"></script>
<script type="text/javascript">
<!-- votre script ici -->
</script>

Dans le cadre d’une utilisation simple des ancres, votre code HTML doit ressembler à peu près à cela :

<a href="#contact">Contact</a>
<!-- plus loin dans la page -->
<h2 id="contact">Contact</h2>

On a donc simplement un href portant une # (dièse) suivie de la valeur de l’id (unique, mais inutile de le rappeler… hein ?) ciblé.

On aurait donc un script jQuery dans ce goût là :

$('a[href^="#"]').click(function(){
	var the_id = $(this).attr("href");

	$('html, body').animate({
		scrollTop:$(the_id).offset().top
	}, 'slow');
	return false;
});

Au click sur un lien dont l’attribut href commence (^=) par #, j’enregistre la valeur de son attribut href ($(this).attr("href")) dans la variable the_id.
Puis je fais une animation (animate()) qui consiste à scroller (scrollTop) vers le haut de l’élément ciblé ($(the_id).offset().top), lentement (slow).
return false; permet d’éviter le comportement normal lors de l’évènement de clic sur le lien (à savoir le saut vers l’ancre et son affichage dans l’url).

La présence du sélecteur $('html, body') permet de corriger un bogue sur Webkit (Chrome et Safari) qui ne semble comprendre qu’une animation sur l’élément body

Prévoir les autres cas de figure, et créer une fonction

Voilà mais…, peut-être que vous allez vouloir créer une fonction identique pour un système qui mélange id et name, ou que vous avez l’intention de distribuer généreusement un script passe-partout à vos visiteurs.

Pour cela il faut prévoir l’exception que nous connaissons, le code va donc être plus long.
Je vous le présente, nous allons le détailler après :

function juizScrollTo(element){			
	$(element).click(function(){
		var goscroll = false;
		var the_hash = $(this).attr("href");
		var regex = new RegExp("\#(.*)","gi");
		var the_element = '';
 
		if(the_hash.match("\#(.+)")) {
			the_hash = the_hash.replace(regex,"$1");
 
			if($("#"+the_hash).length>0) {
				the_element = "#" + the_hash;
				goscroll = true;
			}
			else if($("a[name=" + the_hash + "]").length>0) {
				the_element = "a[name=" + the_hash + "]";
				goscroll = true;
			}
 
			if(goscroll) {
				$('html, body').animate({
					scrollTop:$(the_element).offset().top
				}, 'slow');
				return false;
			}
		}
	});					
};
juizScrollTo('a[href^="#"]');

Pour résumer simplement, nous allons exécuter notre animation de tout à l’heure (ici lignes 22 à 25) dans la seule condition que notre variable goscroll soit ok (true).
Notre problématique est la suivante : nous savons que l’utilisateur va cliquer sur un lien ayant un attribut href commençant par #, mais nous ne savons pas s’il faut le mener vers l’élément porteur de l’attribut correspondant name ou id.

On va donc dans un premier temps faire un simple contrôle en cherchant une dièse dans l’attribut href (l.8), si on trouve la dièse on récupère la valeur se trouvant juste après (l.9) grâce à la RegExp préparée l.6. Une fois qu’on a la chaîne en question, on va pouvoir chercher un élément porteur de l’id ou du name correspondant à cette chaîne.

Il faut bien commencer par l’un ou par l’autre, j’ai choisi de commencer par l’id (lignes 13 à 14). Il suffit de compter le nombre de « #notre_chaine », si on en a au moins 1 (on ne devrait en avoir qu’un !) alors on est bien en recherche d’un id, si on en a pas, alors on compte le nombre de a porteur de l’attribut name de valeur « notre_chaine ». (lignes 15 à 18)

Dans cette étape, nous sommes censés valider l’une ou l’autre des situations, nous récupèrerons donc forcément une valeur de la variable hash, et nous passerons notre variable goscroll à true pour effectuer l’animation.
S’il nous arrivait de ne vérifier ni l’une ni l’autre des conditions (ni id, ni name), alors le visiteur verra la page sauter vers le haut, puisque votre lien semble renvoyer vers une ancre inexistante.

Pour rendre effectif le fonctionnement de la fonction, il ne reste plus qu’à en faire l’appel en précisant les a concernés. (ligne 29)

Personnellement, j’ai encore un peu de mal avec notre sélecteur sur html et body, l’animation ne s’exécute-t-elle pas deux fois simultanément ?
Même si ça marche, ce n’est pas très propre.
Dans l’étape d’après, nous allons ajouter un contrôle, puisque nous savons que la spécificité se trouve sur webkit.

Création d’une extension de jQuery

Cette partie du tutoriel est un peu plus complexe, mais vous permettra de créer une fonction d’extension à la bibliothèque jQuery.
Cette fonction vous permettra d’ajouter des paramètres et de chaîner avec d’autres fonctions jQuery.

Voyons le code de plus près :

(function($){
	$.fn.juizScrollTo = function(speed){
		if(!speed) var speed = 'slow';
 			
		return this.each(function(){
			$(this).click(function(){
				var goscroll = false;
				var the_hash = $(this).attr("href");
				var regex = new RegExp("(.*)\#(.*)","gi");
				var the_element = '';
 
				if(the_hash.match("\#(.+)")) {
 
					the_hash = the_hash.replace(regex,"$2");
 
					if($("#"+the_hash).length>0) {
						the_element = "#" + the_hash;
						goscroll = true;
					}
					else if($("a[name=" + the_hash + "]").length>0) {
						the_element = "a[name=" + the_hash + "]";
						goscroll = true;
					}
 						
					if(goscroll) {
						var container = 'html';
						if ($.browser.webkit) container = 'body';
 
						$(container).animate({
							scrollTop:$(the_element).offset().top
						}, speed);
						return false;
					}
				}
			});
		});
	};
})(jQuery)
 	
$('a[href^="#"]').juizScrollTo('fast').css('color','red');

Comme vous pouvez le voir en dernière ligne de ce code, l’appel à la fonction se fait différemment et propose une possibilité de chaînage et de paramétrage.

	$.fn.juizScrollTo = function(speed){
	};

Pour obtenir ce résultat, nous créons l’extension grâce à la ligne 2 où les informations importantes figurent (le nom de la fonction et les paramètres) sous cette forme :
$.fn.nom_de_fonction = function(parametre1,parametre2).

		if(!speed) var speed = 'slow';

La ligne d’après nous permet de vérifier si le paramètre speed a été renseigné, s’il ne la pas été nous attribuons une valeur par défaut.
Il est possible d’utiliser la fonction extend() pour attribuer un plus grand nombre de paramètres par défaut, cette méthode est plus lisible, mais pour l’exemple nous en resterons à cette manière de procéder.

		return this.each(function(){
		});

Nous en sommes à la ligne 5, qui possède une particularité : toutes les occurrences (each()) de this sont retournées. C’est cette partie qui nous permet de chaîner (l.36). En retournant this (l’élément sur lequel influe notre fonction) nous pouvons le récupérer et le tripatouiller avec une autre fonction :)
Si notre fonction avait pour but de retourner une valeur autre que l’élément ciblé, le chaînage ne serait plus possible.

		$(this).click(function(){
		});

Nous entrons ici dans le gestionnaire d’événement dont nous avons vu dans les étapes précédentes.
Le script reste donc quasiment identique, c’est pourquoi je vais m’attarder sur les quelques changements uniquement.

var container = 'html';  
if ($.browser.webkit) container = 'body'; 

Nous créons la variable container qui aura pour valeur html par défaut, ou body si jamais on détecte une navigation sous Webkit.
Je vous en avais parlé avant, ça nous permet d’effectuer une animation en ne ciblant que l’un des deux éléments.
Il suffit ensuite de remplacer notre ancien sélecteur par la variable (ligne 30).

}, speed);

Sur cette ligne 31 du code, nous avons renseigné notre variable speed, qui correspond anciennement à la valeur "slow".
L’utilisateur de la fonction peut donc aisément y renseigner une valeur de son choix.

Pitite conclusion

Pour ceux qui sont arrivés au bout de ce tutoriel, je vous dis bravo !
En espérant que votre fonction fonctionne à merveille, n’hésitez pas à compléter, corriger ou critiquer ce tutoriel via les commentaires, ou mon formulaire de contact pour les timides.

Il est possible d’aller encore plus loin pour les fondus de jQuery. Par exemple, la fonction animate() peut accueillir une fonction de callback. C’est une fonction qui est appelée une fois l’animation terminée. Vous pouvez donc ajouter en paramètre de la fonction que nous venons de créer, une possibilité de personnaliser la fonction de callback.

Je vous laisse y réfléchir…

Allez, enjoy ! ;)

Bonux

Après publication de cet article (il y a 20 minutes), je me suis rendu compte que la navigation au clavier n’était pas aisée. En effet, une fois la touche Entrée pressée sur un des liens, l’effet de scroll s’effectue, mais le focus n’est pas donné à l’élément ciblé, et pour cause : il ne possède pas de tabindex (dans mon cas, c’est une div).
La solution consiste à passer en fonction de callback de la fonction animate() la fonction suivante :

function(){
$(the_hash).attr('tabindex','0').focus().removeAttr('tabindex');
}

Elle permet d’ajouter un tabindex de valeur 0 (permet d’offrir la possibilité de porter le focus à un élément sans casser l’ordre de tabulation), attribuer le focus, puis retirer le tabindex aussitôt pour ne pas nous retrouver avec des effets de bord.
Je ne sais pas ce que ça vaut en terme d’accessibilité, mais ça semble rendre bien plus aisée la navigation au clavier.
A vous de voir sur la page de démonstration.

Cet article a été étendu sur cette mise à jour. Allez y jeter un œil ;)

Il existe une mise à jour

  1. jQuery – Effet smooth scroll (défilement fluide)