Javascript 向下滚动到部分时突出显示菜单项

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/32395988/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-23 08:00:01  来源:igfitidea点击:

Highlight Menu Item when Scrolling Down to Section

javascriptjqueryhashhyperlinksections

提问by Jens Kvist

I know this question have been asked a million times on this forum, but none of the articles helped me reach a solution.

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

I made a little piece of jquery code that highlights the hash-link when you scroll down to the section with the same id as in the hash-link.

我制作了一小段 jquery 代码,当您向下滚动到与哈希链接中​​具有相同 ID 的部分时,该代码会突出显示哈希链接。

$(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');
        }
    });
});

The problem now is that it highlights all of the hash-links instead of just the one that the section has a relation to. Can anyone point out the mistake, or is it something that I forgot?

现在的问题是它突出显示了所有哈希链接,而不仅仅是与该部分相关的哈希链接。谁能指出错误,还是我忘记了?

回答by David

EDIT:

编辑:

I have modified my answer to talk a little about performance and some particular cases.

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

If you are here just looking for code, there is a commented snippet at the bottom.

如果您在这里只是在寻找代码,那么底部有一个带注释的代码段。



Original answer

原答案

Instead of adding the .activeclassto all the links, you should identify the one which attribute hrefis the same as the section's id.

您应该确定属性href与该部分的id相同的那个,而不是将.active添加到所有链接。

Then you can add the .activeclassto that link and remove it from the rest.

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

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

With the above modification your code will correctly highlight the corresponding link. Hope it helps!

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



Improving performance

提高性能

Even when this code will do its job, is far from being optimal. Anyway, remember:

即使此代码可以完成其工作,也远非最佳。无论如何,请记住:

We should forget about small efficiencies, say about 97% of the time: premature optimization is the root of all evil. Yet we should not pass up our opportunities in that critical 3%. (Donald Knuth)

