Targeting position:sticky elements that are currently in a 'stuck' state

后端 未结 5 1988
名媛妹妹
名媛妹妹 2020-12-04 17:26

position: sticky works on some mobile browsers now, so you can make a menu bar scroll with the page but then stick to the top of the viewport whenever the user scrolls past

相关标签:
5条回答
  • 2020-12-04 17:42

    A compact way for when you have an element above the position:sticky element. It sets the attribute stuck which you can match in CSS with header[stuck]:

    HTML:

    <img id="logo" ...>
    <div>
      <header style="position: sticky">
        ...
      </header>
      ...
    </div>
    

    JS:

    if (typeof IntersectionObserver !== 'function') {
      // sorry, IE https://caniuse.com/#feat=intersectionobserver
      return
    }
    
    new IntersectionObserver(
      function (entries, observer) {
        for (var _i = 0; _i < entries.length; _i++) {
          var stickyHeader = entries[_i].target.nextSibling
          stickyHeader.toggleAttribute('stuck', !entries[_i].isIntersecting)
        }
      },
      {}
    ).observe(document.getElementById('logo'))
    
    0 讨论(0)
  • 2020-12-04 17:43

    Someone on the Google Developers blog claims to have found a performative JavaScript-based solution with an IntersectionObserver.

    Relevant code bit here:

    /**
     * Sets up an intersection observer to notify when elements with the class
     * `.sticky_sentinel--top` become visible/invisible at the top of the container.
     * @param {!Element} container
     */
    function observeHeaders(container) {
      const observer = new IntersectionObserver((records, observer) => {
        for (const record of records) {
          const targetInfo = record.boundingClientRect;
          const stickyTarget = record.target.parentElement.querySelector('.sticky');
          const rootBoundsInfo = record.rootBounds;
    
          // Started sticking.
          if (targetInfo.bottom < rootBoundsInfo.top) {
            fireEvent(true, stickyTarget);
          }
    
          // Stopped sticking.
          if (targetInfo.bottom >= rootBoundsInfo.top &&
              targetInfo.bottom < rootBoundsInfo.bottom) {
           fireEvent(false, stickyTarget);
          }
        }
      }, {threshold: [0], root: container});
    
      // Add the top sentinels to each section and attach an observer.
      const sentinels = addSentinels(container, 'sticky_sentinel--top');
      sentinels.forEach(el => observer.observe(el));
    }
    

    I haven't replicated it myself, but maybe it helps someone stumbling over this question.

    0 讨论(0)
  • 2020-12-04 17:48

    There is currently no selector that is being proposed for elements that are currently 'stuck'. The Postioned Layout module where position: sticky is defined does not mention any such selector either.

    Feature requests for CSS can be posted to the www-style mailing list. I believe a :stuck pseudo-class makes more sense than a ::stuck pseudo-element, since you're looking to target the elements themselves while they are in that state. In fact, a :stuck pseudo-class was discussed some time ago; the main complication, it was found, is one that plagues just about any proposed selector that attempts to match based on a rendered or computed style: circular dependencies.

    In the case of a :stuck pseudo-class, the simplest case of circularity would occur with the following CSS:

    :stuck { position: static; /* Or anything other than sticky/fixed */ }
    :not(:stuck) { position: sticky; /* Or fixed */ }
    

    And there could be many more edge cases that would be difficult to address.

    While it's generally agreed upon that having selectors that match based on certain layout states would be nice, unfortunately major limitations exist that make these non-trivial to implement. I wouldn't hold my breath for a pure CSS solution to this problem anytime soon.

    0 讨论(0)
  • 2020-12-04 17:56

    In some cases a simple IntersectionObserver can do the trick, if the situation allows for sticking to a pixel or two outside its root container, rather than properly flush against. That way when it sits just beyond the edge, the observer fires and we're off and running.

    const observer = new IntersectionObserver( 
      ([e]) => e.target.toggleAttribute('stuck', e.intersectionRatio < 1),
      {threshold: [1]}
    );
    
    observer.observe(document.querySelector('nav'));
    

    Stick the element just out of its container with top: -2px, and then target via the stuck attribute...

    nav {
      background: magenta;
      height: 80px;
      position: sticky;
      top: -2px;
    }
    nav[stuck] {
      box-shadow: 0 0 16px black;
    }
    

    Example here: https://codepen.io/anon/pen/vqyQEK

    0 讨论(0)
  • 2020-12-04 17:57

    Not really a fan of using js hacks for styling stuff (ie getBoudingClientRect, scroll listening, resize listening), but this is how I'm currently solving the problem. This solution will have issues with pages that have minimizable/maximizable content (<details>), or nested scrolling, or really any curve balls whatsoever. That being said, it's a simple solution for when the problem is simple as well.

    let lowestKnownOffset: number = -1;
    window.addEventListener("resize", () => lowestKnownOffset = -1);
    
    const $Title = document.getElementById("Title");
    let requestedFrame: number;
    window.addEventListener("scroll", (event) => {
        if (requestedFrame) { return; }
        requestedFrame = requestAnimationFrame(() => {
            // if it's sticky to top, the offset will bottom out at its natural page offset
            if (lowestKnownOffset === -1) { lowestKnownOffset = $Title.offsetTop; }
            lowestKnownOffset = Math.min(lowestKnownOffset, $Title.offsetTop);
            // this condition assumes that $Title is the only sticky element and it sticks at top: 0px
            // if there are multiple elements, this can be updated to choose whichever one it furthest down on the page as the sticky one
            if (window.scrollY >= lowestKnownOffset) {
                $Title.classList.add("--stuck");
            } else {
                $Title.classList.remove("--stuck");
            }
            requestedFrame = undefined;
        });
    })
    
    0 讨论(0)
提交回复
热议问题