88 votes

Événement à détecter lorsque position:sticky est déclenché

J'utilise le nouveau position: sticky ( info ) pour créer une liste de contenu semblable à celle d'iOS.

Il fonctionne bien et est de loin supérieur à la précédente alternative JavaScript ( exemple ) mais pour autant que je sache, aucun événement n'est déclenché, ce qui signifie que je ne peux rien faire lorsque la barre atteint le haut de la page, contrairement à la solution précédente.

J'aimerais ajouter une classe (par ex. stuck ) lorsqu'un élément avec position: sticky en haut de la page. Existe-t-il un moyen d'écouter cela avec JavaScript ? L'utilisation de jQuery est correcte.

107voto

vsync Points 11280

Démo con IntersectionObserver (utiliser une astuce) :

// get the sticky element
const stickyElm = document.querySelector('header')

const observer = new IntersectionObserver( 
  ([e]) => e.target.classList.toggle('isSticky', e.intersectionRatio < 1),
  {threshold: [1]}
);

observer.observe(stickyElm)

body{ height: 200vh; font:20px Arial; }

section{
  background: lightblue;
  padding: 2em 1em;
}

header{
  position: sticky;
  top: -1px;                       /*  the trick */

  padding: 1em;
  padding-top: calc(1em + 1px);    /*  compensate for the trick */

  background: salmon;
  transition: .1s;
}

/* styles for when the header is in sticky mode */
header.isSticky{
  font-size: .8em;
  opacity: .5;
}

<section>Space</section>
<header>Sticky Header</header>

En top La valeur doit être -1px ou l'élément n'intersectera jamais le haut de la fenêtre du navigateur (ce qui ne déclenchera jamais la fonction observateur de l'intersection ).

Pour contrer cela 1px de contenu caché, une 1px d'espace doit être ajouté soit à la bordure, soit au remplissage de l'élément collant.

Sinon, si vous souhaitez conserver le CSS tel quel ( top:0 ), puis vous pouvez appliquer la "correction" à l'endroit où se trouve l'appareil. observateur de l'intersection -en ajoutant le paramètre rootMargin: '-1px 0px 0px 0px' (comme @mattrick a montré dans sa réponse)

Démonstration à l'ancienne scroll auditeur d'événements :

  1. Détection automatique du premier parent scrollable
  2. Limitation de l'événement de défilement
  3. Composition fonctionnelle pour la séparation des préoccupations
  4. Mise en cache des rappels d'événements : scrollCallback (pour pouvoir se délier si nécessaire)

    // get the sticky element const stickyElm = document.querySelector('header');

    // get the first parent element which is scrollable const stickyElmScrollableParent = getScrollParent(stickyElm);

    // save the original offsetTop. when this changes, it means stickiness has begun. stickyElm._originalOffsetTop = stickyElm.offsetTop;

    // compare previous scrollTop to current one const detectStickiness = (elm, cb) => () => cb & cb(elm.offsetTop != elm._originalOffsetTop)

    // Act if sticky or not const onSticky = isSticky => { console.clear() console.log(isSticky)

    stickyElm.classList.toggle('isSticky', isSticky) }

    // bind a scroll event listener on the scrollable parent (whatever it is) // in this exmaple I am throttling the "scroll" event for performance reasons. // I also use functional composition to diffrentiate between the detection function and // the function which acts uppon the detected information (stickiness)

    const scrollCallback = throttle(detectStickiness(stickyElm, onSticky), 100) stickyElmScrollableParent.addEventListener('scroll', scrollCallback)

    // OPTIONAL CODE BELOW ///////////////////

    // find-first-scrollable-parent // Credit: https://stackoverflow.com/a/42543908/104380 function getScrollParent(element, includeHidden) { var style = getComputedStyle(element), excludeStaticParent = style.position === "absolute", overflowRegex = includeHidden ? /(auto|scroll|hidden)/ : /(auto|scroll)/;

    if (style.position !== "fixed") 
      for (var parent = element; (parent = parent.parentElement); ){
          style = getComputedStyle(parent);
          if (excludeStaticParent && style.position === "static") 
              continue;
          if (overflowRegex.test(style.overflow + style.overflowY + style.overflowX)) 
            return parent;
      }
    
    return window

    }

    // Throttle // Credit: https://jsfiddle.net/jonathansampson/m7G64 function throttle (callback, limit) { var wait = false; // Initially, we're not waiting return function () { // We return a throttled function if (!wait) { // If we're not waiting callback.call(); // Execute users function wait = true; // Prevent future invocations setTimeout(function () { // After a period of time wait = false; // And allow future invocations }, limit); } } }

    header{ position: sticky; top: 0;

    / not important styles / background: salmon; padding: 1em; transition: .1s; }

    header.isSticky{ / styles for when the header is in sticky mode / font-size: .8em; opacity: .5; }

    / not important styles/

    body{ height: 200vh; font:20px Arial; }

    section{ background: lightblue; padding: 2em 1em; }

    <section>Space</section> <header>Sticky Header</header>


Voici un Démonstration d'un composant React qui utilise la première technique

39voto

mattrick Points 165

J'ai trouvé une solution quelque peu similaire à la réponse de @vsync, mais elle ne nécessite pas le "hack" que vous devez ajouter à vos feuilles de style. Vous pouvez simplement modifier les limites de l'IntersectionObserver pour éviter d'avoir à déplacer l'élément lui-même en dehors du viewport :

const observer = new IntersectionObserver(callback, {
  rootMargin: '-1px 0px 0px 0px',
  threshold: [1],
});

observer.observe(element);

24voto

Scott Leonard Points 469

Si quelqu'un arrive ici via Google, un de ses ingénieurs a trouvé une solution en utilisant IntersectionObserver, des événements personnalisés et des sentinelles :

https://developers.google.com/web/updates/2017/09/sticky-headers

3voto

Il suffit d'utiliser de la vanille JS pour ça. Vous pouvez utiliser la fonction throttle de lodash pour éviter certains problèmes de performance.

const element = document.getElementById("element-id");

document.addEventListener(
  "scroll",
  _.throttle(e => {
    element.classList.toggle(
      "is-sticky",
      element.offsetTop <= window.scrollY
    );
  }, 500)
);

2voto

Turadg Points 3621

Après que Chrome ait ajouté position: sticky il s'est avéré que pas assez prêt y relégué au drapeau --enable-experimental-webkit-features . Paul Irish a déclaré en février "La fonctionnalité est dans un état de limbes bizarre atm".

J'utilisais le polyfill jusqu'à ce que ça devienne un trop gros casse-tête. Cela fonctionne bien quand c'est le cas, mais il y a des cas particuliers, comme les problèmes CORS, et cela ralentit le chargement des pages en faisant des requêtes XHR pour tous vos liens CSS et en les réparant pour la déclaration "position : sticky" que le navigateur a ignorée.

Maintenant, j'utilise ScrollToFixed que je préfère à StickyJS parce que ça ne perturbe pas ma mise en page avec un wrapper.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X