← Article précédent Article suivant →
Publié le 24 mai 2015
Concept Design

Un concept utilisant les animations CSS3 et très peu de JavaScript pour réaliser un bouton d'action flottant Material Design et du morphing.

Tutoriel HTML5 / CSS3 / jQuery - Bouton d'Action Goutte d'Eau et Morphing Material Design

Avant de débuter


Cette série de tutoriels intitulée "Concept Design" a pour but de convertir en code les concepts de design que nous pouvons voir sur la toile. En effet, de plus en plus nous voyons des concepts de design voir le jour, s'en suit alors des réalisations originales de designers. J'ai alors eu l'idée de réaliser le développement de ces créations et de vous les partager sous forme de tutoriels.
N'hésitez pas à me partager des liens de concept design qui pourraient faire l'objet d'un nouveau tutoriel dans cette série.

A l'origine de ce design


Cette fois-ci j'ai laissé libre cours à mon imagination et réalisé le design moi-même.

Qu'est-ce que c'est ?


Lorsque l'on clique sur notre bouton d'action (Guide Google), d'autres boutons apparaissent les uns à la suite des autres suite à une goutte d'eau. Une fois cliqués, ces nouveaux boutons feront l'objets d'une transformation (morphing) en s'agrandissant et en prenant tout l'espace du conteneur parent.

Une histoire de code


Le Material Design étant dans l'ère du temps, les ressources qu'il renferme sont intéressantes pour ce que nous allons voir.
Le but ici sera d'adopter une certaine logique de façon à utiliser le moins de code JavaScript possible et en se focalisant essentiellement sur notre code CSS. HTML et CSS seront les principales technologies, nous utiliserons la librairie jQuery de façon à aller plus vite dans le développement.
En effet, jQuery sera seulement employée pour écouter les évènements au clic de souris et y faire la gestion de certaines de nos classes CSS en fonction.

Je travaille dans un fichier "index.html" pour la structure et un fichier "style.css" pour le style. Je ne mets que le code permettant de faire l'essentiel, vous pouvez télécharger le code en entier à l'aide du bouton "Code source" ci-dessus. Mais essayez de bien comprendre avant de le télécharger. :) Allez hop, on passe au code, c'est parti !

La structure HTML


On structure notre page via HTML de façon à adopter une certaine hiérarchie pour notre réalisation.
<!-- Conteneur principal où la transformation sera appliquée -->
<div id="wrapper">

    <!-- Goutte d'eau -->
    <div class="water-drop"></div>

    <!-- Bouton d'action flottant -->
    <div class="button button-floating"></div>
    <!-- Ombre du bouton d'action -->
    <div class="button-floating-shadow"></div>

    <!-- Les sous boutons -->
    <div class="button button-sub" data-color="purple"></div>
    <div class="button button-sub" data-color="green"></div>
    <div class="button button-sub" data-color="pink"></div>
    <div class="button button-sub" data-color="indigo"></div>
    
</div>

JavaScript


Comme dit précédemment, nous allons utiliser un peu de JavaScript afin de gérer certaines classes CSS suite aux clics de souris.

Lorsque notre bouton d'action est cliqué, alors on ajoute ou retire la classe "button-floating-clicked" à notre conteneur principal. De cette façon, nous pourrons alors styliser nos éléments une fois que notre bouton d'action est cliqué ou non. A chaque clic de notre bouton d'action, nous réinitialisons les classes de notre conteneur et nous ajoutons ou retirons la classe "button-floating-clicked-out" pour l'animation de sortie de nos sous boutons que nous verrons dans la partie CSS.

Nous faisons la même chose pour nos sous boutons. A la différence qu'ici, nous avons plusieurs boutons représentés par une couleur différente. Pour savoir quel sous bouton a été cliqué, nous récupérons la valeur correspondant à l'attribut "data-color" de notre HTML. Ensuite nous ajoutons notre classe "button-sub-[couleur]-clicked" à notre conteneur principal pour que l'on sache quel sous bouton a été cliqué et alors styliser en fonction.
$(document).ready(function() {
    $(".button-floating").click(function() {
        var $wrapper = $("#wrapper");

        if (!$wrapper.hasClass("button-floating-clicked"))
            $wrapper.toggleClass("button-floating-clicked-out");

        $wrapper.toggleClass("button-floating-clicked");

        $(".button-sub").click(function() {
            var color = $(this).data("color");

            $wrapper.attr("class", "button-floating-clicked\
                                    button-floating-clicked-out");
            $wrapper.addClass("button-sub-" + color + "-clicked");
        });
    });
});

Le style


On passe au CSS pour styliser le tout.

Reset CSS


