Créer un menu latéral (swipe menu) avec Touch Events et Click en CSS 3 et Javascript natif

Mathieu Chartier 7 janvier 2019 à 14:00 Tutoriels illustrés 0 commentaire

Et voilà un petit tutoriel technique pour débuter 2019 sur de bonnes bases. Quoi de mieux qu'un peu d'HTML, de CSS 3 et de Javascript Vanilla (natif) pour faire oublier le foie gras, les huîtres et autres mets gourmands ? Nous allons prendre le cas d'un menu latéral ergonomique et UX Design (un swipe menu ou un slide menu dans le jargon) pour présenter plusieurs concepts en un rien de temps :

  • les @keyframes et animations en CSS (et @-webkit-keyframes pour plus de compatibilité...) ;
  • les techniques de Swipe ou Slide en Javascript Vanilla (natif), compatibles partout et sans avoir à passer par jQuery et consorts ;
  • les événéments Touch (Touch events) pour aller plus loin dans l'ergonomie et l'UX ;
  • les media queries avec @media pour faire un peu de responsive web design (ça, vous devriez déjà maîtriser sans trop de problème... ^^).

Nous allons avoir besoin de toutes ces techniques pour aller au bout de la démarche, et à la fin, nous pourrons obtenir un menu latéral avec effet swipe ou slide en Javascript et CSS 3 compatible sur ordinateur, tablette et mobile. Voici ce qui sera possible avec le code complet :

  • cliquer sur un bouton de menu burger (menu classique à trois barres) pour déclencher l'apparition du menu par la gauche ;
  • faire glisser un doigt de gauche à droite sur l'écran d'un smartphone ou d'un mobile pour faire apparaître le menu (et dans le sens inverse pour le refermer).

L'icône du menu burger est animée comme vous pouvez le voir pour que cela fasse encore plus UX design, et les deux événements (Touch Event et Click) fonctionnent en parallèle. Le menu a volontairement été adapté de plusieurs manières pour que vous ayez plusieurs idées de possibilités (menu en pleine largeur sur mobile, à 50% de largeur sur tablette et à 25% sur grand écran).

Avant de passer aux codes détaillées, voici un gif animé ci-dessous vous montre à quoi cela ressemble sur ordinateur...

Et voici le résultat avec un menu en pleine largeur sur mobile, avec effet swipe/slide et "click" en JS natif.

Effet slide menu ou swipe menu en Javascript natif et CSS 3 sur mobile

Pressé de savoir faire tout ça en quelques lignes de code (car tout le secret réside dans quelques dizaines de ligne de code au total...) ? Vous pouvez d'ores-et-déjà télécharger le code complet de l'exemple présenté dans les captures d'écran, ça pourrait vous aider pour les copier/coller. C'est parti... :-)

Télécharger “SwipeMenu.zip”SwipeMenu.zip – Téléchargé 214 fois – 52 KB

Étape n°1

Code HTML

Création de l’HTML utile pour le menu burger mais aussi pour le menu qui apparaîtra latéralement.

Ici, le menu est un bloc <nav> à part et vous pouvez placer le bloc du menu burger là où c’est pertinent pour vous.

Dans l’exemple complet à télécharger, il y a bien plus d’HTML puis j’ai enrobé tout cela dans une interface d’exemple pour que ça ait plus de consistance… ^^

<nav id="menu">
	<ul>
		<li><a href="#">Item 1</a></li>
		<li><a href="#">Item 2</a></li>
		<li><a href="#">Item 3</a></li>
		<li><a href="#">Item 4</a></li>
		<li><a href="#">Item 5</a></li>
	</ul>
</nav>

Et pour le menu burger (à placer où vous voulez le voir s’afficher)…

<div id="menu-burger">
	<div class="bar1"></div>
	<div class="bar2"></div>
	<div class="bar3"></div>
</div>

Étape n°2

Code CSS

Mettre l’ensemble un peu en forme en CSS. Le code présenté correspond à l’exemple des captures d’écran, vous pouvez bien sûr modifier le rendu final selon vos choix.

Il convient juste de respecter le style du menu burger et notamment les transformations 2D (avec transform).

/* Menu burger */
#menu-burger {position:absolute; top:2.5em; right:3.5em; display:inline-block; cursor:pointer;}
#menu-burger .bar1,
#menu-burger .bar2,
#menu-burger .bar3 {width:32px; height:5px; background-color:#F1F1F1; margin:6px 0; transition:0.4s;}
#menu-burger:hover {opacity:.75; transition:.5s;}
#menu-burger.clicked .bar1 {-webkit-transform:rotate(-45deg) translate(-9px, 6px); transform:rotate(-45deg) translate(-9px, 6px);}
#menu-burger.clicked .bar2 {opacity: 0;}
#menu-burger.clicked .bar3 {-webkit-transform:rotate(45deg) translate(-8px, -8px); transform:rotate(45deg) translate(-8px, -8px);}

