Un joli casse-tête en intégration

Cet après-midi, j’ai rencontré un joli casse-tête en intégration. Après 2 heures de remue-méninges, j’ai fini par trouver une solution qui me convenait. J’ai posté le problème sur Twitter, et à ma grande surprise j’ai eu beaucoup de réponses, dont certaines assez astucieuses, mais ne collant pas tout à fait à ce que je souhaitais faire. Alors avant de donner la solution que j’ai trouvé, j’ai décidé de faire durer le suspens un peu plus longtemps et de poster le problème ici.

Voici le casse-tête en question :

Un casse-tête en CSS

C’est un effet graphique assez classique, en particulier dans la presse papier. Pourtant, il se trouve que ça reste relativement complexe à reproduire de manière simple en CSS.

Voici les règles à respecter :

  1. Le texte ne doit contenir aucune balises (donc pas de span pour chaque ligne, pas de br)
  2. Vous pouvez utiliser autant de balises que vous voulez autour du texte
  3. Pas de JavaScript, que du HTML et CSS
  4. Le fond blanc doit suivre le contour du texte
  5. Il y a une marge à l’intérieur du fond blanc autour du texte de 20px à gauche et à droite de chaque ligne, et d’environ 10px en haut et en bas
  6. Le texte est dynamique, et on doit donc pouvoir le modifier en conservant l’effet
  7. La solution doit fonctionner sur les navigateurs modernes (IE9, Firefox 13, Chrome 19, …)

Pour vous aider à démarrer, j’ai mis à votre disposition sur jsFiddle un exemple de code HTML et CSS. Vous êtes libres de modifier le code HTML et CSS, tant que vous respectez les règles ci-dessus.

