Chez Jérémie

Chez Jérémie, parfois c'est sérieux, parfois non !

Améliorez vos liens avec la génération de contenu CSS

- Web - Lien permanent

Les liens hypertextes sont la colonne vertébrale du Web. La plus part du temps, ils se résument à un texte graphiquement mis en évidence (historiquement, bleu et souligné). Néanmoins, techniquement et sémantiquement parlant, les liens peuvent bénéficier de méta-données qui offrent un certain nombre d'informations qui pourraient être utile à l'utilisateur mais ne sont pas mises en évidence par les navigateurs. Par exemple, l'attribut target permet de définir dans quel fenêtre ou cadre va s'ouvrir le lien, ou l'attribut hreflang qui permet de connaitre la langue de la page où mène le lien et je ne vous parle même pas de l'attribut rel. Dans cet article je vais vous montrer comment utiliser la génération de contenu CSS pour afficher certaines de ces informations à l'utilisateur.

Le résultat attendu

L'idée est d'afficher au survol d'un lien le contenu de son attribut hreflang et target dans un badge en surimpression. Une image valant mieux qu'un long discourt, voila le résultat attendu :

L'arrière plan des liens

Afin de facilité la lecture du badge et surtout son association visuelle avec le lien, on va attribuer aux liens un arrière plan coloré qui va légèrement déborder de la zone de lecture "normal".

On commence par leurs donner une couleur d'arrière plan au survol :

a:hover{
    background : #CCD;
}

L'effet de débord est réalisé en jouant sur les propriétés padding et margin. L'utilisation de padding va permettre d'élargir la zone visuelle. Malheureusement, cela agrandi aussi réellement la zone occupée par le lien qui va repousser ce qui l'entoure. Pour éviter cet effet, on va attribuer une valeur négative de margin équivalente à chacune des valeurs de padding. Cet artifice permet de donner assez facilement l'illusion d'un élément qui "déborde" tout en étant à sa place naturel dans le flux HTML.

a{
    display : inline-block;

    padding :  0.1em  0.6em;
    margin  : -0.1em -0.6em;
}

Notez que la balise a est une balise de type inline. Cela veux dire que certains navigateurs ne vont pas appliquer (ou seulement partiellement) le padding et le margin à l'élément. Pour corriger cela, vous devez ajouter la propriété display: inline-block. Si vos intitulés de liens sont longs, il faut se poser la question de savoir si on veut réellement réaliser un tel effet graphique. En effet, en cas de retour à la ligne avec une balise inline-block, c'est l'intégralité du contenu de la balise qui passe à la ligne (ce qui peut casser la continuité et la lisibilité de votre texte) et si le contenu continue d'être trop grand pour tenir sur une seule ligne, le retour à la ligne se fera à l'intérieur de la balise inline-block. C'est donc à vous de voir ce que vous voulez faire selon le contexte sachant que l'utilisation de la valeur inline-block est obligatoire avec Internet Explorer 8 lorsque vous voulez créer un contenu généré, positionné de manière absolue (ce qu'on va faire ci-après). Si vous n'utilisez pas cette valeur, le badge s'affichera sous le lien !

Avec le truc du débord que je viens de vous montrer, vous allez constater un effet assez déplaisant. Au survol de votre lien le texte qui précède le lien sera masqué par l'arrière plan alors que le texte qui suit restera visible par dessus l'arrière plan. Vous pouvez régler l'empilement des éléments en jouant avec la propriété z-index. Vous pouvez soit lui donner la valeur -1, mais dans ce cas, les liens vont se retrouver sous les paragraphes et ils ne seront jamais survolés par la souris (vous les voyez, mais en fait ils sont "masqués" par l'élément contenant le texte), soit vous lui donnez la valeur 1 et dans ce cas le lien passera entièrement au dessus du texte qui l'entoure. C'est assez intéressant graphiquement, mais pas forcément très satisfaisant en terme de lisibilité je vous montrerai un peu plus loin comment s'en sortir avec CSS 3.

a{
    position : relative; /* nécessaire pour la bonne application de z-index */
    z-index  : 1;
}