Nous allons utiliser le bout de code Reset CSS afin d'avoir un rendu similaire en terme d'espaces, marges, etc. pour les différents navigateurs que nous utilisons.
html, body, div, span, applet, object, iframes,
h1, h2, h3, h4, h5, h6, p, blockquote, pre,
a, abbr, acronym, address, big, cite, code,
del, dfn, em, img, ins, kbd, q, s, samp,
small, strike, sub, sup, tt, var,
u, i, center,
dl, dt, dd, ol, ul, li,
fieldset, form, label, legend,
table, caption, tbody, tfoot, thead, tr, th, td,
article, aside, canvas, details, embed,
figure, figcaption, footer, header, hgroup,
menu, nav, output, ruby, section, summary,
time, mark, audio, video {
    margin: 0;
    padding: 0;
    border: 0;
    font-size: 100%;
    font: inherit;
    vertical-align: baseline;
}

article, aside, details, figcaption, figure,
footer, header, hgroup, menu, nav, section {
    display: block;
}

blockquote, q {
    quotes: none;
}

blockquote:before, blockquote:after,
q:before, q:after {
    content: '';
    content: none;
}

table {
    border-collapse: collapse;
    border-spacing: 0;
}

* : stylisation de tous nos éléments


Aussi, nous allons appliquer un "box-sizing" à "border-box" à tous nos éléments pour que leurs dimensions ne soient pas impactées par les "padding" que nous ajoutons. (en savoir plus)
* {
    box-sizing: border-box;
}

#wrapper : notre conteneur principal


On centre horizontalement et verticalement notre conteneur principal au milieu de notre page en lui donnant une "position absolute" ainsi qu'à l'aide de la propriété "transform" et "left, top".
De plus, on indique l'"overflow" à "hidden" de façon à ce que lorsque l'on fera nos transformations sur nos sous boutons, ceux-ci ne sortent pas de notre conteneur.
#wrapper {
    position: absolute;
    background-color: #FFF;
    left: 50%;
    top: 50%;
    width: 600px;
    height: 400px;
    padding: 24px;
    overflow: hidden;
    border-radius: 1.5px;
    box-shadow: 0 3px 6px rgba(0, 0, 0, .16),
                0 3px 6px rgba(0, 0, 0, .23);
    -webkit-transform: translate(-50%, -50%);
    transform: translate(-50%, -50%);
}

.water-drop : la goutte d'eau


Initialement on rend notre goutte d'eau non visible. On lui donne une priorité sur l'axe "Z" de 30 de façon à ce qu'elle superpose nos sous boutons qui auront un z-index inférieur.
Une fois notre bouton d'action cliqué, alors on renseigne une animation à notre goutte d'eau. Ici, l'animation durera 0.8s, comme vous pouvez le voir, la goutte d'eau stagnera au début et tombera ensuite. Nous contrôlons sa chute en terme de timing via "cubic-bezier".
.water-drop {
    visibility: hidden;
    position: absolute;
    z-index: 30;
    margin-left: 6px;
    background: url(../images/waterdrop.svg) 0 0 no-repeat;
    width: 42px;
    height: 58px;
    background-size: 42px 58px;
}
.button-floating-clicked .water-drop {
    -webkit-animation: waterDrop .8s cubic-bezier(1, 0, .5, 0);
    animation: waterDrop .8s cubic-bezier(1, 0, .5, 0);
}

@keyframes waterDrop : animation de notre goutte d'eau