/* Main menu (slide) */
#menu {position:fixed; z-index:9999; left:-25%; top:auto; background:#e74c3c; padding:4em 2em; width:25%; min-height:100%; box-shadow:1px 0 1px #9A2519;}
#menu ul li {list-style:none;}
#menu ul li a {display:block; color:#fff; text-decoration:none; font-weight:700; background:#2c3e50; transition:background .5s; padding:1em; text-align:center; margin-bottom:.5em; box-shadow:1px 1px 0 #666}
#menu ul li a:hover {background:#f1c40f; transition:background .5s;}

Étape n°3

Ajouter les animations CSS 3 avec la propriété animation et les @keyframes.

Notez bien la présence du mot-clé « forwards » dans la propriété animation. Cela permet de clôturer l’animation et de la laisser en l’état (sinon le menu disparaît après…).

Il n’existe pas une compatibilité pleine de @keyframes (ne fonctionne pas sur IE 6 à 9) mais cela reste globalement fonctionnel partout en 2019. Il n’existe à priori pas de polyfill spécifique donc il faudrait réaliser l’animation avec la propriété transition dans ce cas (mais ce serait un peu moins fonctionnel d’après mes tests), ou via Javascript pour contourner le problème…

#menu.visible {animation:.5s slideRight ease-in forwards; transition-delay:0;}
#menu.invisible {animation:1s slideLeft ease-out forwards; transition-delay:0;}

/* Animations pour le menu slide */
@keyframes slideRight {
	from {left:-25%;}
	to {left:0%;}
}
@-webkit-keyframes slideRight {
	from {left:-25%;}
	to {left:0%;}
}
@keyframes slideLeft {
	from {left:0%;}
	to {left:-25%;}
}
@-webkit-keyframes slideLeft {
	from {left:0%;}
	to {left:-25%;}
}

Étape n°4

Vous pouvez également ajouter le CSS pour la version responsive de l’effet de menu latéral.

Ici, rien n’est obligatoire et peut être totalement modifié, c’est juste pour avoir des rendus différents selon les tailles d’écran, grâce aux media queries.

/* Responsive design */
@media (max-width:1024px) {
	#menu {left:-50%; width:50%;}
	#menu-burger.clicked {position:fixed;}

	/* Animations pour le menu slide */
	@keyframes slideRight {
		from {left:-50%;}
		to {left:0%;}
	}
	@-webkit-keyframes slideRight {
		from {left:-50%;}
		to {left:0%;}
	}
	@keyframes slideLeft {
		from {left:0%;}
		to {left:-50%;}
	}
	@-webkit-keyframes slideLeft {
		from {left:0%;}
		to {left:-50%;}
	}
}
@media (max-width:600px) {
	#menu-burger {top:1em; right:2em; z-index:9999;}
	#menu {left:-100%; width:100%;}

	/* Animations pour le menu slide */
	@keyframes slideRight {
		from {left:-100%;}
		to {left:0%;}
	}
	@-webkit-keyframes slideRight {
		from {left:-100%;}
		to {left:0%;}
	}
	@keyframes slideLeft {
		from {left:0%;}
		to {left:-100%;}
	}
	@-webkit-keyframes slideLeft {
		from {left:0%;}
		to {left:-100%;}
	}
}

Étape n°5

Code Javascript

Il est temps de passer à Javascript.

La première étape permet de récupérer les blocs cibles (menu burger et menu latéral), cela nous sera utile tout au long des étapes Javascript…

Gérons dans un premier temps l’effet de l’événement « click », qui est le plus simple.

Le problème des conflits d’événements

Comme nous prévoyons d’utiliser les événements Touch (touchstart notamment) de Javascript, j’ai dû rajouter un petit bout de code qui permet de détecter le premier événement réalisé sur certains types d’écrans, afin d’éviter tout conflit d’événements JS. En effet, peut arriver que « click » et « touchstart » entrent en conflit, ce qui provoque l’ouverture/fermeture lors du clic sur le menu burger…

Pour le reste, rien de bien compliqué, nous créons une sorte de « toggle » simple pour ajouter ou non une classe CSS. Selon la classe présente ou non, le style CSS adapté s’applique et créé l’animation.

En commentaire, j’ai mis une version alternative pour un cas, avec l’objet .classList en Javascript, mais la compatibilité est limitée…

// Récupération des blocs
var mainMenu = document.querySelector("#menu");
var burgerMenu = document.querySelector("#menu-burger");

/*===============================*/
/*=== Clic sur le menu burger ===*/
/*===============================*/
// Vérifie si l'événement touchstart existe et est le premier déclenché
var clickedEvent = "click"; // Au clic si "touchstart" n'est pas détecté
window.addEventListener('touchstart', function detectTouch() {
	clickedEvent = "touchstart"; // Transforme l'événement en "touchstart"
	window.removeEventListener('touchstart', detectTouch, false);
}, false);

