Faire un lien sur toute une zone en CSS

On m’a récemment rappelé sur Twitter une pratique qui m’exaspère au plus haut point ces derniers temps. Sur certains sites, le clic secondaire sur des liens est rendu inutilisable. C’est le cas par exemple sur Factornews (j’aime beaucoup Factornews, notamment quand ils font des jeux de mots comme à la fin de cet article).

Factornews

Dans la première zone d’actualités du site, chaque encart d’actualité est entièrement cliquable. Mais si j’utilise le clic de la molette de ma souris (pour ouvrir le lien dans un nouvel onglet), il ne se passe strictement rien. Si je maintiens appuyé la touche majuscule de mon clavier (pour ouvrir le lien dans une nouvelle fenêtre), mon raccourci sera ignoré et le lien sera ouvert dans la fenêtre courante.

Si le clic se comporte comme ça, c’est parce que ce n’est pas un vrai lien HTML. Ici, pour chaque actualité, seul le titre de l’actualité est dans une balise <a>. Le clic sur le reste de la zone est géré via JavaScript. La volonté de rendre toute la zone cliquable est fortement louable, mais l’annulation du comportement natif d’un navigateur engendrée nuit fortement à l’utilisabilité. Je rencontre ce genre de problèmes régulièrement sur d’autres sites comme Le Bon Coin ou LEGO Ideas.

Ce problème d’intégration est vieux comme le monde. Prenons par exemple le code HTML suivant.

<article class="item">
	<h1><a href="/faire-un-lien-sur-toute-une-zone-en-css">Faire un lien sur toute une zone en CSS</a></h1>
	<p>On m'a récemment rappelé sur Twitter une pratique qui m'exaspère au plus haut point ces derniers temps…</p>
</article>

En XHTML ou en HTML4, une balise <a> ne pouvait contenir que des éléments inline. Du coup, l’utilisation de JavaScript (voire de jQuery) était fortement recommandée pour résoudre ce problème. Aujourd’hui, les quelques lignes suivantes suffiraient à rendre toute la zone cliquable pour tous les éléments .item sur notre page.

document.addEventListener('DOMContentLoaded', function() {
	var items = document.querySelectorAll('.item');
	for(var i=0; i < items.length; i++) {
		var item = items[i];
		item.addEventListener('click', function() {
			var url = this.getElementsByTagName('a');
			if(url.length > 0)
				url = url[0];
			window.location = url;
		});
	}
});

Mais cette solution est à l’origine des problèmes d’utilisabilité qui m’exaspèrent tant.

La spécification HTML5 a changé la donne, et on peut désormais englober dans un <a> n’importe quel élément. On pourrait alors simplement englober tout notre .item d’une balise <a>. Mais ce n’est pas forcément une bonne idée, en particulier pour le référencement où il serait préférable de conserver un contenu texte court et pertinent.

Heureusement, une solution est possible en CSS. En utilisant un pseudo-élément ::before ou ::after, on peut le positionner en absolu par rapport au conteneur principal parent et faire en sorte qu’il occupe tout l’espace. Il faudra bien s’assurer que le conteneur parent en question (.item) ait lui aussi un positionnement non statique afin de restreindre le pseudo-élément du lien. Le code suivant fait alors l’affaire.

.item {
	position:relative;
}

.item a:before {
	content:'';
	position:absolute;
	left:0;
	right:0;
	top:0;
	bottom:0;
	background-color:rgba(0,0,0,0);
}

Ça fonctionne bien dans Chrome, Firefox, Safari et Opera. Pour Internet Explorer (9 et plus), il est nécessaire d’ajouter un fond transparent afin que la zone soit cliquable même au-dessus des autres éléments.

L’inconvénient de cette solution est qu’elle ne s’applique pas sur IE8. Même si le pseudo-élément est bien créé, celui-ci restera non cliquable sous les autres éléments de la zone. Mais si vous pouvez vous permettre d’avoir un fonctionnement dégradé gracieusement sur IE8, cette solution me semble assez élégante.