Effet de Vague Material Design

Voir la vidéo

Dans cette vidéo nous allons voir comment réaliser un effet de vague type Material Design à l'aide du CSS trois des animations, mais aussi d'un peu de JavaScript. Le principe est assez simple, lorsque l'on va cliquer sur un élément, un cercle va grandir automatiquement en ayant comme origine le point sur lequel on a cliqué.

Article original : Louistiti

Première version

Le principe de cet effet est qu'il soit capable de s'adapter à n'importe quel élément donc on va commencer par styliser la vague présente sur notre bouton.

.ripple {
    position: absolute;
    opacity: 0;
    border-radius: 50%;
    width: 2px;
    height: 2px;
    background-color: rgba(255, 255, 255, .35);
    animation-duration: 1s;
    animation-name: ripple;
}
@-webkit-keyframes ripple {
    0% {
        opacity: 1;
    }
    100% {
        -webkit-transform: scale(165);
    }
}
@keyframes ripple {
    0% {
        opacity: 1;
    }
    100% {
        transform: scale(165);
    }
}

Pour la partie JavaScript nous allons automatiquement, lorsque l'utilisateur clique sur notre élément, ajouter un nouvel élément qui aura la classe ripple. Si on souhaite aller plus loin, et conserver un code HTML propre,on peut automatiquement détecter la fin de l'animation et retirer notre élément ripple

$(document).ready(function() {
    $(".button").click(function(e) {
        var $this = $(this); 
        var parentOffset = $this.offset(),
            cursorX      = e.pageX - parentOffset.left,
            cursorY      = e.pageY - parentOffset.top;

        $this.children(".ripple").remove();
        $this.append("<div class=\"ripple\"></div>");
        $this.children(".ripple").css({"left" : cursorX + "px", "top" : cursorY + "px"});

        $(".ripple").one("webkitAnimationEnd mozAnimationEnd oAnimationEnd\
                          oanimationend animationend", function() {
            $this.children(".ripple").remove();
        });
    });
});

Pour aller plus loin

Evolutions proposées par Grafikart

