根据滚动位置更改CSS——重构糟糕的代码

4

我已经编写了一个jQuery函数,根据其参考部分是否在窗口中可见,更改导航菜单项的CSS值。

$(window).scroll(function() {

    var scroll = $(window).scrollTop();

    if (scroll <= 590) {
        $("#menu-item-25 a").addClass('blue');
        $("#menu-item-26 a").removeClass('blue');
        $("#menu-item-22 a").removeClass('blue');
        $("#menu-item-23 a").removeClass('blue');
        $("#menu-item-24 a").removeClass('blue');
    }
    else if (scroll >= 591 && scroll <= 1380) {
        $("#menu-item-26 a").addClass('blue');
        $("#menu-item-25 a").removeClass('blue');
        $("#menu-item-22 a").removeClass('blue');
        $("#menu-item-23 a").removeClass('blue');
        $("#menu-item-24 a").removeClass('blue');
    }
    else if (scroll >= 1381 && scroll <= 2545) {
        $("#menu-item-22 a").addClass('blue');
        $("#menu-item-25 a").removeClass('blue');
        $("#menu-item-26 a").removeClass('blue');
        $("#menu-item-23 a").removeClass('blue');
        $("#menu-item-24 a").removeClass('blue');
    }
    else if (scroll >= 2546 && scroll <= 2969) {
        $("#menu-item-23 a").addClass('blue');
        $("#menu-item-25 a").removeClass('blue');
        $("#menu-item-26 a").removeClass('blue');
        $("#menu-item-22 a").removeClass('blue');
        $("#menu-item-24 a").removeClass('blue');
    }
    else if (scroll >= 2970) {
        $("#menu-item-24 a").addClass('blue');
        $("#menu-item-25 a").removeClass('blue');
        $("#menu-item-26 a").removeClass('blue');
        $("#menu-item-22 a").removeClass('blue');
        $("#menu-item-23 a").removeClass('blue');
    }
});

它看起来非常丑陋。有没有更好的方法来实现这个目标?


将添加和删除类作为一个函数编写? - Huey
1
你还应该考虑缓存DOM选择器,例如 var menuItem22 = $("#menu-item-22 a"),然后像这样访问 $(menuItem22).removeClass() 将防止每次操作元素时都访问DOM。 - nicholaswmin
5个回答

8
所有之前的答案都可以正常工作,因为您有多种方法通过更改CSS选择器来使其更好,但是如果您将所有这些计算都放在滚动事件中,则应阅读John Resign Post关于如何处理滚动事件,特别是以下部分:
“将处理程序附加到窗口滚动事件是非常非常糟糕的想法。根据浏览器,滚动事件可能会频繁触发,并且将代码放入滚动回调中将减慢滚动页面的任何尝试(不是一个好主意)。任何由此导致的滚动处理程序的性能下降都只会加剧整体滚动性能的下降。相反,最好使用某种形式的定时器每隔X毫秒检查一次,或者附加滚动事件并仅在延迟后运行您的代码(甚至在执行给定数量后 - 然后延迟)。”
所以,在您的情况下,我会这样做:
HTML:
<div class="menu">
    <a id="menu-item-22">Link 1</a>
    <a id="menu-item-23">Link 2</a>
    <a id="menu-item-24">Link 3</a>
    <a id="menu-item-25">Link 4</a>
    <a id="menu-item-26">Link 5</a>
</div>

Jquery:

var didScroll = false;

$(window).scroll(function() {
    didScroll = true;
});

setInterval(function() {
    if ( didScroll ) {
        didScroll = false;
        var $el;

        //Same that all the if else statements
        switch (true) {
            case (scroll >= 591 && scroll <= 1380):
                $el = $("#menu-item-26 a");
                break;
            case (scroll >= 1381 && scroll <= 2545):
                $el = $("#menu-item-22 a");
                break;
            case (scroll >= 2546 && scroll <= 2969):
                $el = $("#menu-item-23 a");
                break;
            case (scroll >= 2970):
                $el = $("#menu-item-24 a");
                break;
            default: //scroll<=590
                $el = $("#menu-item-25 a");
        }

        //Removing blue class from all links
        $('.menu a').removeClass('blue');

        //Adding blue class to the matched element
        $el.addClass('blue');
    }
}, 50);

谢谢Tom。我总觉得在附加滚动事件处理程序时会有一些不稳定。这看起来是一个很好的解决方案,我会试一下。 - Seth Johnson
假设您要查找的元素与匹配#menu-item-* a的元素不会更改,那么可以通过仅一次查看DOM(即$('foo'))并将每个引用分配给变量来进一步改进此过程。然后可以重复使用这些值,而无需每次都搜索DOM。显然,如果实际元素正在添加/删除DOM,则确实需要每次搜索DOM以获取它们。 - Makyen
这并不是很重要,但我更喜欢超时的方法:var scrollTO; $(window).scroll(function(){clearTimeout(scrollTO);scrollTO=setTimeout(...,100);}); - oriadam

