如何在滚动时替换粘性元素?
How to replace a sticky element while scrolling?
上下文
我正在制作一个博客网站,我想要一个粘性元素,随着用户滚动,该元素会在每个新的一年和每个月更新。这样 header 就会显示所列博客文章的当前月份和年份。
编码的时候,我尝试用HTML实现一个效果,不行再用CSS,不行再用JS。我认为这是一个很好的做法,因为它使用内置功能并减少了所需的计算资源,但如果您不同意这个意见,请告诉我。
问题
理想情况下,元素的样式会在 'stuck' 时更改。为此,我查看了使用 IntersectionObserver
的 David Walsh's solution,但在添加多个元素时会出现故障。
我面临的主要问题是,当有多个条目时,脚本将元素检测为 'pinned',当它位于 window.
的底部边框时
代码
这是一个片段。我也做了一个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>
您的 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!
})
首先,你只需要一个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
class.
您可以简单地向同一个观察者添加另一个 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));
现在,从可见到粘性(或反之亦然)的元素和从不可见到粘性(或反之亦然)的元素都会 class 切换。
上下文
我正在制作一个博客网站,我想要一个粘性元素,随着用户滚动,该元素会在每个新的一年和每个月更新。这样 header 就会显示所列博客文章的当前月份和年份。
编码的时候,我尝试用HTML实现一个效果,不行再用CSS,不行再用JS。我认为这是一个很好的做法,因为它使用内置功能并减少了所需的计算资源,但如果您不同意这个意见,请告诉我。
问题
理想情况下,元素的样式会在 'stuck' 时更改。为此,我查看了使用 IntersectionObserver
的 David Walsh's solution,但在添加多个元素时会出现故障。
我面临的主要问题是,当有多个条目时,脚本将元素检测为 'pinned',当它位于 window.
的底部边框时代码
这是一个片段。我也做了一个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>
您的 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!
})
首先,你只需要一个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
class.
您可以简单地向同一个观察者添加另一个 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));
现在,从可见到粘性(或反之亦然)的元素和从不可见到粘性(或反之亦然)的元素都会 class 切换。