EDIT:
我修改了我的答案,谈论一些性能和一些特殊情况。
如果您在这里只是寻找代码,底部有一个带注释的片段。
原答案
而不是添加.active
class对于所有链接,您应该确定哪个属性href与该部分的相同id.
然后你可以添加.active
class到该链接并将其从其余链接中删除。
if (position >= target) {
$('#navigation > ul > li > a').removeClass('active');
$('#navigation > ul > li > a[href=#' + id + ']').addClass('active');
}
通过上述修改,您的代码将正确突出显示相应的链接。希望能帮助到你!
提高绩效
即使这段代码能够完成其工作,也远非最佳。无论如何,请记住:
我们应该忘记小效率,大约 97% 的情况下:
过早的优化是万恶之源。然而我们不应该通过
在那关键的 3% 中增加我们的机会。 (唐纳德·高德纳)
因此,如果在慢速设备中进行事件测试,您没有遇到性能问题,那么您能做的最好的事情就是停止阅读并考虑您的项目的下一个令人惊叹的功能!
基本上可以通过三个步骤来提高性能:
尽可能多地进行前期工作:
为了避免一次又一次地搜索 DOM(每次触发事件时),您可以预先缓存 jQuery 对象(例如,在document.ready
):
var $navigationLinks = $('#navigation > ul > li > a');
var $sections = $(".section");
然后,您可以将每个部分映射到相应的导航链接:
var sectionIdTonavigationLink = {};
$sections.each( function(){
sectionIdTonavigationLink[ $(this).attr('id') ] = $('#navigation > ul > li > a[href=\\#' + $(this).attr('id') + ']');
});
注意锚选择器中的两个反斜杠:散列'#' 在 CSS 中有特殊含义,所以必须逃脱 https://api.jquery.com/category/selectors/(谢谢@Johnnie https://stackoverflow.com/a/46554013/5247200).
另外,您可以缓存每个部分的位置(Bootstrap 的滚动间谍 http://v4-alpha.getbootstrap.com/components/scrollspy/可以)。但是,如果您这样做,您需要记住每次更改时更新它们(用户调整窗口大小、通过 ajax 添加新内容、展开小节等)。
优化事件处理程序:
想象一下用户正在滚动inside一节:活动导航链接不需要更改。但如果你看一下上面的代码,你会发现它实际上改变了好几次。在突出显示正确的链接之前,所有先前的链接也会执行此操作(因为它们相应的部分也验证了条件position >= target
).
一种解决方案是从底部到顶部迭代各个部分,第一个部分的.offset().top
等于或小于$(window).scrollTop
是正确的。是的,你可以依靠 jQuery 按 DOM 的顺序返回对象 https://stackoverflow.com/a/25165306/5247200 (since 版本1.3.2 http://blog.jquery.com/2009/02/20/jquery-1-3-2-released/)。要从下到上迭代,只需按相反顺序选择它们:
var $sections = $( $(".section").get().reverse() );
$sections.each( ... );
双$()
是必要的,因为get()
返回 DOM 元素,而不是 jQuery 对象。
一旦找到正确的部分,您应该return false
退出循环并避免检查更多部分。
最后,如果正确的导航链接已经突出显示,您不应该执行任何操作,因此请检查一下:
if ( !$navigationLink.hasClass( 'active' ) ) {
$navigationLinks.removeClass('active');
$navigationLink.addClass('active');
}
尽可能少地触发事件:
防止高评级事件(滚动、调整大小...)使您的网站缓慢或无响应的最明确方法是控制事件处理程序的调用频率:确保您不需要检查需要突出显示哪个链接每秒100次!如果,除了链接突出显示之外,您添加一些奇特的视差效果,您可以快速解决介绍麻烦。
此时,您肯定想了解有关throttle、debounce 和requestAnimationFrame 的内容。本文 https://css-tricks.com/debouncing-throttling-explained-examples/这是一个很好的讲座,给你一个关于其中三个的很好的概述。对于我们的情况,节流最适合我们的需求。
基本上,限制会强制执行两个函数执行之间的最小时间间隔。
我在代码片段中实现了节流函数。从那里你可以变得更复杂,甚至更好,使用像这样的库下划线.js http://underscorejs.org/ or lodash https://lodash.com/(如果您不需要整个库,您可以随时从那里提取节流函数)。
注意:如果你环顾四周,你会发现更多简单的节流功能。小心他们,因为他们可能会错过最后一个事件触发器(这是最重要的一个!)。
特殊情况:
我不会将这些案例包含在代码片段中,以免使其进一步复杂化。
在下面的代码片段中,当该部分到达页面的最顶部时,链接将突出显示。如果你想让它们之前突出显示,你可以通过这种方式添加一个小的偏移量:
if (position + offset >= target) {
当您有顶部导航栏时,这特别有用。
如果您的最后一个部分太小而无法到达页面顶部,您可以在滚动条位于最底部位置时突出显示其相应的链接:
if ( $(window).scrollTop() >= $(document).height() - $(window).height() ) {
// highlight the last link
有一些浏览器支持问题的想法。您可以阅读更多相关内容here https://stackoverflow.com/q/3898130/5247200 and here https://stackoverflow.com/q/9439725/5247200.
片段和测试
最后,这里有一个评论片段。请注意,我更改了一些变量的名称,以使它们更具描述性。
// cache the navigation links
var $navigationLinks = $('#navigation > ul > li > a');
// cache (in reversed order) the sections
var $sections = $($(".section").get().reverse());
// map each section id to their corresponding navigation link
var sectionIdTonavigationLink = {};
$sections.each(function() {
var id = $(this).attr('id');
sectionIdTonavigationLink[id] = $('#navigation > ul > li > a[href=\\#' + id + ']');
});
// throttle function, enforces a minimum time interval
function throttle(fn, interval) {
var lastCall, timeoutId;
return function () {
var now = new Date().getTime();
if (lastCall && now < (lastCall + interval) ) {
// if we are inside the interval we wait
clearTimeout(timeoutId);
timeoutId = setTimeout(function () {
lastCall = now;
fn.call();
}, interval - (now - lastCall) );
} else {
// otherwise, we directly call the function
lastCall = now;
fn.call();
}
};
}
function highlightNavigation() {
// get the current vertical position of the scroll bar
var scrollPosition = $(window).scrollTop();
// iterate the sections
$sections.each(function() {
var currentSection = $(this);
// get the position of the section
var sectionTop = currentSection.offset().top;
// if the user has scrolled over the top of the section
if (scrollPosition >= sectionTop) {
// get the section id
var id = currentSection.attr('id');
// get the corresponding navigation link
var $navigationLink = sectionIdTonavigationLink[id];
// if the link is not active
if (!$navigationLink.hasClass('active')) {
// remove .active class from all the links
$navigationLinks.removeClass('active');
// add .active class to the current link
$navigationLink.addClass('active');
}
// we have found our section, so we return false to exit the each loop
return false;
}
});
}
$(window).scroll( throttle(highlightNavigation,100) );
// if you don't want to throttle the function use this instead:
// $(window).scroll( highlightNavigation );
#navigation {
position: fixed;
}
#sections {
position: absolute;
left: 150px;
}
.section {
height: 200px;
margin: 10px;
padding: 10px;
border: 1px dashed black;
}
#section5 {
height: 1000px;
}
.active {
background: red;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="navigation">
<ul>
<li><a href="#section1">Section 1</a></li>
<li><a href="#section2">Section 2</a></li>
<li><a href="#section3">Section 3</a></li>
<li><a href="#section4">Section 4</a></li>
<li><a href="#section5">Section 5</a></li>
</ul>
</div>
<div id="sections">
<div id="section1" class="section">
I'm section 1
</div>
<div id="section2" class="section">
I'm section 2
</div>
<div id="section3" class="section">
I'm section 3
</div>
<div id="section4" class="section">
I'm section 4
</div>
<div id="section5" class="section">
I'm section 5
</div>
</div>
如果你有兴趣,这把小提琴 https://jsfiddle.net/upqwhou2/测试我们讨论过的不同改进。
快乐编码!