1
你不应该使用ID来访问一组共同的元素,例如:
示例:
<div class="menu">
    <a id="menu-item-22 a">Link 1</a>
    <a id="menu-item-23 a">Link 2</a>
    <a id="menu-item-24 a">Link 3</a>
    <a id="menu-item-25 a">Link 4</a>
</div>

1 -.menu 中的所有 <a> 标签中移除类名。

$(".menu a").removeClass("blue");

2 - 显示您想要的内容

$("#menu-item-25 a").addClass("red");

你还应该考虑缓存DOM选择器 - var menuItem22 = $("#menu-item-22 a") 然后像这样访问它:

$(menuItem22).addClass('red')

这将防止您每次想对元素执行操作时都访问DOM。在您的情况下,这不是绝对必要的,但这是一个不错的技巧需要记住。


谢谢!我已经重构了代码,现在更加简洁。我已经缓存了选择器,并通过选择组而不是单个元素来删除蓝色类。再次感谢! - Seth Johnson
请记住,如果您在运行时动态添加了菜单项 - 例如,在缓存菜单后动态插入更多的<a>元素 - 您需要重新缓存它们。虽然我怀疑这不会是问题,但我还是提醒您注意一下。 - nicholaswmin
谢谢你提醒我! - Seth Johnson

1
这个怎么样?

http://pastebin.com/SrtE3yxS

$(function() {

var highlighted = null,         // Keep track of previously selected
    $menuItems  = $('#menu a'); // Grab all menu items once

// Which items at which points
var breakpoints = {
    '#menu-item-25': 590,
    '#menu-item-26': 1380,
    '#menu-item-22': 2545,
    '#menu-item-23': 2969,
    '#menu-item-24': 99999
};

$(window).scroll(function() {

        var scroll = $(window).scrollTop(),
        selected = '#menu-item-24';  // default

    // Loop through breakpoints to find the current one that should be selected
    for (id in breakpoints) {
        if (scroll <= breakpoints[id]) {
            selected = id;
            break;
        }
    }

    // If the right one is already highlighted, do nothing
    if (highlighted == id) {
        return;
    }

    // Remove all
    $menuItems.removeClass('blue');
    // Add it to the one you need
    $(id).addClass('blue');

});

});


忘记一件事...在将类设置为蓝色下面...highlighted = id;这样它就能记住下次高亮显示的内容了。 - Jason Byrne
或者...你可以摆脱高亮变量,然后这样写:如果($(id).hasClass('blue')) { return; } - Jason Byrne

0
你可以使用一个函数来执行相同的操作。
 function menueHandle(ident, color, type){
    for(var i = 0; i < ident.length; i++){
        var handle = $("#menu-item-"+ident[i]+" a");
        if(type == 'remove'){
            handle.removeClass(color);
        }else{
            handle.addClass(color);
        }
    }
}
$(window).scroll(function() {
var scroll = $(window).scrollTop();

if (scroll <= 590) {
    menueHandle([25], 'blue', 'add');
    menueHandle([26,26,22,23, 24], 'blue', 'remove');
}else if (scroll >= 591 && scroll <= 1380) {
    menueHandle([26], 'blue', 'add');
    menueHandle([25,26,22,23, 24], 'blue', 'remove');
}else if (scroll >= 1381 && scroll <= 2545) {
    menueHandle([22], 'blue', 'add');
    menueHandle([25,26,22,23, 24], 'blue', 'remove');
}else if (scroll >= 2546 && scroll <= 2969) {
    menueHandle([23], 'blue', 'add');
    menueHandle([25,26,22,23, 24], 'blue', 'remove');
}else if (scroll >= 2970) {
    menueHandle([24], 'blue', 'add');
    menueHandle([25,26,22,23], 'blue', 'remove');
} 

});


0

我想分享另一种方法(也是我个人喜欢的方法)

  • 使用setTimeout()处理滚动事件

  • 使用getBoundingClientRect()检测可见元素

https://codepen.io/oriadam/pen/NLMqjN

var vertical_threshold = 3; // pixels inside the element for it to be considered in view

function element_in_view(e) {
    if (e.style.display == "none") {
        // when element is hidden, getBoundingClientRect() returns all 0
        return false;
    }
    var r = e.getBoundingClientRect();
    return (
        r.top >= 0 && // vertical check 1
        r.top <=
            (innerHeight || document.documentElement.clientHeight) -
                Math.min(r.height, vertical_threshold) // vertical check 2
        /* CHECK HORIZONTAL SCROLL
                && r.left >= 0 // horizontal check 1
                && r.right <= (innerWidth || document.documentElement.clientWidth) // horizontal check 2
                */
    );
}

function onScroll() {
    console.log("onScroll");
    $("li").each(function() {
        this.classList.toggle("inview", element_in_view(this));
    });
}

$(window).scroll(function() {
    clearTimeout(window.scrollTO);
    window.scrollTO = setTimeout(onScroll, 50);
}).trigger('scroll');

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接