La génération de contenu

Le badge qui contiendra les informations supplémentaires va être créer à la volé au survol d'un lien directement en utilisant CSS. Pour cela, nous allons utiliser le pseudo-élément :after en conjonction avec la propriété content.

Le pseudo élément :after va simuler la présence d'un éléments après le contenu du lien. On appel cela un pseudo élément car ce sélecteur va créer une zone qui va avoir le même comportement qu'un élément HTML traditionnel, exactement comme si vous agissiez sur une balise span. La seul différence, c'est que ce pseudo-élément n'apparait pas dans l'arbre DOM du document. Je vous reparlerai un peu plus loin de ce sujet quand on abordera les questions d'accessibilité. Ce qu'il faut retenir ici c'est que vous allez pouvoir ajouter des styles à un élément sans que vous ayez besoin de rajouter quoi que ce soit dans le balisage HTML de vos liens.

La propriété content, elle, va permettre de rajouter du texte et des images au sein de votre pseudo-éléments. Là encore, rien n'apparait dans l'arbre DOM de votre document HTML.

Nous allons gérer ici quatre cas de figure pour notre badge :

  1. Si l'attribut hreflang est présent, on affichera sont contenu dans notre badge : l'objectif est de dire visuellement à l'internaute quelle est la langue du lien qu'il va ouvrir.
  2. Si l'attribut target est présent et à la valeur _blank, on affichera une icône qui montre l'ouverture d'une nouvelle fenêtre dans notre badge.
  3. Si les deux attributs sont présents, on affichera et la langue et l'icône.
  4. Si aucun des deux attributs n'est présents, on n'affichera pas le badge du tout.
La gestion de ces quatre cas va passer par une utilisation judicieuse du sélecteur d'attribut et de la cascade
a:after{
 /* On masque le badge, mais on utilisera le sélecteur générique pour définir le style global du badge */

    display : none;
}

a[hreflang]:hover:after{
 /* On affiche le contenu de l'attribut hreflang */

    content : attr(hreflang);
    display : block;
}

a[target="_blank"]:hover:after{
 /* Si l'attribut hreflang est définie, on affichera son contenu. S'il n'existe pas, il ne s'affichera pas. Dans tous les cas, on affichera une icône qui matérialise le changement de fenêtre */

    content : attr(hreflang) " " url("external.png");
    display : block;
}

Assez simplement, la fonction attr permet d'extraire le contenu de l'attribut et la fonction url permet d'inclure une image. Notez que dans la déclaration ci-avant, dans le cas ou les deux attributs hreflang et target sont tous les deux définis, c'est la dernière règle CSS qui sera appliquée car elle a, selon les règles de la cascade, le même poids que la règle précédente, mais elle la surcharge du fait de son positionnement dans le code source.

Le positionnement et le design du badge

Maintenant que le contenu du badge est générer il faut lui donner un peu de style (oui, je sais, elle un peu facile celle là :-p )

On va commencer par le sortir du flux en le positionnant de manière absolu et on le fera légèrement déborder en haut et à droite. En suite, on va détailler le minimum syndical graphique : Couleur d'arrière plan, bordures, styles typographiques, marges pour aérer un peu tout ça.

a{
 /* nécessaire pour que le positionnement du badge soit relatif à son lien*/

    position : relative;
}

a:after{
    position : absolute;
    top   : -1.3em;
    right : -1.3em;

 /* L'arrière plan est rouge foncé et le texte blanc */
    background : #009;
    color : #FFF;

 /* On ajoute une bordure blanche de 2 pixels de larges */
    border : 2px solid #FFF;

 /* Le texte est graissé et 1/3 plus petit que le texte du lien. Il est également mis systématiquement en majuscule */
    font : bold 0.6em Verdana, Helvetica, Arial, sans-serif;

    text-transform : uppercase;

 /* L'ajout de marge permet de laisser le texte respirer */
    padding : 0.4em 0.5em;
}

CSS 3 is sexy !

Voila ! Avec ce que vous venez de voir, vous avez fait tous ce qu'il est possible de faire simplement avec CSS 2. Cependant, on n'est pas encore au résultat attendu. On va donc rajouter une petite touche de CSS 3 pour rendre ça plus fin, plus youpi-tralala, plus waow !

