La technique des « Fab Four » pour créer des e‑mails responsive sans media queries

Je pense avoir trouvé une nouvelle façon de créer des e‑mails responsive, sans media queries. La solution implique la fonction CSS calc(), et les trois propriétés width, min-width ou max-width.

Ou comme je me plais à les appeler une fois toutes réunies : les « Fab Four » (en CSS).

calc() & width & min-width & max-width.

Le problème

Intégrer des e‑mails responsive, c’est difficile. En particulier à cause de clients e‑mail sur mobile (comme Gmail, Yahoo ou Outlook.com) qui ne supportent pas les media queries. Une approche hybride, une stratégie « Gmail first », ou un e‑mail responsive sans media queries sont d’excellentes façons de s’adapter à cette situation.

Cette dernière approche est ma préférée jusqu’à présent. L’idée est d’avoir des colonnes dans des <div> avec une largeur fixe alignées avec la propriété display:inline-block. Dès qu’un écran ne peut plus contenir deux blocs côte à côte, ils vont naturellement passer les uns sous les autres. Mais j’ai toujours eu un problème avec ça.

Une fois les blocs empilés, ils ne prennent pas la largeur complète de l’e‑mail.

Illustration par Nicole Merlin. Sans media queries, les colonnes peuvent s'empiler mais pas s'adapter à toute la largeur.

Illustration par Nicole Merlin.

Ça fait des plombes que je suis à la recherche d’une solution à ce problème. Flexbox était un parfait prétendant, mais malheureusement le support de Flexbox dans un e‑mail est exécrable.

Une solution

Un rappel sur width, min-width et max-width

En plus de la fonction calc(), la solution que j’ai trouvé implique ces trois propriétés CSS. Afin de bien comprendre comment tout fonctionne, voici un rappel de comment les propriétés width, min-width et max-width se comportent quand elles sont utilisées toutes ensembles (d’après ce récapitulatif très clair de mon confrère Raphaël Goetter).

  • Si width est plus grand que max-width, c’est max-width qui gagne.
  • Si min-width est plus grand que width ou max-width, c’est min-width qui gagne.

Saurez-vous deviner la largeur de l’élément ayant les styles suivants ?

.box {
    width:320px;
    min-width:480px;
    max-width:160px;
}

(Réponse : l’élément fait 480px.)

calc() et la formule magique

Sans plus attendre, voici un exemple des « Fab Four » pour créer deux colonnes qui s’empileront et grandiront sous 480px.

.block {
    display:inline-block;
    min-width:50%;
    max-width:100%;
    width:calc((480px — 100%) * 480);
}

Voyons ça plus en détail pour chaque propriété width.

min-width:50%;

La propriété min-width définit la largeur de nos colonnes sur ce qu’on pourrait appeler la version desktop. On peut changer cette valeur pour ajouter plus de colonnes (par exemple à 25% pour avoir une grille de quatre colonnes), ou pour définir des colonnes à taille fixe en pixels.

max-width:100%;

La propriété max-width définit la largeur de nos colonnes sur ce qu’on pourrait appeler la version mobile. À 100%, chaque colonne va grandir et s’adapter à la largeur de leur conteneur parent. On peut changer cette valeur si l’on souhaite garder des colonnes sur mobile (par exemple à 50% pour une grille à deux colonnes).

width:calc((480px — 100%) * 480);

Grâce à la fonction calc(), la propriété width est là où la magie se produit vraiment. La valeur 480 correspond à notre point de rupture souhaité. Les 100% correspondent à la largeur du conteneur parent de nos colonnes. Le but de calcul est de créer une valeur plus grande que notre max-width ou plus petit que notre min-width, afin que l’une ou l’autre propriété soit appliquée à la place.

Voici deux exemples.

/* Avec un parent de 500px de large */
.block {
    display:inline-block;
    min-width:50%; /* 250px */ /* Winner ! */
    max-width:100%; /* 500px */
    width:calc((480px — 100%) * 480); /* -9600px */
}

Avec un parent de 500px de large, la largeur calculée vaut -9600px. Elle est plus petite que min-width. Donc c’est le min-width de 50% qui gagne. On a ainsi une grille sur deux colonnes.

/* Avec un parent de 400px de large */
.block {
    display:inline-block;
    min-width:50%; /* 200px */ 
    max-width:100%; /* 400px */ /* Winner ! */
    width:calc((480px — 100%) * 480); /* 38400px */
}

