Sur le dernier site sur lequel je travaille, mon graphiste m’a gentiment demandé d’animer mon plugin hide-show system, pour que les contenus apparaissent progressivement avec un bel effet de fondu. En cogitant un peu avec ma gentille stagiaire, nous sommes arrivés à une solution que je vous détaille ici.
Les seuls moyens de rendre un contenu non focusable (au clavier) sont :
- soit de le cacher avec un
display: none
; - soit avec un
visibility: hidden
; - ou soit de rendre non focusables tous les éléments (dudit contenu) pouvant l’être via un
tabindex="-1"
.
Mon plug-in avait utilisé la solution la plus simple, la première : display: none
. Mes burger-icons utilisent la troisième, vu que les éléments cachés sont de type liens (et donc plus ou moins connus et prévisibles).
Quelques points à savoir :
- Comme je l’avais écrit dans mon billet , on ne peut pas animer la propriété
height
de0
àauto
, il faut une valeur numérique. Dura lex sed lex. - Il faudra donc animer la propriété
max-height
de0
àune valeur suffisante mais pas trop
afin que l’animation soit rapide… - … et seul problème, un élément avec un
max-height
même à une valeur de 0 laisse son contenu focusable. - La propriété
display
n’est pas animable ou « transitionnable ». Dura lex sed lex².
Comme je n’ai aucune prévisibilité sur le contenu – il peut y avoir déjà des éléments avec tabindex="-1"
, cela devient très casse-pied, il faut les marquer pour les retrouver, etc. – j’ai écarté d’emblée la troisième solution. Comme la propriété display
n’est pas animable, exit aussi. Il me reste donc la propriété visibility
en roue de secours.
Rappelons son fonctionnement : cette propriété rend visible ou non le contenu auquel elle s’applique, mais elle ne change pas la place qu’il occupe, qu’il soit visible ou non.
J’ai bien essayé quelques animations CSS, mais hormis des résultats rigolos qui vous feraient vous inquiéter sur ma santé mentale, rien de bien tangible. Bref, l’idée est de revenir aux bases, les transitions CSS (après tout, on parle bien d’une transition entre deux états).
Bref, mon plug-in hide-show permet de connaitre l’état caché ou non caché une fois que le JavaScript applique lesdits états. Donc en supposant que j’ai utilisé l’attribut data-hideshow-prefix-class="animated"
qui va donc préfixer les classes appliquées par ledit plug-in, cela nous donne .animated-expandmore__to_expand
pour le contenu dans l’état « affiché » et [aria-hidden=true].animated-expandmore__to_expand
pour le contenu dans l’état « caché ».
Pour être même plus précis : .animated-expandmore__to_expand
cible tous les états et [aria-hidden=true].animated-expandmore__to_expand
cible uniquement l’état caché (qui peut donc surcharger le précédent).
La première idée fut de faire cela (je mets le code sans préfixes pour des raisons de longueur) :
/* ouvert */
.animated-expandmore__to_expand {
display: block;
overflow: hidden;
max-height: 80em;
visibility: visible;
transition: visibility 1s ease, max-height 1s ease;
}
/* fermé */
[aria-hidden=true].animated-expandmore__to_expand {
display: block;
max-height: 0;
visibility: hidden;
}
Cela semble logique, toutefois, cela ne donne pas le résultat attendu : la propriété visibility
n’a pas d’état « intermédiaire », c’est visible ou invisible. Si je ne la mets pas dans la transition, quand on cache le contenu, tout devient invisible d’un coup et se ferme ensuite. Pas génial.
Du coup, la propriété opacity
va rentrer dans le jeu :
/* ouvert */
.animated-expandmore__to_expand {
display: block;
overflow: hidden;
opacity: 1;
max-height: 80em;
visibility: visible;
transition: visibility 1s ease, max-height 1s ease, opacity 1s ease;
}
/* fermé */
[aria-hidden=true].animated-expandmore__to_expand {
display: block;
opacity: 0;
max-height: 0;
visibility: hidden;
}
Là cela marche bien. Il y a toutefois un truc qui me chagrine, c’est l’état « indéfini » de la propriété visibility
durant la transition. Cette propriété ne me sert in fine qu’à cacher le contenu au clavier. Du coup, cela me fait deux cas :
- Quand on affiche le contenu, il faut que je passe de suite
visibility
dehidden
àvisible
; - Et quand on cache le contenu, il faut que
visibility
passe devisible
àhidden
uniquement à la fin de l’animation.
Bien sûr, hors de question – même si c’est faisable, je l’ai fait sur mon carrousel – de commencer à placer un listener pour récupérer en JavaScript la fin de l’animation CSS, c’est bien trop compliqué.
Je mets en gras les différences :
/* ouvert */
.animated-expandmore__to_expand {
display: block;
overflow: hidden;
opacity: 1;
max-height: 80em;
visibility: visible;
transition: visibility 0s ease, max-height 1s ease, opacity 1s ease;
transition-delay: 0s;
}
/* fermé */
[aria-hidden=true].animated-expandmore__to_expand {
display: block;
opacity: 0;
max-height: 0;
visibility: hidden;
transition-delay: 1s, 0s, 0s;
}
En fait, l’animation sur visibility
se fera de suite… mais je place un délai sur la transition sur cette propriété dans un sens. Voilà comment il faut le comprendre :
- Quand on va de fermé à ouvert,
visibility
passe instantanément àvisible
sans délai (on verra le contenu quand il va s’étendre) ; - Et quand on va d’ouvert à fermé,
visibility
passera devisible
àhidden
après l’animation, donc au bout d’une seconde.
Là, l’effet fonctionne bien, vous pouvez le voir sur la page de mon plugin hide-show system, je viens d’ajouter une rubrique Bonus: some animations?.
Voilà pour cette petite étude. N’hésitez pas à compléter/confirmer/infirmer, je suis preneur de retours avisés.