我查看了您的代码和您的网站,看起来您正在尝试实现以下目标:
- 只有当用户滚动到标题时才会变得粘性的粘性导航
- 当用户第一次滚动并停留在那里时“飞入”一次的服务时间列表
TL;DR - 我在 http://jsfiddle.net/9o7frxp1/4/ 制作了一个包含此基本行为的 jsfiddle。
在上面的小提琴中,我建议使用 CSS 类来控制导航的粘性和飞行 div 的动画。 JavaScript 只是用来根据用户是否在滚动以及他们的滚动位置来打开和关闭这些类。
这对于分离 JS 和 CSS 的关注点非常有用,允许每种语言都做它最擅长的事情,这意味着代码更容易维护。
为了进一步解释,让我们暂时以您的<nav> 为例,这样这篇文章就不会太长了。查看我上面的 jsfiddle 链接以了解飞行 div - 我已将 cmets 添加到代码中以解释正在发生的事情,但如果它没有意义,请告诉我。
所以,我们有一个导航元素:
<nav id="nav">
Nav
</nav>
我们为 <nav> 提供了一些样式 - 一些默认样式,还有一些只有在它也有 .is-sticky 类时才会应用:
#nav {
width: 100%;
height: 40px;
}
#nav.is-sticky {
position: fixed;
top: 0;
}
在我们的 JavaScript 中,我们找到 <nav>:
var navigation = document.getElementById('nav');
现在,我们准备一个在用户滚动时触发的函数。我们还没有添加任何会监听滚动的东西——我们只是准备好了功能。
function addStickyClass() {
// Every time this function fires, we calculate the scroll position
var currentDistance = document.body.scrollTop;
// If we've scrolled 90px down, add the 'is-sticky' class to our nav.
// If not, remove the 'is-sticky' class.
if (currentDistance > 90) {
navigation.className = 'is-sticky';
} else {
navigation.className = '';
}
}
酷 - 我们几乎准备好了。最后一件事 - 让我们确保每次用户滚动时都会触发上述函数。
window.addEventListener('scroll', addStickyClass);
这意味着我们每次用户滚动时都会询问“他们是否距离窗口顶部 90 像素(或更多)?” - 如果是,导航应该是粘性的,如果不是,它不应该。
stekhn 是正确的,您还没有完全正确地实现事件侦听器,这导致您的页面行为异常。
但是,在我上面的代码示例中,我建议只需要添加一个事件侦听器,无需删除它。我还进一步建议您应该使用 JS 和 CSS 的替代组合以稍微不同的方式想象这个问题,就像我上面提到的那样。
在此答案的代码中,我稍微简化了一些事情以更清楚地解释正在发生的事情 - 您将在我发布的 jsfiddle 中看到更详细的版本。
但是,即使我的 jsfiddle 仍然是您网站上的简化版本,所以您需要对其进行调整 - 试一试,让我知道您的进展情况。
更新
TL;DR:
我实际上意识到#flyInDiv 的代码可以更简单一些,并且不需要任何布尔值或if 语句。摆弄http://jsfiddle.net/9o7frxp1/5/
的评论解释
但我仍然想在下面解释我最初实现的内容 - 我认为值得了解:
所以,如果你还记得的话,我们有一个函数会在用户每次滚动时被触发:
function addStickyClass() {
// Every time this function fires, we calculate the scroll position
var currentDistance = document.body.scrollTop;
// If we've scrolled 90px down, add the 'is-sticky' class to our nav.
// If not, remove the 'is-sticky' class.
if (currentDistance > 90) {
navigation.className = 'is-sticky';
} else {
navigation.className = '';
}
}
每次用户滚动时,该函数都会决定是否向我们的元素添加或删除.is-sticky 类。
说到这一点 - 我们希望我们的代码根据用户滚动的时间来决定其他一些事情 - 显示您的 #flyInDiv。也许我们可以将它包含在这个代码块中?
#flyInDiv 的逻辑是这样的:
- 默认情况下,当用户首次登陆页面时,该元素是不可见的,这要归功于 CSS
- 在用户第一次滚动时,该元素应该出现,因为添加了一个额外的 CSS 类
- 在用户访问您的页面的剩余时间里,该元素应保持在原来的位置。我们不再关心用户是否在滚动。
让我们用代码来看看它是如何工作的。首先,由于 CSS,默认情况下该元素是不可见的。我们有 .is-sticky 类用于使元素可见,但默认情况下该类不在元素上:
#flyInDiv {
width: 100px;
height: 100px;
/* Positioned hard right */
position: fixed;
right: 0;
/* Translated an extra 100px to the right, sending it out of view */
transform: translateX(100px);
}
#flyInDiv.is-sticky {
/* No translation, so the element should now simply be hard right */
transform: translateX(0);
}
舞台已经准备好了……我们需要一些 JavaScript!
让我们回到我们的 addStickyClass() 函数 - 但我将从这些示例中删除与 <nav> 相关的其他代码以使事情更清楚。
var flyingDiv = document.getElementById("flyInDiv");
function addStickyClass() {
flyingDiv.className = "is-sticky";
}
请记住,每次用户滚动时都会触发此事件(感谢我们不久前添加的事件侦听器),那么会发生什么?
用户第一次滚动时,元素将获得.is-sticky 类。每次用户滚动时它都会保持设置。
碰巧,这不会影响您网站的行为,我在最新的小提琴中提到了这一点。如果这个类一直设置在#flyInDiv 上实际上很好,因为它只是意味着从用户第一次滚动的那一刻起,它就一直可见。
但是,严格来说,这段代码只需要运行一次。我们只想在第一个滚动事件之后设置.is-sticky 类。我们不需要在每次用户滚动时都设置它。
对于这个例子,我们侥幸成功,因为当代码flyingDiv.className = "is-sticky"; 在每次滚动时执行时,它只是不断地覆盖类名。结果:您的 flyingDiv 始终具有 .is-sticky 类。
但是,在很多情况下,您确实想要强制执行某些肯定只会在第一次滚动之后发生的事情 - 例如,假设对于这个项目,您需要使用 appendChild() 添加一个新的,在第一次滚动后将更复杂的元素放入 DOM,而不是简单地使用 CSS 使其可见 - 我们肯定无法摆脱不断重复的代码 - 这意味着我们的元素不断被附加!
这就是你的做法——让我们的代码知道元素是否可见。这样,它就可以决定是否需要运行代码。
滚动时,它可以不断询问:
- 元素是否不可见?
- 不:这必须是第一个卷轴。让它可见!
- 是的:这不是第一个卷轴。它已经可见了。酷,什么都不做。
所以首先我们需要一个变量来评估。我们给它命名,默认设置为false。为什么?因为变量是flyingDivIsVisible(阅读“飞行的 div 是否可见?”),并且在页面上发生任何其他事情之前,正如我们已经提到的,答案应该是“不”。
重要的是,这发生在任何功能之外和之上。我们在这里设置了舞台,我们不希望这段代码在用户每次滚动时都继续运行。它只是在这里为代码提供我们已经拥有的初步知识 - 当用户访问页面时,但在他们滚动之前,#flyInDiv 是不可见的。
var flyingDivIsVisible = false;
接下来,我们在函数中添加一些东西。至关重要的是,我们添加的这些新内容位于 if 块中,并且仅在 !flyingDivIsVisible 为真时运行(或者换句话说,flyingDivIsVisible === false):
// Tell our code that the div isn't visible.
var flyingDivIsVisible = false;
function addStickyClass() {
// Only run this code if div isn't visible.
if (!flyingDivIsVisible) {
flyingDiv.className = "is-sticky";
// Tell our code that the div is visible
flyingDivIsVisible = true;
}
}
记住,我们在函数上方和外部设置了var flyingDivIsVisible = false;?所以至少在这个函数第一次运行时,flyingDivIsVisible === false 肯定会评估为 true。
但是,只在flyingDivIsVisible === false 时运行的东西?好吧,它恰好也改变了flyingDivIsVisible = true。为什么?因为运行的东西是将.is-sticky 类添加到#flyInDiv,这意味着它现在是可见的——但是我们需要通过设置flyingDivIsVisible = true 来为我们的代码提供这些知识。
因此,从现在开始,用户每再滚动一次,我们现在就会拥有flyingDivIsVisible = true,这意味着if (!flyingDivIsVisible) 块中的任何代码都将不再运行。
因此,我们刚刚实现了一些代码,它会在用户第一次滚动时运行,但此后再也不会运行。
为了确保这一点尽可能清晰,让我们看一下连续运行两次的函数并分解发生了什么。我们还假设我们不断询问代码 div 是否可见。由于我们已经找到了一种方法来给它知识,它可以不断地告诉我们。 PS,我在这个例子中使用了IIFEs——它们是自己运行的函数,而不是等待单独调用。这个例子更有意义,我们连续两次同步运行一段代码:
// Tell our code the div isn't visible
var flyingDivIsVisible = false;
(function () {
// Us: Is the div visible?
// Code: No. You told it me it isn't.
if (!flyingDivIsVisible) {
flyingDiv.className = "is-sticky";
// Tell our code that the div is visible
flyingDivIsVisible = true;
}
})();
(function () {
// Us: Is the div visible?
// Code: Yes. You told me it was last time I ran this function.
if (!flyingDivIsVisible) {
// Nothing in this block runs, because the div is visible.
flyingDiv.className = "is-sticky";
flyingDivIsVisible = true;
}
})();
顺便说一下,如果#flyInDiv 是这段代码唯一控制的东西,我们不妨在第一次滚动后删除事件监听器,这样代码就不会监听不再滚动(它不需要,对吧?它已经完成了让 div 在第一次滚动时可见的工作)。
但是碰巧的是,我们在与 <nav> 显示和隐藏相同的代码的上下文中设想了这一点,它仍然需要事件侦听器不断询问“用户是否在滚动?”一直,所以我们不想移除事件监听器。
希望一切都有意义!