Nous nommons notre animation "waterDrop". Nous rendons notre goutte d'eau visible et au file du temps, nous réduisons son opacité jusqu'à la fin de l'animation à 100% où son opacité atteindra 0.
Aussi, sur l'axe des Y, donc verticalement, nous transformons notre goutte d'eau de 294px (jusqu'au dernier sous bouton), c'est ici que la chute se créée.
@-webkit-keyframes waterDrop {
    0% {
        visibility: visible;
    }
    75% {
        opacity: .6;
    }
    87.5% {
        opacity: .4;
    }
    100% {
        -webkit-transform: translateY(294px);
        opacity: 0;
    }
}
@keyframes waterDrop {
    0% {
        visibility: visible;
    }
    75% {
        opacity: .6;
    }
    87.5% {
        opacity: .4;
    }
    100% {
        transform: translateY(294px);
        opacity: 0;
    }
}

.button : tous les boutons


Consacrons-nous maintenant aux boutons, ici nous allons appliquer un style qui sera valable pour tous. Ils sont ronds et un "margin-bottom" de 18px pour l'espacement entre chacun d'eux excepté le dernier.
.button {
    position: relative;
    border-radius: 50%;
    margin-bottom: 18px;
}
.button:last-child {
    margin-bottom: 0;
}

.button-floating : bouton d'action flottant


Stylisons maintenant notre bouton déclencheur. Premièrement, ajoutons un "+" à celui-ci à l'aide du pseudo-élément "::before". Puis nous lui donnons une taille, ainsi qu'à son ombre que nous verrons juste après. Ensuite il aura une priorité de 40, nous verrons pourquoi avec son ombre et une durée d'animation de 0.3s. Une fois notre bouton cliqué, alors nous lui appliquons une rotation de 225°.
.button-floating::before {
    content: "+";
}
.button-floating, .button-floating-shadow {
    width: 54px;
    height: 54px;
}
.button-floating {
    z-index: 40;
    cursor: pointer;
    background-color: #2196F3;
    color: #FFF;
    font-size: 30px;
    text-align: center;
    line-height: 54px;
    -webkit-transition-duration: .3s;
    transition-duration: .3s;
}
.button-floating-clicked .button-floating {
    -webkit-transform: rotate(225deg);
    transform: rotate(225deg);
}

.button-sub, .button-floating-shadow : les ombres


Attaquons les ombres maintenant. Tous nos sous boutons auront une ombre.
Aussi, rappelez-vous dans notre HTML nous avons une "div" spéciale avec pour classe "button-floating-shadow" pour l'ombre de notre bouton d'action. Mais pourquoi créer une nouvelle "div" pour cette ombre alors que nous pouvons directement l'appliquer à notre classe "button-floating" ?
Je vous laisse essayer d'appliquer cette ombre à votre bouton d'action sans créer une nouvelle "div". N'oubliez pas que nous faisons une rotation sur ce bouton, du coup l'ombre également, et sachant que la fin de l'animation est de +45° par rapport à l'état initial, alors vous verrez votre ombre décalée elle aussi. C'est une petite alternative que j'ai trouvé, il doit sûrement en exister d'autres. :)

Nous donnons une priorité de 20 à l'ombre de notre bouton d'action de façon à ce qu'elle soit derrière celui-ci.
Appliquons une nouvelle ombre à nos sous boutons lorsque ceux-ci sont survolés par le curseur de la souris. Nous allons aussi appliquer cette même ombre à notre bouton d'action lorsque celui-ci est survolé, nous utiliserons le sélecteur d'adjacence directe "+" car n'oublions pas que l'ombre de notre bouton d'action est une autre "div". Ce sélecteur va permettre de sélectionner un élément "frère", donc qui se situe en dessous.
Cette ombre sera conservée lorsque notre bouton déclencheur est cliqué grâce à ".button-floating-clicked .button-floating-shadow".
.button-sub, .button-floating-shadow {
    box-shadow: 0 3px 6px rgba(0, 0, 0, .16),
                0 3px 6px rgba(0, 0, 0, .23);
    -webkit-transition-duration: .3s;
    transition-duration: .3s;
}
.button-floating-shadow {
    position: absolute;
    z-index: 20;
    top: 24px;
    border-radius: 50%;
}
.button-sub:hover,
.button-floating:hover + .button-floating-shadow,
.button-floating-clicked .button-floating-shadow {
    box-shadow: 0 10px 20px rgba(0, 0, 0, .19),
                0 6px 6px rgba(0, 0, 0, .23);
}

.button-sub : les sous boutons


Par défaut, nous donnons une opacité de 0 à nos sous boutons ainsi qu'une taille plus petite que notre bouton d'action. Nous renseignons "animation-fill-mode" à "backwards" pour que nous conservons les valeurs de notre première animation, ici notre animation "bounceIn".

Alors pourquoi une classe "button-floating-clicked-out" alors que nous pouvons attribuer l'animation bounceOut directement à ".button-sub" ? L'animation bounceOut a une opacité de "1" à son début, si on l'applique à ".button-sub" alors au chargement de notre page, nous verrons nos sous boutons sans même avoir fait quoi que ce soit. C'est pour cela qu'avec le JavaScript nous jouons avec toggleClass(). :)

Vous l'avez deviné, on associe l'animation fadeOut lorsque nous recliquons pour la deuxième fois sur notre bouton d'action. Et bounceIn est attribué lors du premier clic sur notre bouton d'action.
.button-sub {
    opacity: 0;
    z-index: 20;
    left: 6px;
    width: 42px;
    height: 42px;
    -webkit-animation-duration: .3s;
    animation-duration: .3s;
    -webkit-animation-fill-mode: backwards;
    animation-fill-mode: backwards;
}
.button-floating-clicked-out .button-sub {
    -webkit-animation-name: fadeOut;
    animation-name: fadeOut;
}
.button-floating-clicked .button-sub {
    opacity: 1;
    cursor: pointer;
    -webkit-animation-name: bounceIn;
    animation-name: bounceIn;
}

@keyframes bounceIn, @keyframes fadeOut : d'accord mais elles font quoi ces animations ?


Tout au long de nos animations, nous controlons l'opacité de nos sous boutons.
Pour l'animation bounceIn, nous donnons l'effet de "rebond" en transformant légèrement via la fonction scale() qui augmentera et réduira nos sous boutons.
Pour l'animation fadeOut, c'est la même chose, mais seulement avec un état de début et de fin (0% et 100%) sans intermédiaire.
@-webkit-keyframes bounceIn {
    0% {
        opacity: 0;
        -webkit-transform: scale(.3);
    }
    50% {
        opacity: 1;
        -webkit-transform: scale(1.05);
    }
    70% {
        -webkit-transform: scale(.9);
    }
    100% {
        -webkit-transform: scale(1);
    }
}
@keyframes bounceIn {
    0% {
        opacity: 0;
        transform: scale(.3);
    }
    50% {
        opacity: 1;
        transform: scale(1.05);
    }
    70% {
        transform: scale(.9);
    }
    100% {
        transform: scale(1);
    }
}
@-webkit-keyframes fadeOut {
    0% {
        opacity: 1;
        -webkit-transform: scale(1);
    }
    100% {
        -webkit-transform: scale(0);
    }
}
@keyframes fadeOut {
    0% {
        opacity: 1;
        transform: scale(1);
    }
    100% {
        transform: scale(0);
    }
}

Délai d'animation de nos sous boutons


De façon à avoir un effet de cascade lors de l'apparation ou disparition de nos boutons, on va apporter un délai d'animation différent pour chacun d'eux.
Par exemple, pour le bouton de couleur "purple", on attribue par défaut un délais de 0.2s. Notre animation fadeOut sera jouée 0.2s plus tard pour ce bouton pour qu'il soit le dernier à disparaître. Une fois notre bouton d'action cliqué, rappelez-vous nous faisons appel à notre animation bounceIn, alors nous attribuons un délai de 0.6s à notre bouton de couleur "purple" pour qu'il apparaît en premier. Ce délai est volontairement "long" car nous avons l'animation de notre goutte d'eau avant l'animation de nos sous boutons.
.button-sub[data-color=purple] {
    background-color: #9C27B0;
    -webkit-animation-delay: .2s;
    animation-delay: .2s;
}
.button-floating-clicked .button-sub[data-color=purple] {
    -webkit-animation-delay: .6s;
    animation-delay: .6s;
}

.button-sub[data-color=green] {
    background-color: #8BC34A;
    -webkit-animation-delay: .15s;
    animation-delay: .15s;
}
.button-floating-clicked .button-sub[data-color=green] {
    -webkit-animation-delay: .65s;
    animation-delay: .65s;
}

.button-sub[data-color=pink] {
    background-color: #E91E63;
    -webkit-animation-delay: .1s;
    animation-delay: .1s;
}
.button-floating-clicked .button-sub[data-color=pink] {
    -webkit-animation-delay: .7s;
    animation-delay: .7s;
}

.button-sub[data-color=indigo] {
    background-color: #3F51B5;
    -webkit-animation-delay: .05s;
    animation-delay: .05s;
}
.button-floating-clicked .button-sub[data-color=indigo] {
    -webkit-animation-delay: .75s;
    animation-delay: .75s;
}

Le morphing des sous boutons


Allez dernière étape, courage ! :D

Avec JavaScript, nous connaissons le sous bouton qui a été cliqué en récupérant l'attribut "data-color" et en ajoutant la classe "button-sub-[couleur]-clicked" à notre conteneur principal "#wrapper". De cette manière, nous pouvons styliser nos sous boutons en fonction.
Donc, lorsque notre bouton d'action est cliqué et qu'un sous bouton est cliqué, ce même sous bouton à un "z-index" de 0 de façon à ne pas le retrouver au dessus de la pile. En effet, nous le transformons à l'aide de la fonction scale() en renseignant une valeur à 30, le faisant prendre tout l'espace de notre conteneur principal. Nous avons transformé un bouton, il faut alors mettre son "cursor" à "default" pour ne pas voir notre curseur de souris en "pointer" lorsque nous survolons notre conteneur "#wrapper".
.button-floating-clicked.button-sub-purple-clicked .button-sub[data-color=purple],
.button-floating-clicked.button-sub-green-clicked .button-sub[data-color=green],
.button-floating-clicked.button-sub-pink-clicked .button-sub[data-color=pink],
.button-floating-clicked.button-sub-indigo-clicked .button-sub[data-color=indigo] {
    z-index: 0;
    cursor: default;
    -webkit-transition: all .4s ease-in;
    transition: all .4s ease-in;
    -webkit-transform: scale(30);
    transform: scale(30);
}

Contact


Si vous rencontrez des difficultés ou des erreurs en rapport avec ce tutoriel, contactez-moi via mon adresse email ou Twitter.

Faites-moi parvenir des créations de ce type, on verra ce que l'on pourra faire. :)

Partagez


En espèrant que ce tutoriel vous ait plu ! En attendant les prochains, n'hésitez pas à le partager en cliquant sur les boutons ci-dessous.