Bloguons JavaScript

Parce que je me dis tout le temps : « Faut que j'écrive ça quelque part. »
Home

Aller au menu | Aller à la recherche

Remplacer slideDown() et slideUp() dans jQuery

, 22:46 - Lien permanent

Ce n'est pas facile de trouver une solution aisée à remplacer les méthodes $.slideDown() et $.slideUp(), qui servent essentiellement à plier et déplier des boîtes verticalement. Heureusement, Javascript a assez évolué pour pouvoir programmer une alternative assez succincte.

Pour ce faire, on va utiliser certaines méthodes natives :

window.getComputedStyle(Element), qui permet de retrouver les valeurs de toutes les propriétés CSS d'un élément. Il retourne le même objet que la propriété Element.style, à la différence que ce dernier ne retourne que ce qui est dans l'attribut stylede l'élément, alors que le premier retourne le résultat de l'application des feuilles de style. La méthode existe depuis IE10, donc on est tranquille côté support des navigateurs.

On exploite aussi les transitions CSS, qui existent sous leur forme standard depuis au moins 12 ans.

C'est quand même relativement beaucoup de code, mais on n'a pas le choix: La hauteur d'un élément dépend de plusieurs facteurs : le contenu dans l'élément, certes, mais aussi la marge extérieure (margin), la bordure (border) et la marge intérieure (padding).

On va commencer par slideUp(), relativement facile puisqu'on fait disparaître l'élément.

function slideUp(el, dur = 500) {
    // On commence par spécifier la durée de la transition
    el.style.transitionProperty = 'height, margin, padding, border-width';
    el.style.transitionDuration = dur + 'ms';
    // On fixe la hauteur
    el.style.boxSizing = 'border-box';
    el.style.height = el.offsetHeight + 'px';
    el.offsetHeight; // On stabilise avant la transformation.
    el.style.overflow = 'hidden'; // Pour éviter que le contenu dépasse du cadre
    // Les propriétés suivantes seront affectées au ralenti
    el.style.height = '0';
    el.style.paddingTop = '0';
    el.style.paddingBottom = '0';
    el.style.marginTop = '0';
    el.style.marginBottom = '0';
    el.style.borderTopWidth = '0';
    el.style.borderBottomWidth = '0';
    // Une fois les propriétés demandées,
    // on laisse la transition CSS faire son oeuvre,
    // puis on supprime les propriétés et cache littéralement l'élément
    window.setTimeout(() => {
        el.style.display = 'none';
        el.style.removeProperty('height');
        el.style.removeProperty('padding-top');
        el.style.removeProperty('padding-bottom');
        el.style.removeProperty('margin-top');
        el.style.removeProperty('margin-bottom');
        el.style.removeProperty('overflow');
        el.style.removeProperty('transition-duration');
        el.style.removeProperty('transition-property');
        el.style.removeProperty('border-top-width');
        el.style.removeProperty('border-bottom-width');
    }, dur);
}

cible.offsetHeight retourne la hauteur actuelle de l'élément, incluant sa bordure. Pour ce faire, le box-sizing doit être border-box. La raison pour laquelle on l'appelle sans l'affecter à une variable est parce que cela laisse passer un "tick" permettant à la page de stabiliser son affichage avec les propriétés CSS qu'on vient d'affecter au préalable.

slideDown() fera réapparaître l'élément :

function slideDown(el, dur = 500) {
    el.style.removeProperty('display');
    const c = window.getComputedStyle(el);
    el.style.display = c.display === 'none' ? 'block' : c.display;
    // On va chercher la hauteur que l'élément prendra lorsqu'il sera ouvert
    let height = el.offsetHeight;
    // On ferme l'élément
    el.style.overflow = 'hidden';
    el.style.height = '0';
    el.style.paddingTop = '0';
    el.style.paddingBottom = '0';
    el.style.marginTop = '0';
    el.style.marginBottom = '0';
    el.style.borderTopWidth = '0';
    el.style.borderBottomWidth = '0';
    el.offsetHeight; // On stabilise avant la transformation
    el.style.boxSizing = 'border-box';
    el.style.transitionProperty = "height, margin, padding, border";
    el.style.transitionDuration = dur + 'ms';
    el.style.height = height + 'px';
    el.style.removeProperty('padding-top');
    el.style.removeProperty('padding-bottom');
    el.style.removeProperty('margin-top');
    el.style.removeProperty('margin-bottom');
    el.style.removeProperty('border-top-width');
    el.style.removeProperty('border-bottom-width');
    // Une fois la transition faite, on enlève toutes
    // les propriétés fixées auparavant, ni vu ni connu.
    window.setTimeout(() => {
        el.style.removeProperty('height');
        el.style.removeProperty('overflow');
        el.style.removeProperty('transition-duration');
        el.style.removeProperty('transition-property');
    }, dur);
}

slideToggle() appellera l'une des deux méthodes, selon l'état actuel de l'élément :

function slideToggle(el, dur = 500) {
    return window.getComputedStyle(el).display === 'none' ?
        slideDown(el, dur) : 
        slideUp(el, dur);
}