向下滚动到截面时突出显示菜单项

我知道这个问题在这个论坛上被问了一百万次,但没有一篇文章帮助我找到解决方案。

我做了一小段jquery代码,当你向下滚动到与hash-link中id相同的部分时,它突出显示了hash-link。

$(window).scroll(function() { var position = $(this).scrollTop(); $('.section').each(function() { var target = $(this).offset().top; var id = $(this).attr('id'); if (position >= target) { $('#navigation > ul > li > a').attr('href', id).addClass('active'); } }); }); 

现在的问题是它突出显示所有哈希链接,而不仅仅是该部分与之关系的哈希链接。 任何人都可以指出错误,还是我忘记了什么?

编辑:

我已经修改了我的答案,谈了一些性能和一些特殊情况。

如果您只是在寻找代码,那么底部会有一个注释片段。


原始答案

不应将.active 添加到所有链接,而应标识属性href与节的id相同的那个。

然后,您可以将.active 添加到该链接并从其余部分中删除它。

  if (position >= target) { $('#navigation > ul > li > a').removeClass('active'); $('#navigation > ul > li > a[href=#' + id + ']').addClass('active'); } 

通过上述修改,您的代码将正确突出显示相应的链接。 希望能帮助到你!


提高绩效

即使这个代码能够完成它的工作,也远非最佳。 无论如何,请记住:

我们应该忘记小的效率,大约97%的时间说:过早的优化是所有邪恶的根源。 然而,我们不应该放弃那个至关重要的3%的机会。 ( 唐纳德克努特

因此,如果在慢速设备中进行事件测试,您没有遇到性能问题,那么您可以做的最好的事情就是停止阅读并考虑项目的下一个惊人function!

基本上,有三个步骤可以改善性能:

尽可能多地进行以前的工作:

为了避免一次又一次地搜索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中具有特殊含义,因此必须对其进行转义 (感谢@Johnnie )。

此外,您可以缓存每个部分的位置(Bootstrap的Scrollspy会这样做 )。 但是,如果你这样做,你需要记住每次更改时更新它们(用户调整窗口大小,通过ajax添加新内容,扩展子部分等)。

优化事件处理程序:

想象一下,用户一个部分滚动:活动导航链接不需要更改。 但是如果你看一下上面的代码,你会发现实际上它会多次改变。 在突出显示正确的链接之前,所有之前的链接也会这样做(因为它们的相应部分也validation条件position >= target )。

一种解决方案是迭代底部到顶部的部分,第一个部分的.offset().top等于或小于$(window).scrollTop是正确的部分。 是的, 您可以依赖jQuery以DOM的顺序返回对象 (从版本1.3.2开始 )。 要从下到上迭代,只需按相反的顺序选择它们:

 var $sections = $( $(".section").get().reverse() ); $sections.each( ... ); 

double $()是必需的,因为get()返回DOM元素,而不是jQuery对象。

找到正确的部分后,应该return false以退出循环并避免检查其他部分。

最后,如果已突出显示正确的导航链接,则不应执行任何操作,因此请将其检出:

 if ( !$navigationLink.hasClass( 'active' ) ) { $navigationLinks.removeClass('active'); $navigationLink.addClass('active'); } 

尽可能少地触发事件:

防止高评级事件(滚动,resize……)使您的网站变慢或无响应的最明确方法是控制调用事件处理程序的频率:确保您不需要检查哪些链接需要突出显示每秒100次! 如果,除了链接突出显示,你添加一些奇特的视差效果,你可以跑快速介绍的麻烦。

此时,确定要阅读有关油门,去抖动和requestAnimationFrame的信息。 这篇文章是一个很好的讲座,给你一个很好的概述,其中有三个。 对于我们的情况,节流最符合我们的需求。

基本上,限制会强制执行两个函数执行之间的最小时间间隔。

我在代码片段中实现了一个节流function。 从那里你可以得到更复杂,甚至更好,使用像underscore.js或lodash这样的库(如果你不需要整个库,你总是可以从那里提取节流function)。

注意:如果你环顾四周,你会发现更简单的油门function。 要小心它们,因为它们可能会错过最后一个事件触发器(这是最重要的事件!)。

特殊情况:

我不会将这些案例包含在代码段中,以免进一步复杂化。

在下面的代码段中,当该部分到达页面的最顶部时,链接将突出显示。 如果您希望之前突出显示它们,则可以通过以下方式添加小偏移:

 if (position + offset >= target) { 

如果您有一个顶部导航栏,这是特别有用的。

如果您的上一部分太小而无法到达页面顶部,则当滚动条位于其最底部位置时,您可以高亮显示其相应的链接:

 if ( $(window).scrollTop() >= $(document).height() - $(window).height() ) { // highlight the last link 

有一些浏览器支持问题的想法。 你可以在这里和这里阅读更多相关信息。

片段和测试

最后,这里有一个注释片段。 请注意,我更改了一些变量的名称,使其更具描述性。

 // 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; } 
   
I'm section 1
I'm section 2
I'm section 3
I'm section 4
I'm section 5

对于最近尝试使用此解决方案的任何人来说,我试图让它发挥作用。 您可能需要像这样转义href:

 $('#navigation > ul > li > a[href=\\#' + id + ']'); 

现在我的浏览器不会在该文件上抛出错误。

在这一行:

  $('#navigation > ul > li > a').attr('href', id).addClass('active'); 

实际上,您正在设置每个$(’#navigation> ul> li> a’)元素的href属性,然后还将活动类添加到所有元素。 可能你需要做的是:

 $('#navigation > ul > li > a[href=#' + id + ']') 

并且只选择与href匹配的a。 合理?