Un e-mail interactif avec filtre SVG et transitions CSS

Cette année, Litmus organise des concours communautaires visant à présenter des concepts d’e-mails un peu plus originaux que de trucs tout en tableaux. Le second concours vient de se terminer, et avait pour but de présenter une navigation créative au sein d’un e-mail. Pour participer, j’ai eu envie de porter cet effet de menu gluant créé par Lucas Bebber (et détaillé chez Codrops).

C’est un exemple particulièrement intéressant car cet effet nécessite à la fois l’utilisation d’un filtre SVG pour l’effet gluant à proprement parler, de transitions et transformations CSS pour faire bouger les éléments, et d’une interaction sans JavaScript. J’ai appris plein de choses en m’essayant à cet exercice. Voici le résultat final de ma participation et quelques détails sur le fonctionnement de tout ça.

L’effet gluant

Même s’il a un côté m’as-tu-vu assez prononcé, j’aime beaucoup cet effet parce que c’est proche d’un one-liner en CSS. On applique un filtre SVG en CSS avec filter:url(#monfiltreSVG), et c’est tout. Toute l’ingéniosité de l’effet repose sur le filtre SVG en lui-même. Lucas Bebber a écrit un article très complet chez CSS-Tricks sur l’effet gluant pour expliquer tout ça.

effet-gluant-SVG

Dans les grandes lignes, ça fonctionne comme ça :

  1. On positionne des éléments qui se chevauchent.
  2. On applique un flou avec un effet de filtre feGaussianBlur.
  3. On renforce le contraste sur le résultat flouté avec un effet de filtre feColorMatrix, ce qui donne des bords nets.
  4. On rapplique le contenu initial par dessus avec un filtre feComposite.

Dans le code, ça donne :

<svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="1" height="1">
	<defs>
		<filter id="goo">
			<feGaussianBlur in="SourceGraphic" stdDeviation="10" result="blur"></feGaussianBlur>
			<feColorMatrix in="blur" mode="matrix" values="1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 19 -9" result="goo"></feColorMatrix>
			<feComposite in="SourceGraphic" in2="goo" operator="atop"></feComposite>
		</filter>
	</defs>
</svg>

Pour que cet effet fonctionne dans un e-mail, il faut donc deux choses : le support de la propriété CSS filter, et le support d’une balise <svg> dans le code HTML.

Ça fonctionne bien dans les applications Thunderbird, E-Mail (d’Android 4.4 et moins), Outlook (Android), et sur les webmails d’Orange et SFR.

Ça ne fonctionne pas dans Apple Mail (OS X et iOS), Gmail (webmail et applications), Yahoo, Outlook.com (qui filtre la propriété filter, mais pas le SVG), et les Outlook et autres Lotus Notes.

L’avantage de cet effet, c’est qu’il se dégrade tout à fait gracieusement s’il n’est pas supporté. On peut alors conserver le principe de navigation reposant sur des transformations et transitions CSS.

Transformations et transitions

Dans sa démonstration originale, Lucas Bebber utilisait un positionnement absolu en CSS pour placer les icônes du menu dans leur état par défaut. Malheureusement, pour un e-mail, la propriété position n’est supportée dans quasiment aucun webmail (ni Gmail, Yahoo, Orange, SFR). J’ai alors préféré utilisé la propriété margin (avec une majuscule pour que ça passe bien sur Outlook.com), en utilisant des marges négatives pour masquer les liens du menu derrière l’icône principale.

J’ai alors découvert que Yahoo filtre toute propriété margin contenant une valeur négative. J’ai donc compris que rien de tout ceci ne fonctionnerait sur Yahoo, et j’ai utilisé une déclaration @media yahoo (hack déniché par Mark Robbins) afin de créer des cibles spécifiques pour créer un affichage statique pour Yahoo.

Pour faire apparaître chaque icône du menu, j’utilise une translation en CSS, animée avec une transition. Pour la transition, j’utilise une timing-function avec une courbe d’accélération définie par une fonction cubic-bezier. En y définissant des valeur supérieures à 1, je peux alors donner un léger effet élastique, où les icônes iront légèrement plus loin que leur valeur d’arrivée.

transition:transform 0.5s cubic-bezier(0.5, 1.5, 0.5, 1.25);

Pour réaliser la translation, j’ai utilisé la propriété CSS transform et la fonction translate3d. Je m’étais demandé pourquoi dans ces démos, Lucas Bebber utilisait toujours un translate3d et pas un simple translate. J’ai vite compris en testant dans Safari et Apple Mail, où le rendu en 2D rame dès qu’il est animé.

-webkit-transform:translate3d(-90px, -30px, 0);
transform:translate3d(-90px, -30px, 0);

Et c’est alors que j’ai appris plein de choses en testant sur Outlook.com. Outlook.com filtre toute transformation CSS contenant un translate3d. Outlook.com filtre également toute transformation contenant la moindre unité en pixel. Mais, magie de l’ingénierie chez Microsoft, pas une transformation utilisant une unité %. Le code suivant, combiné au précédent, permet de rendre tout ça fonctionnel sur Outlook.com.

-webkit-transform:translate(-150%, -50%);
transform:translate(-150%, -50%);

Tout ça fonctionne bien dans les applications Apple Mail (OS X et iOS), Thunderbird, E-Mail (d’Android 4.4 et moins), Outlook (iOS et Android), l’application Mail de Windows Phone 8.1, et sur les webmails d’Orange, SFR, Outlook.com.

Ça ne fonctionne pas dans Gmail (webmail et applications), Yahoo, et sur les webmails desktop de La Poste et Free (tous les deux basés sur Zimbra, qui supporte bien la propriété transition, mais supprime la valeur de la propriété transform), et les Outlook et autres Lotus Notes.

Il ne reste plus qu’à rendre tout ça interactif, en déclenchant l’ouverture du menu lors d’une action utilisateur.

Interaction

Il existe plusieurs techniques pour déclencher un événement au clic en CSS. Hugo Giraudel a écrit un bon résumé des techniques les plus courantes chez Codrops. J’ai privilégié l’utilisation d’une case à cocher (avec un <input type="checkbox" />), qui n’est filtré quasiment nulle part. Curieusement, les applications ou webmails qui paniquent et filtrent la moindre propriété CSS margin acceptent sans broncher qu’on utilise des éléments de formulaire dans un e-mail.

<input type="checkbox" class="h8u-checkbox" style="display:none;" />
<div class="h8u-menu-wrapper">
	<a class="h8u-nav-item h8u-nav-item--1">…</a>
	<a class="h8u-nav-item h8u-nav-item--2">…</a>
	<a class="h8u-nav-item h8u-nav-item--3">…</a>
</div>

Ensuite, j’utilise une pseudo-classe :checked en CSS pour cibler la case à cocher une fois cochée. En ajoutant à ce sélecteur le sélecteur d’une balise adjacente ou suivante, on peut ainsi agir sur celle-ci. Dans l’exemple suivant, j’applique la translation sur mon lien ayant la classe h8u-nav-item--1 dès que la case est cochée.

.h8u-checkbox:checked + * .h8u-nav-item--1 {
	-webkit-transform:translate(-150%, -50%);
	transform:translate(-150%, -50%);
	-webkit-transform:translate3d(-90px, -30px, 0);
	transform:translate3d(-90px, -30px, 0);
}

J’aurais pu en rester là si c’était une démo pour le Web. Mais e-mail oblige, certains clients nécessitent du code spécifique. Par exemple, Thunderbird affiche très bien les cases à cocher. Mais on ne peut pas les cocher. Nulle part, jamais. Du coup, le sélecteur :checked n’est jamais déclenché, et le menu ne peut pas s’ouvrir. J’ai donc du cibler spécifiquement Thunderbird (qui ajoute une <div class="moz-text-html" lang="x-unicode"> autour de chaque e-mail), en déclenchant le menu au survol. La même chose se produit dans Outlook.com, où un événement JavaScript du webmail empêche les cases d’être cochées. J’ai donc également ciblé Outlook.com, en utilisant la classe ExternalClass présente sur le webmail desktop. En reprenant la règle CSS précédente, ça donne les sélecteurs suivants.

.h8u-checkbox:checked + * .h8u-nav-item--1,
.moz-text-html .h8u-menu-wrapper:hover .h8u-nav-item--1,
.ExternalClass .h8u-menu-wrapper:hover .h8u-nav-item--1 {
	…
}

Dernier cas particulier : le webmail d’Orange remplace le moindre <input type="checkbox" /> par une balise <noinput>. Cette balise n’existe pas. Et lorsqu’un navigateur moderne rencontre une balise auto-fermante qu’il ne connaît pas, il va faire comme si c’était une balise de type bloc et englober tout le contenu suivant. Le code HTML précédent est alors interprété comme suit.

<noinput type="checkbox" class="h8u-checkbox" style="display:none;">
	<div class="h8u-menu-wrapper">
		<a class="h8u-nav-item h8u-nav-item--1">…</a>
		<a class="h8u-nav-item h8u-nav-item--2">…</a>
		<a class="h8u-nav-item h8u-nav-item--3">…</a>
	</div>
</noinput>

Et ça, ça ne m’arrange pas du tout, car le display:none; normalement dédié à masquer la case à cocher masque désormais toute la navigation. J’ai donc ajouté la règle suivante afin de forcer l’affichage de la balise <noinput>.

noinput.h8u-checkbox {
	display:block !important;
}

La case à cocher n’existant pas, j’ai donc également du faire fonctionner cet effet au survol, en préfixant les règles par un sélecteur de la balise <noinput>. Au final, ça donne le code suivant.

.h8u-checkbox:checked + * .h8u-nav-item--1,
.moz-text-html .h8u-menu-wrapper:hover .h8u-nav-item--1,
.ExternalClass .h8u-menu-wrapper:hover .h8u-nav-item--1,
noinput.h8u-checkbox .h8u-menu-wrapper:hover .h8u-nav-item--1 {
	…
}

Conclusion

Créer un e-mail interactif avec des effets CSS avancés, c’est possible. Ce n’est pas facile, chaque client mail ayant ses particularités nécessitant une bonne compréhension de ses règles de filtrage. Mais c’est possible.

Je considère que l’e-mail que j’ai créé fonctionne correctement dans Apple Mail (sur OS X ou iOS), Thunderbird, E-mail (sur Android 4.4 et moins), l’application Mail de Windows Phone 8.1 et sur les webmails d’Outlook.com, SFR et Orange.

Le rendu de l’e-mail se dégrade gracieusement sur Gmail (webmail et application iOS), Yahoo (webmail et application iOS), l’application SFR sur iOS, et les Outlook et autres Lotus Notes.

L’e-mail ne fonctionne pas (c’est à dire que la navigation est totalement masquée ou non déployable), sur les webmails de La Poste et Free (qui ne supportent pas les transformations CSS), le webmail mobile de La Poste (qui englobe la case à cocher dans une <div class="ui-checkbox">), l’application Outlook (sur iOS et Android).

J’ai passé une dizaine d’heures pour comprendre et créer cet e-mail. En y passant encore un peu plus de temps, je pourrais trouver des solutions alternatives pour ces différents clients, mais je pense en rester là pour le moment.

  1. max, le

    Quel boulot ! Merci pour ce témoignage :-)

  2. Nels, le

    Tu viens de me faire très mal au cerveau ;)

  3. Alain TOMASIAN, le

    Bravo, c’est super intéressant… et ça ouvre de nouvelle perspective.

  4. Nicole Merlin, le

    Very cool effect, and a great article, thank you! Have you found any issues with images loading inside the circles? I am seeing issues on iOS Mail where the text or images inside each circle do not show up at all, but if you close the email and then open it again, the images will load. It’s quite strange.

  5. Rémi, le

    Hi Nicole ! I have indeed stumbled upon this problem, but didn’t find any solution. This seems like a WebKit problem, as I’m also able to reproduce this in Safari. From what I understand, if you apply the SVG filter before the images are fully loaded, then the filter will appear as if images weren’t loaded, and it will never be repainted correctly. In Safari, simply resizing the window seems to repaint the page and correctly calculate the SVG filter with images loaded. I did not encounter this problem with simple HTML text instead of images inside the circles. If you ever find a workaround this, let me know.

  6. Nicole Merlin, le

    Yes, your explanation makes sense! I didn’t find a solution unfortunately, I ended up just disabling the SVG filter, and it still looked really cool anyway :) Thanks again for your great post! It was really fun to try it out.

  7. Nicole Merlin, le

    PS Your blog has so many great posts.. I need to learn French! :)

  8. Bertrand, le

    « Curieusement, les applications ou webmails qui paniquent et filtrent la moindre propriété CSS margin acceptent sans broncher qu’on utilise des éléments de formulaire dans un e-mail. »

    Eh oui en 2015 on marche encore sur la tête ;-)

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.