Couleur transparentes

Actuellement, l'arrière plan de nos liens est opaque au survol, ce qui fait qu'on masque une partie du texte à cause de l'effet de "débord" de l'arrière plan. En utilisant une couleur semi-transparente on peut atténuer ce problème. pour cela, il suffit d'utiliser la notation rgba définie dans le module "Border & Background" de CSS 3.

a:hover{
    background : #CCD;
    background : rgba(128, 128, 160, .4);
}

Notez dans l'exemple ci-avant que la propriété background est déclarée deux fois. C'est normal. En effet, de cette manière si un navigateur ne comprend pas la notation rgba (IE8 par exemple), il ignorera la déclaration inconnu et se rabattra sur la déclaration précédente qui respecte la norme CSS 2. Le mécanisme qui consiste à ignorer une déclaration de style inconnue ou mal formée est un comportement standard. C'est une façon simple de gérer les capacités des navigateurs sans avoir à recourir à des artifices comme Modernizr.

Une autre solution pour régler ce problème de sur-impression consisterai à appliquer un z-index négatif au lien et à utiliser la propriété css pointer-events définie par SVG. Néanmoins, cette solution est plus lourde à mettre en œuvre (il faut appliquer au conteneur de chaque lien la valeur none et réapliquer la valeur auto à tous les liens). A noter que seul Firefox, Chrome et Safari supportent la propriété pointer-events sur les éléments HTML (je n'ai pas eu l'occasion de tester avec IE9, mais IE8 et Opera 10.53 ne la supportent pas).

Bords arrondis

Pour adoucir un peu l'effet on va rajouter des bords arrondis aux liens et aux badges

a:hover{
    -moz-border-radius : 1.3em;
 -webkit-border-radius : 1.3em;
        border-radius : 1.3em;
}

a:after{
    -moz-border-radius : 50%;
 -webkit-border-radius : 1.3em;
        border-radius : 1.3em;
}

Comme souvent avec les propriété issu de CSS3, vous aller devoir faire plusieurs déclarations. En effet, peu de modules ont atteint le statut de Candidate Recommendation au W3C, or, c'est seulement à partir de ce statut que les constructeurs peuvent retirer leur préfixe spécifique aux propriétés qu'ils ont implémenté. le module Border & Background a atteint ce statut et tous les constructeurs sont d'accord pour dire qu'en ce qui concerne la propriété border-radius, il est plus que temps de retirer ce préfixe. Opera 10.53 supporte déjà cette propriété sans préfixe et les futures IE9 et Firefox 4 (en cour de discussion) devraient aussi la supporter sans préfixe.

Notez que pour le badge, avec Firefox, j'ai utilisé la valeur 50%. Cette valeur permet de créer des demi-cercles parfaits aux extrémités gauche et droite. Néanmoins, c'est un comportement spécifique à Firefox et mes tests ont montrés que certaines implémentations de Webkit (Chrome 5 par exemple)  ainsi qu'Opera ne supporte par les pourcentages avec cette propriété. En outre, de ce que je comprend de la spécification, cette déclaration devrait produire une boite ovale plutôt que deux demi cercle réparti de part et d'autre de la boite, donc, pour la propriété standard, j'ai préféré mettre également une valeur absolue.

Ombres et lumières

Notre badge commence à être pas mal, on va le finaliser en lui donnant un peu de relief avec des effets d'ombres et de lumières.