Je vous invite à partager vos réalisations dans les commentaires. Mercredi soir, je publierais la solution que j’ai trouvé, et je sélectionnerais mes solutions préférées parmi les commentaires. Amusez-vous bien, et bonne chance à tous !

  1. Loïc, le

    La solution qui m’est venu tout de suite en tête tout à l’heure http://jsfiddle.net/ruinyourfun/3vTXU/1/ Pas des plus propres mais en utilisant une bordure et en jouant avec le line-height, ça se fait facilement (si j’ai tout bien suivi du moins). J’ai rien de mieux sur le coup ;)

  2. Victor, le

    Et bien moi j’aurais fait comme ça : http://jsfiddle.net/Victa/MGCKq/

    Curieux de voir si il y a une solution plus légère…

  3. Victor, le

    Ah bah on a eu a peu prêt le même genre d’idée avec Loïc, mais c’est plus « compliant » car ma solution déconne sous FF en fait….
    Dommage, ça me plaisait plus d’ajouter l’espacement avec un pseudo élément.

  4. nicolas, le

    Et avec outline? http://jsfiddle.net/Akfb4/28/
    Là ça ne marche pas du tout (particulièrement le z-index, et les marges sont pas bien faites), mais c’est une piste explorable.

    Sinon en print, ça se fait (entre autres) avec un souligné, simplement épaissi et bien calé en dessous. J’imagine que quand on aura tous les text-underline-* (et text-line-through-*), ça se fera tout simplement (c’est déjà implémenté dans certains navigateurs déjà ? Je ne sais plus).

  5. Nicolas Chambrier, le

    J’arrive pas au bout (bon en même temps je suis pas intégrateur :P) mais je sens bien qu’il y a un truc à creuser avec le propriété outline

  6. Pierre, le

    Ma solution (OK sur IE8+, Opera, Firefox, Chrome) : http://jsfiddle.net/Ch7bL/29/

    position:relative et left négatif pour simuler un padding-right, et padding-left doublé sur le conteneur.

    Seul problème, Firefox ne permet pas d’utiliser height:100% à l’intérieur d’un élément en display:inline, ni bottom:0 (il le positionne sur le bottom de la première ligne, contrairement à tous les autres navigateurs).

    On ajoute donc un deuxième masque pour Firefox, mais ça crée un petit décalage d’1px sur Opera, qui peut facilement être corrigé, mais ça ira comme ça :-)

  7. François "cahnory" Germain, le

    Ma solution à base de box-shadow : http://dabblet.com/gist/2991253
    Imparfaite tout de même, si le line-height est diminué, il y a possibilité de chevauchement.

  8. Patrick, le

    Ma solution light. http://jsfiddle.net/zSUBt/2/

  9. Fabien Ménager, le

    Ma solution à base de box-shadow : http://jsfiddle.net/dZep2/1/ marche sous Chrome, Firefox et IE9

  10. mat, le

    Début de soluce à base de outline: http://virgule.net/tmp/test.html

    Ça marche dans Chrome, pas dans FF, à cause du outline sur le h1… lequel est nécessaire à cause de Chrome… Pas testé le reste, et de toutes façons c’est imparfait car ça oblige à utiliser les mêmes marges sur le coté et en haut/bas.

    J’aurais bien testé un truc à base de box-shadow, mais j’ai eu l’idée trop tard, et je suis pas intégrateur non plus, donc j’arrête la :-)

  11. Grégocentrique, le

    Alors en deux spans Pépita (display : inline oblige).

    http://jsfiddle.net/Gr4HB/

    Avec du border, du padding, et du position:relative.

  12. ZeMoko, le

    Yyyyeeess ! Je crois que je l’ai :
    Le résultat : http://jsfiddle.net/zemoko/njvRf/4/embedded/result/
    Ma solution : http://jsfiddle.net/zemoko/njvRf/4/

  13. Rodleg, le

    Voila pour ma solution qui est loin d’être parfaite: http://jsfiddle.net/rodlegdesign/AF9Tm/
    J’attends avec impatience la correction

    Ps: François « cahnory » Germain félicitation pour ta solution bien trouvé .

  14. asha, le

    J’ai un truc du genre : http://jsfiddle.net/8Bbkb/9/ (non testé sous IE). Un peu la lutte, le line-height, mais pas trop le temps de creuser.

  15. Rodleg, le

    désolé: http://jsfiddle.net/AF9Tm/1/ comme ça c’est un peu mieux

  16. asha, le

    Meuh, Loïc m’a devancé en fait :P

  17. Raph, le

    Sympa ce défit ! Ça parait facile mais non :)

    Bon, sans prétention, je joue le jeu.
    Indulgence, je débute ;)

    http://dabblet.com/gist/2992045

    PS : François « cahnory » Germain, jolie solution !

  18. Identitools, le

    5 minutes de réflexion à peine, petite flemme de continuer j’ai une partie de Civilization V à finir ^^’ http://jsfiddle.net/Akfb4/327/ (oui c’est très con, oui j’ai inventé un nouvel effet typographique à moi tout seul)

  19. Mathieu, le

    mes deux cents de contribution matudinal : http://silicon-velay.me/cassetete/

  20. oilvier, le

    Voici ma proposition, basé sur les différents exemples utilisant outline :
    http://jsfiddle.net/oilvier/qktGR/

    OK sur FF13, Chrome19, Opéra et IE>8 (PC)
    OK sur FF13, Chrome19 et Safari (Mac)
    Léger bug sur Safari PC au niveau des coins (corrigé sur Chrome mais pas sur Safari :/)

  21. Arnaud, le

    Salut,
    je vois que ma solution n’est pas unique :) je la mets quand même
    http://jsfiddle.net/Akfb4/351/

  22. François "cahnory" Germain, le

    Bon, j’ai revu un peu ma copie et elle est maintenant plus simple et sans limitation (il faut biensur le support de box-shadow mais il n’y a plus de risque de chevauchement, merci position:relative) : http://dabblet.com/gist/2994237

    Testé sous les dernières versions de chrome, safari et Firefox ainsi qu’IE 9 à l’aide du site « IE NetRenderer ».

  23. Vincent, le

    La solution la plus propre à base de box-shadow et la valeur d’étendue de l’ombre. Bien sur, si le line-height est réduit, il y a chevauchement.

    http://jsfiddle.net/iamvdo/pDHMk/

  24. Germain, le

    @Patrick , plutôt astucieux de faire un double box-shadow! Je ne savais pas que c’était possible.

    En tout cas je trouve que c’est la solution la plus simple à mettre en place ici.

  25. neolao, le

    Rah, je n’arrive pas à le faire sans rajouter de balise. http://jsfiddle.net/Akfb4/436/

  26. Rhaze, le

    @François Bien joué j’étais de prendre la tête pour résoudre ce problème.
    Bon du coup je post pas j’étais sur cette piste.

  27. Rhaze, le

    Bon il manque des mots dans mon commentaire mais vous m’avez compris.
    Sympa en tout cas comme défi faudrait faire ça plus souvent.

  28. Raph, le

    Copie revue et corrigée après ma courte nuit de sommeil :)

    Bon, je ne suis toujours pas intégrateur (auto-formation le soir en quête d’un contrat pro).

    Voici ma solution en 2 balises : h1 et span.
    Marges (haute, basse, gauche, droite) gérées (confirmez !)

    Des petits commentaires explicatifs sont dispo sur la page ;)

    http://jsfiddle.net/GfBPx/7/

    Je repars m’occuper de mes serveurs … Amusez vous bien :D

  29. Tino, le

    Comme je crois que l’idée n’a pas encore été postée, voici une piste de solution simple en combinant mark et outline.
    http://jsfiddle.net/Maboutik/EfcNf/2/

  30. ZeMoko, le

    Zut, me suis trompé dans mes liens jsFiddle :(
    Je les ai fait sur la version 4 alors que ce n’est pas la dernière :
    – Le résultat : http://jsfiddle.net/zemoko/njvRf/embedded/result/
    – Ma solution : http://jsfiddle.net/zemoko/njvRf/

  31. Geoffrey, le

    Hello,
    À condition de ne pas en avoir grand chose à faire du zoom : http://jsfiddle.net/Geoffrey/Akfb4/591/
    Mais c’est très figé…
    Je n’ai pas utilisé de span supplémentaire, je suis simplement parti de la base proposée.
    Hâte de voir ta solution (certaines proposées ici sont très flexibles)

  32. Eliéban Golay, le

    Bon bah moi ma solution est celle là => http://jsfiddle.net/Akfb4/613/
    Fait un peu à l’arrache, mais fonctionne correctement normalement ;)

  33. Thomas, le

    Super idée ce concours :)
    La solution est pas propre et j’imagine que je suis pas le premier !
    Mais je l’aurais fait comme ça http://jsfiddle.net/tzilliox/Akfb4/668/

    Bonne journée,
    Thomas.

  34. suskut, le

    http://doctype.com/add-padding-subsequent-lines-inline-text-element-3

    2ème réponse.

    – On ajoute un en relative, on décale à -20 px à gauche pour la marge droite
    – On ajoute un border-left de 20px sur le encadrant le texte en inline-block pour créer la marge gauche

  35. earth01, le

    Une solution simple qui marche parfaitement avec IE et Opera. WebCore et Gecko ne respectent pas les standards sur ce point, désolé pour les fans x)

    http://files.myopera.com/earth01/files/dynamic.html

  36. charles, le

    En silverlight c’est facile.

  37. Greg, le

    Hop, mas soluce, qui ressemble fort aux précédente finalement ^^
    Double span (pour garder la bonne largeur), double box-shadow pour avoir une valeur différente en hauteur et largeur.

    http://jsfiddle.net/NTWj3/

  38. lionelB, le

    une solution à base de
    – border top et bottom pour les gérer les espaces en haut et en bas,
    – box-shadow pour gérer les espaces à droite et à gauche
    – un 2ieme span imbriqué avec position: relative pour annuler l’effet de recouvrement sur les lettres y,g,q,p… dans certain cas de line-height

    Sans le deuxième span, cela nécessite plus de réglages au niveau des border pour éviter le problème de recouvrement.

    http://jsfiddle.net/Akfb4/918/

  39. cssyren, le

    Ma solution http://jsfiddle.net/Akfb4/919/

  40. Rémi, le

    Chose promise, chose dûe. Voici la solution que j’avais trouvé : http://dabblet.com/gist/3006471.

    La meilleure solution (à mon avis) consistait à utiliser la propriété box-shadow. Sauf qu’il s’avère que j’avais totalement oublié la quatrième valeur numérique possible, qui permets d’agrandir la taille de l’ombre. Dans ma solution, j’avais donc bêtement dupliqué quatre ombres, décalées de chaque côté pour créer l’illusion du contour.

    Mais la solution proposée par Cahnory (utilisant 2 box-shadow agrandies) est clairement bien meilleure. Je vais donc me permettre de lui repiquer !

    Je ferais un petit article compte-rendu d’ici la fin de la semaine. En tout cas, merci à tous d’avoir participé !

  41. François "cahnory" Germain, le

    Bah je décapsule une bière en mon honneur, santé ! ;)

  42. Vincent, le

    Hello,
    Mais c’est possible avec une seule ombre, non?

    box-shadow: 0 0 0 10px #FFF;

  43. Rémi, le

    Dans mon exemple, je voulais 10px en haut et en bas, et 20px à gauche et à droite. C’était donc une petite subtilité supplémentaire qui demande une deuxième ombre.

  44. Raph, le

    Dans la sOlution, on peut se passer d’un span ;) cf mon exemple.

  45. Vincent, le

    OK, j’avais zappé ça…
    Au temps pour moi.

  46. François "cahnory" Germain, le

    @raph je pense que la solution à deux span est plus viable et universelle.
    Si comme dans ton exemple on se passe d’un span en mettant un display inline au h1 ça fonctionne, sauf que dans la vraie vie il est rare que l’on puisse passer un h1 en inline (perte des marges, du retour à la ligne…).
    Il y a l’autre solution consistant à supprimer le span « inner » mais du coup, sans le positionnement relatif, le line-height ne peut être définie comme bon nous semble sans risquer un chevauchement.
    Dans les deux cas, on ajoute des contraintes à la définitions de certaines propriétés qui sont loin d’être triviales.
    Enfin, le double span passe partout, on pourrait très bien l’appliquer à une portion de text d’un h1, p… et non à l’ensemble… mais il faut garder à l’esprit que les ombres risquent de chevaucher ce qu’il y a autour.

  47. Identitools, le

    Pour le span il peut être facilement remplacé par une balise « a », de plus ce genre de titrage pointe généralement vers un article et vu que a est une balise inline…

    Reste plus qu’à appliquer la couleur du texte sur le lien et un petit text-decoration:none; et c’est dans la poche, plus de span horrible.

  48. François "cahnory" Germain, le

    @Identitools le span n’a rien d’horrible, c’est un contener inline generic et dans le cas présent c’est à mon sens ce qui convient le mieux.
    Bien sûr, si l’on souhaite appliquer ce style à un élément qui est déjà du type inline comme « a » il serait idiot d’y ajouter un span (enfin, dans la solution à deux span il faudrait tout de même en ajouter un mais on éviterait le premier). Sauf que là il n’était pas question d’habiller un lien mais un titre et passer par une balise a serait ajouter une particularité qui n’est pourtant pas présente au départ.
    En clair, mon point de vue est que dans ce genre d’exercice le but est d’être le plus générique possible et de dénaturer au minimum le matériaux de base. La balise span n’apportant rien d’un point de vue sémantique je la préfère et je fait un pari sur l’intelligence de l’utilisateur final qui doit savoir adapter un cas générique au cas particulier.
    Puis bon, les liens dans les titres c’est courant mais c’est pas non plus systématique ;)

  49. Raph, le

    Je n’avais pas vu la chose sous cet angle (rendre l’effet générique). Merci de la précision ;)