// Créé un "toggle class" en Javascrit natif (compatible partout)
burgerMenu.addEventListener(clickedEvent, function(evt) {
	console.log(clickedEvent);
	// Modification du menu burger
	if(!this.getAttribute("class")) {
		this.setAttribute("class", "clicked");
	} else {
		this.removeAttribute("class");
	}
	// Variante avec x.classList (ou DOMTokenList), pas 100% compatible avant IE 11...
	// burgerMenu.classList.toggle("clicked");

	// Créé l'effet pour le menu slide (compatible partout)
	if(mainMenu.getAttribute("class") != "visible") {
		mainMenu.setAttribute("class", "visible");
	} else {
		mainMenu.setAttribute("class", "invisible");
	}
}, false);

Étape n°6

Rendu visuel à cet étape…

Un clic sur le bouton entraîne la déformation du menu burger pour se transformer en croix, et le menu latéral apparaît en arrivant de la gauche…

Swipe effect Javascript pendant un clic

Étape n°7

Dernière étape pour gérer les Touch Events avec Javascript. L’idée est de créer un effet d’ouverture du menu quand on glisse avec un doigt sur un écran tactile de la gauche vers la droite, et un effet de fermeture dans l’autre sens.

Pour capter un geste en particulier, il convient de trouver le sens de déplacement (gauche vers la droite, haut vers le bas, etc.) et d’indiquer une distance minimum à parcourir pour que le geste semble suffisant (un glissement de doigt signifie que « x » pixels sont parcours avec un appui sur l’écran tactile…).

Pour arriver à nos fins, il faut premièrement capter les événements touchstart (au premier toucher), touchmove (pendant le déplacement) et touchend (fin du toucher). Chaque étape méritera son petit bout de code :

  • touchstart sert à initialiser les variables utiles, comme le premier pixel touché, la distance entre les pixels (forcément à 0 ici), etc.
  • touchmove sert uniquement à éviter les effets de bord dû à la captation de points de contact.
  • touchend fait le gros du travail puisqu’il calcule le dernier pixel toucher, et donc la distance entre le premier et le dernier. Grâce à cela nous pouvons aussi capter le sens du swipe/slide sur l’écran. Et de fait, nous pouvons lancer la fonction de l’effet de menu latéral… ^^
/*===============================*/
/*=== Swipe avec Touch Events ===*/
/*===============================*/
// Si l'écran est plus petit que "x" pixels (optionnel) // 1024px ici
if(screen.width <= 1024) {
	var startX = 0; // Position de départ
	var distance = 100; // 100 px de swipe pour afficher le menu

	// Au premier point de contact
	window.addEventListener("touchstart", function(evt) {
		// Récupère les "touches" effectuées
		var touches = evt.changedTouches[0];
		startX = touches.pageX;
		between = 0;
	}, false);

	// Quand les points de contact sont en mouvement
	window.addEventListener("touchmove", function(evt) {
		// Limite les effets de bord avec le tactile...
		evt.preventDefault();
		evt.stopPropagation();
	}, false);

	// Quand le contact s'arrête
	window.addEventListener("touchend", function(evt) {
		var touches = evt.changedTouches[0];
		var between = touches.pageX - startX;

		// Détection de la direction
		if(between > 0) {
			var orientation = "ltr";
		} else {
			var orientation = "rtl";
		}

		// Modification du menu burger
		if(Math.abs(between) >= distance && orientation == "ltr" && mainMenu.getAttribute("class") != "visible") {
				burgerMenu.setAttribute("class", "clicked");
		}
		if(Math.abs(between) >= distance && orientation == "rtl" && mainMenu.getAttribute("class") != "invisible") {
				burgerMenu.removeAttribute("class");
		}

		// Créé l'effet pour le menu slide (compatible partout)
		if(Math.abs(between) >= distance && orientation == "ltr" && mainMenu.getAttribute("class") != "visible") {
			mainMenu.setAttribute("class", "visible");
		}
		if(Math.abs(between) >= distance && orientation == "rtl" && mainMenu.getAttribute("class") != "invisible") {
			mainMenu.setAttribute("class", "invisible");
		}
	}, false);
}

Conclusion

L’exemple présenté ici reste simple et peut totalement être modifié à votre guise, c’est le principe qu’il faut retenir et comprendre. Il convient de ne pas oublier les risques de conflits entre « click » et « touchstart » par exemple, ou encore bien faire chaque étape autour des Touch Events pour calculer le sens du swipe ou slide effect, etc.

Vous pouvez également vous réaliser autre chose qu’un menu avec le même effet, il faudrait donc peut-être du Javascript et HTML plus poussé pour réaliser cela. Mais quoi qu’il en soit, l’effet d’apparition latérale serait identique.

J’espère que ce premier tutoriel de 2019 vous aura plu en tout cas, n’hésitez pas à me faire des retours… ;-)