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.

  1. Benoit, le

    Ou pourquoi pas un <a> en display: block+floats? (ou inline-block)
    Concernant le code JS, ça risque d’être lent si il y a beaucoup d’éléments. Perso, je définis toujours ma fonction avant (dans une ‘variable’) ce qui évite d’avoir plusieurs fois ce code en mémoire.
    Encore mieux, utiliser une délégation d’événements.
    Je suis peut être hors sujet, mais ces optimisations, sur certaines (grosses) pages, sont aussi importantes que l’ergonomie générale, et negligée par presque tous les devs avec lesquels j’ai travaillé.
    Pour le coté utilisabilité, rien n’empêche, en quelques lignes de plus, de gérer le bouton souris source de l’event, ou de vérifier si Ctrl est pressé pour faire un window.open.
    Trêve de pinaillerie, article intéressant (comme toujours).

  2. Jack NUMBER, le

    Hello,
    En effet les liens javascript sont vraiment pénibles, pour moi ça relève de la disponibilité (cf. Pierre-Damien Huyghe). On empêche l’utilisateur de naviguer comme il le souhaite, surtout que le clic molette ou le clic droit > « ouvrir dans un nouvel onglet » sont des comportements que l’on retrouve dans tous les navigateurs.

    Pour IE8 la solution est de mettre un lien sans contenu avec pour propriétés :
    display: block;
    position: absolute;
    width: 100%;
    height: 100%;

    Et un position: relative sur le parent.
    Mais je viens de penser que ça peut empêcher la sélection du texte.

  3. Gring, le

    Au début, ce genre de technique se rencontrait pour les images : l’image est dans une balise dont le href pointe vers son url, mais au clic, il y a une interception Javascript pour afficher l’image dans une galerie en overlay.

    De cette manière, au clic gauche, l’image s’affiche dans un overlay, mais au clic central ou droit, l’image s’ouvre seule dans un nouvel onglet.

    Je me suis toujours demandé si c’était bien vu par Google.

    Aujourd’hui, on a la possibilité de mettre les pages à jour via « Ajax », et de changer l’url de la page en conséquence (lors d’un accès direct, le serveur devant générer pour cette url une page dans le même état qu’après des mises à jour en « Ajax »).
    Du coup, quand on fait de la mise à jour de page « dynamique », j’imagine qu’il faut quand même mettre des liens avec le bon URL dans le paramètre href, mais intercepter le clic gauche pour provoquer une mise à jour « Ajax » de la page (et pas un rechargement complet, sauf si on est sur un vieux navigateur).

    Donc toujours la même question : Google flingue t’il le référencement de la page si on fait ça ?

  4. tetue, le

    On en parlait récemment par là : http://seenthis.net/messages/300809 :)

    Voir aussi la restitution vocale, avec ces exemples d’implémentation de Romain Gervois, qui confirme l’approche CSS, plutôt que JS ou HTML : http://www.romaingervois.fr/implementations/lien-block.html

  5. RastaPopoulos, le

    On en a parlé là aussi :
    http://seenthis.net/messages/300809

    J’ai émis des doutes sur la stylabilité du truc, notamment pour bien faire voir le focus sur l’ensemble du bloc. Et effectivement on ne peut pas avec cette technique CSS, seulement en faisant un HTML5 qui entoure tout (mais qui est moins bien pour Google et moins bien pour les lecteurs d’écrans).

    Donc pour l’instant j’ai moi aussi appliqué cette méthode du before, tant pis pour mes jolis focus. (J’ai fait un style en SCSS facilement réutilisable un peu où on veut sans changer le HTML.)