Avec un parent de 400px de large, la largeur calculée vaut 38400px. Elle est plus grande que min-width, mais max-width est plus petit. Donc c’est le max-width de 100% qui gagne. Ainsi on a une mise en page sur une seule colonne.

Démo

Voici une démonstration de ce que cette technique permet de faire.
Vous pouvez voir la démo complète en ligne ici.

Démo de la technique des Fab Four en CSS

Illustrations par Elias Stein

Et voici deux captures d’écran de cette démo dans Gmail, sur le webmail desktop et sur l’application mobile sur iOS. Même code, rendu différent.

Exemple des Fab Four sur Gmail

Dans cette démo, j’ai défini plusieurs exemples de grilles (avec deux, trois ou quatre colonnes). La première grille, avec les images, est conçue pour passer de quatre colonnes sur desktop à deux colonnes sur mobile. Les autres grilles sont prévues pour s’agrandir totalement sur mobile.

Remarquez aussi comme le titre change d’un alignement à gauche sur la version desktop à un alignement centré sur mobile. C’est fait en donnant au titre une largeur fixe de 190px et un margin:0 auto; pour le centrer. Sur desktop, le conteneur parent du titre a un min-width de 190px appliqué, donc le logo reste à gauche. Sur mobile, le conteneur parent grandit sur toute la largeur, donc le logo devient centré.

Un chouette aspect de cette technique est que, puisque tout est basé sur la largeur du conteneur parent des colonnes, un e‑mail peut s’adapter même sur un webmail desktop. Par exemple, sur Outlook.com, peu importe que vous ayez choisi un panneau de lecture en bas ou à droite, l’e‑mail s’affichera correctement selon la largeur du panneau de lecture. Ce serait impossible à faire avec des media queries.

Sur Outlook.com, l'e‑mail s'adapte à la vue choisie.

Support

Dans les navigateurs, calc() est plutôt bien supporté depuis IE9. Il se trouve que calc() a aussi un plutôt bon support dans les clients mail. Ça fonctionne dans Apple Mail (sur iOS et OS X), Thunderbird, Outlook (l’application iOS et Android), Gmail (webmails et applications iOS et Android), les webmails d’AOL, Orange, SFR et La Poste, et l’ancienne version d’Outlook.com (encore active par défaut en Europe).

L’ancienne version d’Outlook.com

Outlook.com pose un léger problème, cependant. Le webmail filtre toute propriété avec un calc() qui contient des parenthèses à l’intérieur. Cela signifie que calc(480px - 100%) est supporté, mais pas calc((480px - 100%) * 480). Vu que ma formule initiale comporte des parenthèses, il faut qu’on la refactorise pour éviter les parenthèses. La formule modifiée pour supporter l’ancienne version d’Outlook.com ressemble alors à la suivante.

width:calc(480px * 480 — 100% * 480);

Clients non supportés

Bien entendu, calc() n’est pas supporté sur de vieux clients mail comme Lotus Notes, ou même la dernière version d’Outlook 2016 pour Windows (qui utilise le moteur de rendu de Word). Ça ne fonctionnera pas non plus sur Outlook Web App (la nouvelle version d’Outlook.com et Office 365) et Yahoo (webmail et applications iOS et Android).

Solutions de repli

Dans ces cas, je suggère de dupliquer les propriétés impliquées avec des valeurs fixes pour les clients ne supportant pas calc(). Afin de cacher les « Fab Four » de ces clients, je conseille d’utiliser des fonctions calc(), même lorsque ce n’est pas techniquement nécessaire. Notre premier exemple devient alors…

.block {
    display:inline-block;
    min-width:240px;
    width:50%;
    max-width:100%;
    min-width:calc(50%);
    width:calc(480px * 480 — 100% * 480);
}

Outlook Web App

Cependant, Outlook Web App (la nouvelle version d’Outlook.com et Office 365) a une particularité embêtante supplémentaire. Quand une fonction calc() contient une multiplication (avec le caractère *), Outlook Web App va supprimer l’intégralité de l’attribut style correspondant. Cela signifie qu’il faut qu’on fasse les calculs des multiplications à la main pour ne laisser qu’une soustraction dans la formule. Voici à quoi ressemble le calcul final pour un point de rupture à 480px.

width:calc(230400px — 48000%);

Les préfixes WebKit

Les anciennes versions d’Android (avant Android 5.0) et d’iOS (avant iOS 7) nécessitent des préfixes -webkit- pour que ça fonctionne. La version finale de notre code ressemble alors à ça.

