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

时间:2015-09-04 10:37:12

标签: javascript jquery hash hyperlink sections

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

我做了一小段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');
        }
    });
});

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

5 个答案:

答案 0 :(得分:30)

编辑:

我修改了我的答案,谈了一些关于表现和某些特定情况的内容。

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

原始答案

您应该识别属性 href 与“&#; ID

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

.active

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

提高绩效

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

  

我们应该忘记效率低,大约97%的时间说:   过早优化是万恶之源。但我们不应该通过   在关键的3%中我们的机会。 ( Donald Knuth

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

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

尽可能多地完成以前的工作:

为了避免一次又一次地搜索DOM(每次触发事件),您可以事先缓存jQuery对象(例如在 if (position >= target) { $('#navigation > ul > li > a').removeClass('active'); $('#navigation > ul > li > a[href=#' + id + ']').addClass('active'); } 上):

document.ready

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

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

请注意锚点选择器中的两个反斜杠:哈希' ' CSS中有特殊含义it must be escaped(感谢@Johnnie)。

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

优化事件处理程序:

想象一下,用户正在里面滚动一个部分:活动导航链接不需要更改。但是如果你看一下上面的代码,你会发现它实际上会改变几次。在突出显示正确的链接之前,所有以前的链接也会这样做(因为它们的相应部分也会验证条件var sectionIdTonavigationLink = {}; $sections.each( function(){ sectionIdTonavigationLink[ $(this).attr('id') ] = $('#navigation > ul > li > a[href=\\#' + $(this).attr('id') + ']'); }); )。

一种解决方案是迭代底部到顶部的部分,position >= target等于或小于.offset().top的第一部分是正确的部分。是的,you can rely on jQuery returning the objects in the order of the DOM(从version 1.3.2开始)。要从下到上迭代,只需按相反的顺序选择它们:

$(window).scrollTop

var $sections = $( $(".section").get().reverse() ); $sections.each( ... ); 是必要的,因为$()返回DOM元素,而不是jQuery对象。

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

最后,如果已经突出显示正确的导航链接,您就不应该做任何事情,所以请查看:

return false

尽可能少地触发事件:

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

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

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

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

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

特殊情况:

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

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

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

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

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

if (position + offset >= target) {

有一些浏览器支持问题。您可以详细了解herehere

摘录和测试

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



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 );
&#13;
#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;
}
&#13;
&#13;
&#13;

如果您感兴趣,this fiddle会测试我们所讨论的不同改进。

快乐的编码!

答案 1 :(得分:4)

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

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

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

答案 2 :(得分:1)

我已经采用了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));

答案 3 :(得分:0)

在这一行:

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

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

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

并且仅选择与href匹配的a。有意义吗?

答案 4 :(得分:0)

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();
});
相关问题