Le script proposé précédemment fonctionne très bien, mais selon moi comporte quelques petits problèmes. Le premier étant que l'on est obligé d'importer la librairie jQuery pour le faire fonctionner, alors que l'on n'en a pas forcément besoin pour ce cas là. Cet effet étant principalement esthétique on peut se passer d'une compatibilité avec les vieux navigateurs. De plus, il y a quelques problèmes au niveau des intéractions :

  • Il n'est pas possible d'appuyer plusieurs fois sur le bouton pour déclencher plusieurs vagues
  • Si on reste appuyé sur la souris, aucun effet n'est déclenché
  • L'effet ne s'appliquera pas sur les éléments qui sont ajoutés après coup (par exemple en utilisant de l'Ajax)

Donc je vous propose d'essayer de réécrire le script, sans utiliser jQuery, et en corrigeant les problèmes vus précédemment.

On va commencer par réécrire en partie le CSS. Le principe est de créer une classe .wave-effect que l'on va associer à tous les éléments, sur lesquels on souhaite mettre en place l'effet de vague. La vague, quant à elle, sera créée en utilisant un élément qui a la classe .wave. Afin de garder le code le plus simple possible je vous donne ici la version sans préfixes.

.wave-effect{
    z-index: 1;
    position: relative;
    user-select: none;
}

.wave {
    position: absolute;
    border-radius: 50%;
    opacity: 1;
    z-index: -1;
    background: rgba(255, 255, 255, 0.25);
    transition: transform 1s cubic-bezier(0.23, 1, 0.32, 1), opacity 2s cubic-bezier(0.23, 1, 0.32, 1);
    transform: scale(0);
    pointer-events: none;
}

Pour l'élément .wave-effect on applique une position relative afin que la vague vienne se placer par rapport à cet élément. On ajoute aussi différents attributs permettant d'éviter un style propre aux navigateurs mobiles (carré bleu lorsque l'on clique sur un élément par exemple). Pour l'élément .wave on retrouve des règles similaires à ce que l'on a vu précédemment, sauf que cette fois-ci on utilisera les transitions pour réaliser l'animation. On modifiera la propriété transform en JavaScript.

Pour la partie JavaScript, on va isoler notre code à l'intérieur d'une fonction afin d'éviter que les déclarations de nos différentes fonctions rentrent en collision avec des déclarations précédentes. Contrairement à ce que l'on a fait précédemment dans cette version nous allons écouter deux événements :

  • Lorsque l'utilisateur initialisera un clic on déclenchera l'affichage de la vague.
  • Lorsque l'utilisateur lèvera le doigt, ou relâchera son clic, on déclenchera alors le masquage
;( function( window ) {
    "use strict";

    // On lance l'affichage de la vague au touch ou au click
    if ( "ontouchstart" in window ) {
        document.body.addEventListener( "touchstart", show, false );
    }
    document.body.addEventListener( "mousedown", show, false );

} )( window );

Il faudra donc maintenant créer la fonction show() qui permettra d'afficher notre vague. Contrairement à l'exemple précédent, l'événement est lancé sur le body, du coup il nous faudra, dans un premier temps, savoir si l'événement affecte un élément qui a la classe wave–effect. Si on trouve un élément avec cette classe, alors on rajoutera notre élément wave et on modifiera son échelle et son opacité pour faire apparaître la vague.

Pour l'animation on va passer l'échelle de 0 à 1, et on va faire en sorte que le cercle .wave soit suffisamment grand pour englober la totalité de notre élément. Histoire de garder le code propre et compréhensible, on va séparer cette logique dans une fonction et utiliser le fameux théorème de Pythagore pour obtenir le rayon de ce cercle.

// Cette fonction permet d'afficher une vague suite à un évènement
function show( e ) {

    var element = null;

    // On regarde si l'évènement affecte un élément avec la class .wave-effect
    var target = e.target || e.srcElement;
    while ( target.parentElement !== null ) {
        if ( target.classList.contains( "wave-effect" ) ) {
            element = target;
            break;
        }
        target = target.parentElement;
    }

    // Si aucun élement .wave-effect on abandonne, it's a TRAP !
    if ( element === null ) {
        return false;
    }

    // On crée l'élément wave et on l'ajoute à notre élement
    var wave = document.createElement( "div" );
    wave.className = "wave";
    element.appendChild( wave );

    // On anime la transformation scale() sans oublier les préfixes...
    var position = getRelativeEventPostion( element, e );
    var radius = getRadius( element, position );
    var scale = "scale(1)";
    wave.style.left = ( position.x - radius ) + "px";
    wave.style.top = ( position.y - radius ) + "px";
    wave.style.width = ( radius * 2 ) + "px";
    wave.style.height = ( radius * 2 ) + "px";
    wave.style[ "-webkit-transform" ] = scale;
    wave.style[ "-moz-transform" ] = scale;
    wave.style[ "-ms-transform" ] = scale;
    wave.style[ "-o-transform" ] = scale;
    wave.style.transform = scale;

    // Quand on quitte le bouton
    element.addEventListener( "mouseup", hide, false );
    element.addEventListener( "mouseleave", hide, false );
    if ( "ontouchstart" in window ) {
        document.body.addEventListener( "touchend", hide, false );
    }

}

// Permet de récupérer la position d'un élement sur la page
function getRelativeEventPostion( element, e ) {
    var offset = {
        top: element.getBoundingClientRect().top + window.pageYOffset - element.clientTop,
        left: element.getBoundingClientRect().left + window.pageXOffset - element.clientLeft
    };
    return {
        y: e.pageY - offset.top,
        x: e.pageX - offset.left
    };
}

// Permet d'obtenir le rayon d'un cercle qui contiendra tout l'élément, merci Pythagore ^^
function getRadius( element, position ) {
    var w = Math.max( position.x, element.clientWidth - position.x );
    var h = Math.max( position.y, element.clientHeight - position.y );
    return Math.ceil( Math.sqrt( Math.pow( w, 2 ) + Math.pow( h, 2 ) ) );
}

À la fin de cette fonction, on va détecter si l'utilisateur lève son doigt de la souris (ou de son écran tactile), et lancer une fonction qui permettra de cacher la vague.

Pour cette seconde fonction, on va chercher au sein de notre élément, la dernière vague .wave. Sur cette dernière on va faire une animation de disparition en diminuant l'opacité, et la retirer ensuite de notre élément parent afin de garder un DOM propre. Pour cet effet de disparition, on va mettre en place un petit délai pour éviter que lors d'un clic rapide, la vague se cache immédiatement.

// Déclenché au moment d'un release, on masque la dernière vague présente
function hide( e ) {
    var element = this;

    // On trouve le dernier élement .wave
    var wave = null;
    var waves = element.getElementsByClassName( "wave" );
    if ( waves.length > 0 ) {
        wave = waves[ waves.length - 1 ];
    } else {
        return false;
    }

    // On fait disparaitre la vague en opacité
    wave.style.opacity = 0;

    // On supprime l'élément vague au bout de la durée de l'animation
    setTimeout( function() {
        try {
            element.removeChild( wave );
        } catch ( e ) {
            return false;
        }
    }, 2000 );
}

Voilà on a donc terminé avec cet effet. Cette version du code, sans jQuery, est un petit peu plus longue, car on a ajouté quelques fonctionnalités au passage. Ceci étend dit, on voit ici qu'il est tout à fait possible de créer des effets JavaScript, sans forcément devoir importer la librairie jQuery. Cela permet de rendre le code plus portable si on souhaite l'intégrer plus tard dans des librairies plus complexes comme ReactJS ou Angular.

Publié
Technologies utilisées
Auteur :
Louistiti
Partager