.block {
    display:inline-block;
    min-width:240px;
    width:50%;
    max-width:100%;
    min-width:-webkit-calc(50%);
    min-width:calc(50%);
    width:-webkit-calc(230400px — 48000%);
    width:calc(230400px — 48000%);
}

Limitations et conclusion

Comme a peu près tout dans le monde de l’intégration d’e‑mails, la technique des « Fab Four » n’est pas parfaite. Voici quelques limites qui me viennent à l’esprit :

  • Ça ne fonctionne pas sur Yahoo. La version desktop du webmail supporte les media queries, par contre. Donc on pourrait améliorer tout ça un peu en faisant une version « mobile first » de notre e‑mail, et en l’améliorant sur desktop avec des media queries.
  • On ne peut définir qu’un seul point de rupture. Ce n’est peut-être pas trop un problème pour des e‑mails, ceci dit, vu que les designs vont rarement au delà de 600px sur desktop et ne nécessitent donc pas plus d’un point de rupture pour une adaptation sur mobile.
  • On peut seulement diminuer le nombre de colonnes de la version desktop à la version mobile. Même si ça arrive rarement, on ne pourrait pas passer d’une grille à quatre colonnes sur mobile à une seule colonne sur desktop.
  • La version finale du calcul (pour supporter l’ancien Outlook.com et se dégrader gracieusement sur le nouveau) est difficile à lire. Utiliser un pré-processeur et un mixin pour générer toutes les propriétés requises pourrait être plus qu’utile.

Je suis convaincu que cette technique reste utile dans de nombreux cas, en particulier pour des optimisations sur Gmail. Je suis certain que ça peut également être utile pour des sites web (comme pour des widgets, des publicités, …).

Et j’ai hâte de voir ce que ça peut vous inspirer de créer.

  1. MisterJack, le

    Il y à une erreur sur le second exemple avant la démo le /*Winner !*/ n’est pas au bon endroit ^^ En tout cas super article très intéressant !

  2. Rémi, le

    @MisterJack : C’est corrigé, merci !

  3. itupix, le

    Super technique qui pourrait même être utile dans autre chose que des emails. Pour faire des « media queries » en fonction de la largeur du parent d’un element dans une page par exemple.

  4. Nels, le

    Alors là … tu m’épates !
    Chapeau l’artiste.

    Je vais tester ça de ce pas ;)

  5. Nicolas, le

    Merci beaucoup pour la recherche de cette solution et le partage :)

  6. fvsch, le

    Technique très intéressante. Une question sur le coefficient multiplicateur dans le calcul du width: dans l’exemple donné le coefficient est toujours 480, mais il me semble qu’on peut prendre une autre valeur, par exemple 1000, et le résultat sera le même. Si on utilise 1000 comme coefficient, ça simplifie pas mal les calculs.

  7. Rémi, le

    @fvsch : C’est une bonne idée. J’ai voulu souligner le fait que le coefficient multiplicateur devait être au minimum égal au point de rupture, et essayer d’éviter d’avoir un chiffre magique sorti de nulle part.

  8. Defossez, le

    Houuuuu, alors ça c’est très tordu, mais bougrement intelligent! :D

  9. Benoit, le

    Bonjour,

    Dans l’exemple vous mettez une masjucule au Margin est-ce normal ?

    Merci

  10. Rémi, le

    @Benoit : Oui, tout à fait, c’est pour l’ancienne version d’Outlook.com.

  11. Romain, le

    Après un test litmus, ça ne marche pas sur GMail App, ainsi que Outlook lte 2003 et les plateformes IBM :(

  12. Rémi, le

    @Romain : Le support CSS de Gmail peut varier selon le type de compte utilisé. Litmus utilise des comptes tiers. L’application Gmail Android ne supporte pas les media queries et la fonction calc() dans cas là. J’ai écris à ce sujet ici.

    En ce qui concerne Outlook 2003 et les Lotus Notes (qui utilisent le moteur de rendu d’Internet Explorer), on peut prévoir d’ajouter des commentaires conditionnels pour cibler IE. Dans ma démo, tu peux essayer de remplacer les <!--[if mso]> par <!--[if mso | IE]>.

Les commentaires sont modérés manuellement et soumis à un filtre anti-spam. Merci de respecter l'auteur de l'article, les autres participants à la discussion, et la langue française. Vous pouvez suivre les réponses par flux RSS.