a:after{
    background : #900;
    background : -moz-radial-gradient(35% 35% 0deg, ellipse cover, #E00 0%, #800, 90%);
    background :      radial-gradient(35% 35% 0deg, ellipse cover, #E00 0%, #800, 90%);

 -webkit-box-shadow : rgba(0, 0, 0, .8) 0 2px 5px;

    -moz-box-shadow : rgba(0, 0, 0, .8) 0 2px 5px,
                      rgba(255, 247, 0, .9) 0 0 3px inset;

        box-shadow : rgba(0, 0, 0, .8) 0 2px 5px,
                      rgba(255, 247, 0, .9) 0 0 3px inset;

}

On commence par ajouter un dégradé radial en arrière plan pour donner l'illusion d'un éclairage venant du haut et de la gauche (positionnement du centre du dégradé à 35% du haut de la boite et à 35% du bord gauche de la boite). Ce dégradé aura la forme d'une ellipse horizontal (0 degrés de rotation) qui couvrira toute la boite. Le dégradé va d'un rouge vif au centre à un rouge sombre prés des bords). La syntaxe des dégradés sous Webkit est très différentes de celle de Gecko et honnêtement je n'y comprend rien donc, tant pis pour Safari et Chrome (faite vous plaisir dans les commentaires si vous avez la solution qui va bien). La syntaxe de Gecko étant celle qui est en cour de standardisation, je reproduit la déclaration sans le préfixe -moz-, mais c'est un parie sur l'avenir. En effet, les dégradés CSS font partie du module "Image Values" qui en est à son tout premier draft. Autant dire que ça peut (va ?) sérieusement bouger.

Ensuite on ajoute une ombre porté qui va permettre de "détacher" le badge de la surface de la page. Cette ombre est décalé de 2 pixels vers le bas et sont rayon de flou est de 5 pixels. Dans la mesure ou tout les navigateurs majeurs du marché qui supportent la propriété box-shadow supportent également les couleurs rgba, on ne va pas s'amuser à gérer une alternative sans couleur transparente (mais on pourrait :-p ).

Comme box-shadow supporte les déclarations multiples séparées par un virgule, on va en profiter pour rajouter une ombre intérieur (mot clé inset). C'est une ombre de couleur jaune pour créer un effet de lumière spéculaire (on aurait pu le faire via le dégradé radial, mais personnellement je trouve ça plus simple via box-shadow). Vous noterez que je n'ai pas appliqué cette deuxième ombre à Webkit. Cela tiens au fait que l'implémentation des ombres internes couplé à l'utilisation de border-radius est une catastrophe sous Chrome 5 et Safari 4 (je n'ai pas eu l'occasion de tester avec Safari 5). En effet, l'ombre reste carré la où la boite est arrondie... horrible !

Attention à l'accessibilité

Voilà, on a créer un jolie badge qui illumine la vie de notre internaute. Mais le loup se cache au coin du bois !

Au royaume des aveugles les borgnes sont rois.

Comme je vous le disait ci-avant, le problème majeur de la génération de contenu via CSS, c'est que ce contenu n'est pas inclus dans le DOM du document HTML. Ainsi, toute les outils d'aide à l'accessibilité qui repose sur le DOM ne pourront pas exploiter ce contenu généré. C'est par exemple le cas des revus d'écran qui vocalisent les pages Web. A ma connaissance, aucune ne prend en charge les contenus généré via CSS. Pour cette raison, il est vivement déconseillé de générer un contenu signifiant à l'aide de CSS. Dans le cas qui nous occupe ici, cette question n'est pas un problème. En effet, il s'agit uniquement d'enrichir l'expérience utilisateur, donc, tant pis pour les aveugles et les personnes utilisant de vieux navigateur (vous voyez de quoi je parle là, n'est-ce pas), ils pourront toujours utiliser les liens tel qu'ils ont l'habitude de les pratiquer, pour les autres, c'est cadeau, c'est bonheur :)

Les souris, ce fléau de l'humanité !

Vous aurez noté que l'apparition et la disparition du badge sont conditionnées par l'application de la pseudo-classe :hover. Cette pseudo-classe correspond au survol de la souris sur le lien. Pas de bol, certains excentriques utilisent leur clavier, d'autres utilisent des interfaces tactiles (et même si Safari sous iPhone sait s'accommoder de cette pseudo-classe, ce n'est pas le cas de tous le monde) ... pire, votre souris pourrait tomber en panne ! Vous pourriez avoir la même attitude que précédemment et décréter que "tant pis pour ces êtres étranges qui sont mal équipé" et en rester là. C'est toujours possible mais ce serait dommage car il est assez facile de leur offrir la même expérience en étendant légèrement nos sélecteurs CSS en incluant les pseudo-classes :active et :focus.

