【问题标题】:How to replace a sticky element while scrolling?滚动时如何替换粘性元素?
【发布时间】:2020-12-24 16:28:19
【问题描述】:

上下文

我正在制作一个博客网站,我希望有一个粘性元素,当用户滚动时,它会在每个新年和每个月更新。这样标题就会显示列出的博客文章的当前月份和年份。

在编码的时候,我尝试用 HTML 来实现效果,如果不行就用 CSS,如果不行就用 JS。我认为这是一种很好的做法,因为它使用了内置功能并减少了所需的计算资源,但如果您不同意这种观点,请告诉我。

问题

理想情况下,元素的样式会在“卡住”时改变。为此,我查看了使用 IntersectionObserver 的 David Walsh's solution,但在添加多个元素时会出现故障。

我面临的主要问题是,当有多个条目时,脚本会在某个元素位于窗口的底部边框时将其检测为“固定”。

代码

这是一个sn-p。我也发了jsfiddle with the same code。

//Essentially putting David Walsh's code in a loop

document.querySelectorAll(".myElement").forEach((i) => {
const observer = new IntersectionObserver(([i]) => i.target.classList.toggle("is-pinned", i.intersectionRatio < 1),
{threshold: [1]});
observer.observe(i);
})
#parent { 
  height: 2000px; 
}

.myElement {
  position: sticky;
  top: -1px;
}

/* styles for when the header is in sticky mode. The transition times accentuate the undesired effect */
.myElement.is-pinned {
  color: red;
  transition: color 0.3s, background-color 0.3s;
  background-color: orange;
}
<div id="parent">
  <!-- Adding more than one 'hello' element. The br's are here to add vertical space and be longer than the viewport height -->
  <br><br><br><br>
  <div class="myElement">Hello!</div>
  
  <br><br><br><br>
  <div class="myElement">Hello 2!</div>
  
  <br><br><br><br>
  <div class="myElement">Hello 3!</div>
  
  <br><br><br><br>
  <div class="myElement">Hello 4!</div>
  
  <br><br><br><br>
  <div class="myElement">Hello 5!</div>
  
  <br><br><br><br>
  <div class="myElement">Hello 6!</div>
  
  <br><br><br><br>
  <div class="myElement">Hello 7!</div>
  
  <br><br><br><br>
  <div class="myElement">Hello 8!</div>
</div>

【问题讨论】:

    标签: javascript css sticky intersection-observer html-heading


    【解决方案1】:

    首先,您只需要一个 IntersectionObserver。只要您需要相同的回调和选项(在这种情况下您需要这样做),您就可以observe() 具有相同观察者的多个元素。只有您的 observer.observe(i); 需要在循环内。

    但是,如果您向上或向下跳转页面,则可以同时调用多个条目来调用您的单个观察者。所以你需要遍历所有观察到的条目。

    更重要的是,intersectionRatio 不关心元素在屏幕上的位置。元素在框的顶部和底部都超过了 100% 的可见性阈值。

    你只关心盒子顶部的元素。 IntersectionObserverEntry 对象还有一个 boundingClientRect 属性,它告诉你元素现在在哪里。您可以使用它来仅切换顶部的元素。

    所以你最终得到了这个:

    const observer = new IntersectionObserver((entries) => {
        for (let i of entries) {
            i.target.classList.toggle(
                "is-pinned", i.boundingClientRect.y < 0);
        }
    }, {threshold: [1]});
    
    document.querySelectorAll(".myElement").forEach(i => observer.observe(i));
    

    但是,这仍然会给您带来问题。在您的示例中,您正在滚动的框足够长,如果您直接从顶部跳到底部,则会出现从“框下方 0% 可见”到“框顶部可见 99%”的元素。这不会超过 100% 的阈值,因此不会为这些元素触发 IntersectionObserver 回调!这意味着他们没有得到is-pinned 类。

    您也可以简单地向同一个观察者添加另一个 0% 的阈值来捕捉这些变化:

    const observer = new IntersectionObserver((entries) => {
        for (let i of entries) {
            i.target.classList.toggle(
                "is-pinned", i.boundingClientRect.y < 0);
        }
    }, {threshold: [0, 1]});
    
    document.querySelectorAll(".myElement").forEach(i => observer.observe(i));
    

    现在,从可见变为粘性(反之亦然)的元素和从不可见变为粘性(或反之亦然)的元素都会切换它们的类。

    【讨论】:

      【解决方案2】:

      你在 JS 的最后一行犯了一个错误。将其更改为:

      document.querySelectorAll(".myElement").forEach((i) => {
        const observer = new IntersectionObserver(
        ([i]) => i.target.classList.toggle("is-pinned", i.intersectionRatio < 1), {
          threshold: [1]
        });
        observer.observe(document.querySelector(".myElement")); // Use the element instead!
      })
      

      【讨论】:

      • 这不会将新标题放在首位,也不会改变它们的样式
      • 对不起,你是对的,我的回答是错误的。也许您的代码没有任何问题,但是将它放在 iframe 中会给您这个结果。看看你是否仍然有这个问题,而不是在 iframe 中,比如:jsfiddle.net/dirkjf/zh0nd1gx/2/show
      • 可惜没有去掉窗口底部的效果
      猜你喜欢
      • 2015-05-06
      • 1970-01-01
      • 2021-05-23
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-01-20
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多