我们应该忘记小效率,比如大约 97% 的时间:过早的优化是万恶之源。然而,我们不应该错过关键的 3% 的机会。(唐纳德·克努斯

So if, event testing in a slow device, you experience no performance issues, the best you can do is to stop reading and to think about the next amazing feature for your project!

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

There are, basically, three steps to improve the performance:

基本上,有三个步骤可以提高性能:

Make as much previous work as possible:

尽可能多地做以前的工作:

In order to avoid searching the DOM once and again (each time the event is triggered), you can cache your jQuery objects beforehand (e.g. on document.ready):

为了避免一次又一次地搜索 DOM(每次触发事件时),您可以预先缓存您的 jQuery 对象(例如 on document.ready):

var $navigationLinks = $('#navigation > ul > li > a');
var $sections = $(".section"); 

Then, you can map each section to the corresponding navigation link:

然后,您可以将每个部分映射到相应的导航链接:

var sectionIdTonavigationLink = {};
$sections.each( function(){
    sectionIdTonavigationLink[ $(this).attr('id') ] = $('#navigation > ul > li > a[href=\#' + $(this).attr('id') + ']');
});

Note the two backslashes in the anchor selector: the hash '#' has a special meaning in CSS so it must be escaped(thanks @Johnnie).

请注意锚选择器中的两个反斜杠:散列“ #”在 CSS 中具有特殊含义,因此必须对其进行转义(感谢@Johnnie)。

Also, you could cache the position of each section (Bootstrap's Scrollspydoes it). But, if you do it, you need to remember to update them every time they change (the user resizes the window, new content is added via ajax, a subsection is expanded, etc).

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

Optimize the event handler:

优化事件处理程序:

Imagine that the user is scrolling insideone section: the active navigation link doesn't need to change. But if you look at the code above you will see that actually it changes several times. Before the correct link get highlighted, all the previous links will do it as well (because their corresponding sections also validate the condition position >= target).

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

One solution is to iterate the sections for the bottom to the top, the first one whose .offset().topis equal or smaller than $(window).scrollTopis the correct one. And yes, you can rely on jQuery returning the objects in the order of the DOM(since version 1.3.2). To iterate from bottom to top just select them in inverse order:

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

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

The double $()is necessary because get()returns DOM elements, not jQuery objects.

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

Once you have found the correct section, you should return falseto exit the loop and avoid to check further sections.

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

Finally, you shouldn't do anything if the correct navigation link is already highlighted, so check it out:

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

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

Trigger the event as less as possible:

尽可能少地触发事件:

The most definitive way to prevent high-rated events (scroll, resize...) from making your site slow or unresponsive is to control how often the event handler is called: sure you don't need to check which link needs to be highlighted 100 times per second! If, besides the link highlighting, you add some fancy parallax effect you can ran fast intro troubles.

防止高评分事件(滚动、调整大小...)使您的网站变慢或无响应的最明确的方法是控制调用事件处理程序的频率:确保您不需要检查哪个链接需要突出显示每秒100次!如果除了链接突出显示之外,您还添加了一些奇特的视差效果,那么您可以快速运行介绍。

At this point, sure you want to read about throttle, debounce and requestAnimationFrame. This articleis a nice lecture and give you a very good overview about three of them. For our case, throttling fits best our needs.

在这一点上,确定您想阅读有关油门、去抖动和 requestAnimationFrame 的内容。这篇文章是一个很好的讲座,并为您提供了关于其中三个的很好的概述。对于我们的情况,节流最适合我们的需求。

Basically, throttling enforces a minimum time interval between two function executions.

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

I have implemented a throttle function in the snippet. From there you can get more sophisticated, or even better, use a library like underscore.jsor lodash(if you don't need the whole library you can always extract from there the throttle function).

我在代码片段中实现了一个油门功能。从那里你可以变得更复杂,甚至更好,使用像underscore.jslodash这样的库(如果你不需要整个库,你总是可以从那里提取油门功能)。

Note: if you look around, you will find more simple throttle functions. Beware of them because they can miss the last event trigger (and that is the most important one!).

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

Particular cases:

特殊情况:

I will not include these cases in the snippet, to not complicate it any further.

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

In the snippet below, the links will get highlighted when the section reaches the very top of the page. If you want them highlighted before, you can add a small offset in this way:

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

if (position + offset >= target) {

This is particullary useful when you have a top navigation bar.

当您有顶部导航栏时,这特别有用。

And if your last section is too small to reach the top of the page, you can hightlight its corresponding link when the scrollbar is in its bottom-most position:

如果您的最后一部分太小而无法到达页面顶部,则可以在滚动条位于最底部位置时突出显示其对应的链接:

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

There are some browser support issues thought. You can read more about it hereand here.

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

Snippet and test

片段和测试

Finally, here you have a commented snippet. Please note that I have changed the name of some variables to make them more descriptive.

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

// 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>

And in case you are interested, this fiddletests the different improvements we have talked about.

如果您有兴趣,这个小提琴测试了我们讨论过的不同改进。

Happy coding!

快乐编码!

回答by Johnnie

For anyone trying to use this solution more recently, I hit a snag trying to get it to work. You may need to escape the href like so:

对于最近尝试使用此解决方案的任何人,我在尝试使其工作时遇到了障碍。您可能需要像这样转义 href :

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

And now my browser doesn't throw an error on that piece.

现在我的浏览器不会在那个片段上抛出错误。

回答by user1020495

I've taken David's excellent code and removed all jQuery dependencies from it, in case anyone's interested:

我已经采用了 David 的优秀代码并从中删除了所有 jQuery 依赖项,以防有人感兴趣:

// cache the navigation links 
var $navigationLinks = document.querySelectorAll('nav > ul > li > a');
// cache (in reversed order) the sections
var $sections = document.getElementsByTagName('section');

// map each section id to their corresponding navigation link
var sectionIdTonavigationLink = {};
for (var i = $sections.length-1; i >= 0; i--) {
 var id = $sections[i].id;
 sectionIdTonavigationLink[id] = document.querySelectorAll('nav > ul > li > a[href=\#' + id + ']') || null;
}

// 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 getOffset( el ) {
 var _x = 0;
 var _y = 0;
 while( el && !isNaN( el.offsetLeft ) && !isNaN( el.offsetTop ) ) {
  _x += el.offsetLeft - el.scrollLeft;
  _y += el.offsetTop - el.scrollTop;
  el = el.offsetParent;
 }
 return { top: _y, left: _x };
}

function highlightNavigation() {
 // get the current vertical position of the scroll bar
 var scrollPosition = window.pageYOffset || document.documentElement.scrollTop;

 // iterate the sections
 for (var i = $sections.length-1; i >= 0; i--) {
  var currentSection = $sections[i];
  // get the position of the section
  var sectionTop = getOffset(currentSection).top;

    // if the user has scrolled over the top of the section  
  if (scrollPosition >= sectionTop - 250) {
   // get the section id
   var id = currentSection.id;
   // get the corresponding navigation link
   var $navigationLink = sectionIdTonavigationLink[id];
   // if the link is not active
   if (typeof $navigationLink[0] !== 'undefined') {
    if (!$navigationLink[0].classList.contains('active')) {
     // remove .active class from all the links
     for (i = 0; i < $navigationLinks.length; i++) {
      $navigationLinks[i].className = $navigationLinks[i].className.replace(/ active/, '');
     }
     // add .active class to the current link
     $navigationLink[0].className += (' active');
    }
   } else {
     // remove .active class from all the links
     for (i = 0; i < $navigationLinks.length; i++) {
      $navigationLinks[i].className = $navigationLinks[i].className.replace(/ active/, '');
     }
   } 
   // we have found our section, so we return false to exit the each loop
   return false;
  }
 }
}

window.addEventListener('scroll',throttle(highlightNavigation,150));

回答by Sean Pelser

function navHighlight() {
    var scrollTop = $(document).scrollTop();

    $("section").each(function () {
        var xPos = $(this).position();
        var sectionPos = xPos.top;
        var sectionHeight = $(this).height();
        var overall = scrollTop + sectionHeight;

        if ((scrollTop + 20) >= sectionPos && scrollTop < overall) {
            $(this).addClass("SectionActive");
            $(this).prevAll().removeClass("SectionActive");
        }

        else if (scrollTop <= overall) {
            $(this).removeClass("SectionActive");
        }

        var xIndex = $(".SectionActive").index();
        var accIndex = xIndex + 1;

        $("nav li:nth-child(" + accIndex + ")").addClass("navActivePage").siblings().removeClass("navActivePage");
    });
}


.navActivePage {
    color: #fdc166;
}


$(document).scroll(function () {
    navHighlight();
});

回答by duatis

In this line:

在这一行:

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

You are actually setting the href attribute of every $('#navigation > ul > li > a') element, and then adding the active class also to all of them. May be what you need to do is something like:

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

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

And select only the a which href match the id. Make sense?

并仅选择与 id 匹配的 href 。有道理?