a[hreflang]:hover:after,
a[hreflang]:active:after,
a[hreflang]:focus:after{
    ...
}

a[target="_blank"]:hover:after,
a[target="_blank"]:active:after,
a[target="_blank"]:focus:after{
    ...
}

Mon royaume pour un cheval.

Certains navigateurs sont comparables à des piétons et d'autres à des calèches grand luxe tractées par au moins 4 chevaux (ce qui sous entend également que même les navigateurs actuels les plus en pointe ont encore un long chemin à parcourir pour pouvoir être comparé à des coupés sport à injection directe !). Vous pouvez bien sur tester ce badge avec votre propre navigateur, et si vous n'avez pas tout un panel de navigateurs à votre disposition, voila quelques exemples de rendus :

Tableau comparatif de quelques rendus navigateurs
Navigateur OS Résultat du rendu
IE 6 Windows XP Pas de badge ni de bords arrondis sur le lien
IE 7 Windows XP Pas de badge ni de bords arrondis sur le lien
IE 8 Windows XP Un bug sur le badge qui passe derrière le texte du lien
IE 9 (Test Drive) Windows 7 Le rendu des pseudo éléments souffre du même bug que IE8
Firefox 3.5 Mac OS Leopard Pas de dégradé sur le badge
Firefox 3.6 Windows XP Le rendu optimal de référence
Firefox 3.7a5 Ubuntu 10.04
Le rendu reste optimal
Chrome 5 Windows XP Pas de dégradé ni de lueur spéculaire interne
Safari 4 Windows XP Pas de dégradé ni de lueur spéculaire interne
Safari 5 Mac OSX Leopard Pas de dégradé ni de lueur spéculaire interne
Opera 10.53 Windows XP Pas de dégradé
Opera 10.60 beta Ubuntu 10.04 Pas de dégradé

Vous noterez que, au delà des dégradations prévisibles (Dégradés, ombre, coins arrondis), c'est le rendu typographique qui diffère le plus d'un OS à l'autre (d'où l'importance d'utiliser des em pour définir la taille des arrondis plutôt que des pixels). Vous noterez également un bug (?) avec IE8 (non, ne levez pas les yeux au ciel !), où le badge est positionné au dessus de l'arrière plan du lien, mais au dessous du texte (!!). Il semblerai que pour l'instant, l'équipe de IE9 ne se soit pas encore penchée sur la gestion des pseudo éléments qui se comportent exactement comme avec IE8.

Conclusion

Comme vous pouvez le voir, il n'est pas très difficile d'enrichir l'expérience des utilisateurs. On est dans un cas typique d'enrichissement progressif. Avec un navigateur ancien, l'utilisateur ne gagne rien, mais il ne perd rien non plus. Avec un navigateur moderne, il gagne en confort. Personne n'est lésé, tout le monde est gagnant.

Certes, cela est intéressant pour l'utilisateur, mais cela peut également être intéressant pour vous à titre personnel. En effet, rien ne vous empêche d'utiliser cette technique dans la feuille de style utilisateur de votre navigateur préféré pour enrichir votre propre expérience de navigation sur tous les sites sur lesquels vous surfez ;)

Enfin, c'est grâce à ce genre de test qu'on peut remarquer que Webkit n'est pas aussi bon que l'affirme Apple et Google en terme de support de CSS3 (quand il ne font pas un honteux amalgame avec HTML5). Je ne parle pas des dégradés radiaux (là, c'est moi qui fait volontairement l'impasse) mais plutôt du support de box-shadow couplé à border-radius qui est loin de ce que propose les dernières versions de Gecko. Bon soyons claire, il ne sert à rien de se lancer dans un pinaillage du genre "Ah oui mais Webkit supporte mieux tel truc que Gecko". Ce que je veux vous dire, c'est qu'il ne faut pas prendre pour argent comptant ce que racontent les constructeurs de navigateurs. Testez, testez et testez encore ! Il n'y a que ça pour vous permettre de savoir ce qui marche dans les cas qui vous intéressent.