在 jquery 中处理 $(window).scroll 函数的更有效方法?

发布于 2024-12-04 05:31:49 字数 828 浏览 0 评论 0原文

在下面的代码中,我检查窗口是否滚动到某个点,如果是,则更改元素以使用固定位置,以便它不会滚动到页面顶部。唯一的问题是,这似乎是高度客户端内存密集型(并且确实降低了滚动速度),因为在每个滚动像素上,我都会一遍又一遍地更新元素上的样式属性。

在尝试更新之前检查 attr 是否已经存在会产生重大影响吗?是否有完全不同且更有效的做法来获得相同的结果?

$(window).scroll(function () {
    var headerBottom = 165;
    var fcHeight = $("#pnlMainNavContainer").height();

    var ScrollTop = $(window).scrollTop();
    if (ScrollTop > headerBottom) {
        $("#HeaderContentBuffer").attr("style", "margin-top:" + (fcHeight) + "px;");
        $("#AddFieldsContainer").attr("style", "position:fixed;width:320px;top:70px;left:41px;");
    } else {
        $("#HeaderContentBuffer").attr("style", "margin-top: 0px;");
        $("#AddFieldsContainer").removeAttr("style");
    }
});

当我输入此内容时,我注意到 StackOverflow.com 使用相同类型的功能,其黄色“类似问题”和“帮助”菜单位于此页面的右侧。我想知道他们是怎么做到的。

In the code below, I'm checking to see if the window is being scrolled past a certain point and if it is, change an element to use fixed position so that it doesn't scroll off the top of the page. The only problem is that is seems to be HIGHLY client-side-memory intensive (and really bogs down the scrolling speed) because at every single scroll pixel I am updating the style attributes over and over on the element.

Would checking if the attr is already there before attempting to update it make a significant difference? Is there a completely different and more efficient practice to get the same result?

$(window).scroll(function () {
    var headerBottom = 165;
    var fcHeight = $("#pnlMainNavContainer").height();

    var ScrollTop = $(window).scrollTop();
    if (ScrollTop > headerBottom) {
        $("#HeaderContentBuffer").attr("style", "margin-top:" + (fcHeight) + "px;");
        $("#AddFieldsContainer").attr("style", "position:fixed;width:320px;top:70px;left:41px;");
    } else {
        $("#HeaderContentBuffer").attr("style", "margin-top: 0px;");
        $("#AddFieldsContainer").removeAttr("style");
    }
});

As I'm typing this, I notice that StackOverflow.com using the same type of functionality with their yellow "Similar Questions" and "Help" menus on the right hand side of this page. I wonder how they do it.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

╰沐子 2024-12-11 05:31:49

您可以使用的一种技术是在滚动事件上设置一个计时器,并且仅在滚动位置短时间内没有更改时才执行主要工作。我在调整具有相同问题的事件大小时使用该技术。您可以尝试一下合适的超时值。较短的时间以较短的滚动暂停进行更新,因此在滚动期间可能会更频繁地运行,较长的时间需要用户实际暂停所有运动一段有意义的时间。您必须尝试什么超时值最适合您的目的,并且最好在相对较慢的计算机上进行测试,因为这就是滚动滞后问题最明显的地方。

以下是如何实现这一点的总体思路:

var scrollTimer = null;
$(window).scroll(function () {
    if (scrollTimer) {
        clearTimeout(scrollTimer);   // clear any previous pending timer
    }
    scrollTimer = setTimeout(handleScroll, 500);   // set new timer
});

function handleScroll() {
    scrollTimer = null;
    var headerBottom = 165;
    var fcHeight = $("#pnlMainNavContainer").height();

    var ScrollTop = $(window).scrollTop();
    if (ScrollTop > headerBottom) {
        $("#HeaderContentBuffer").attr("style", "margin-top:" + (fcHeight) + "px;");
        $("#AddFieldsContainer").attr("style", "position:fixed;width:320px;top:70px;left:41px;");
    } else {
        $("#HeaderContentBuffer").attr("style", "margin-top: 0px;");
        $("#AddFieldsContainer").removeAttr("style");
    }
}

您还可以通过在滚动首次开始时缓存一些选择器来加速滚动功能,这样就不必每次都重新计算它们。在这里,每次创建 jQuery 对象的额外开销可能对您没有帮助。


下面是一个 jQuery 附加方法,可以为您处理滚动计时器:

(function($) {
    var uniqueCntr = 0;
    $.fn.scrolled = function (waitTime, fn) {
        if (typeof waitTime === "function") {
            fn = waitTime;
            waitTime = 500;
        }
        var tag = "scrollTimer" + uniqueCntr++;
        this.scroll(function () {
            var self = $(this);
            var timer = self.data(tag);
            if (timer) {
                clearTimeout(timer);
            }
            timer = setTimeout(function () {
                self.removeData(tag);
                fn.call(self[0]);
            }, waitTime);
            self.data(tag, timer);
        });
    }
})(jQuery);

工作演示: http://jsfiddle.net/ jfriend00/KHeZY/

然后你的代码将像这样实现:

$(window).scrolled(function() {
    var headerBottom = 165;
    var fcHeight = $("#pnlMainNavContainer").height();

    var ScrollTop = $(window).scrollTop();
    if (ScrollTop > headerBottom) {
        $("#HeaderContentBuffer").attr("style", "margin-top:" + (fcHeight) + "px;");
        $("#AddFieldsContainer").attr("style", "position:fixed;width:320px;top:70px;left:41px;");
    } else {
        $("#HeaderContentBuffer").attr("style", "margin-top: 0px;");
        $("#AddFieldsContainer").removeAttr("style");
    }
});

One technique you can use is to set a timer on the scroll event and only do the main work when the scroll position hasn't changed for a short period of time. I use that technique on resize events which have the same issue. You can experiment with what timeout value seems to work right. A shorter time updates with shorter pauses in scrolling and thus may run more often during the scroll, a longer time requires the user to actually pause all motion for a meaningful time. You will have to experiment with what timeout value works best for your purposes and it would be best to test on a relatively slow computer since that's where the issue of scroll lag would be most pronounced.

Here's the general idea how this could be implemented:

var scrollTimer = null;
$(window).scroll(function () {
    if (scrollTimer) {
        clearTimeout(scrollTimer);   // clear any previous pending timer
    }
    scrollTimer = setTimeout(handleScroll, 500);   // set new timer
});

function handleScroll() {
    scrollTimer = null;
    var headerBottom = 165;
    var fcHeight = $("#pnlMainNavContainer").height();

    var ScrollTop = $(window).scrollTop();
    if (ScrollTop > headerBottom) {
        $("#HeaderContentBuffer").attr("style", "margin-top:" + (fcHeight) + "px;");
        $("#AddFieldsContainer").attr("style", "position:fixed;width:320px;top:70px;left:41px;");
    } else {
        $("#HeaderContentBuffer").attr("style", "margin-top: 0px;");
        $("#AddFieldsContainer").removeAttr("style");
    }
}

You may also be able to speed up your scroll function by caching some of the selectors when the scrolling first starts so they don't have to be recalculated each time. This is one place where the extra overhead of creating a jQuery object each time might not be helping you.


Here's a jQuery add-on method that handles the scrolling timer for you:

(function($) {
    var uniqueCntr = 0;
    $.fn.scrolled = function (waitTime, fn) {
        if (typeof waitTime === "function") {
            fn = waitTime;
            waitTime = 500;
        }
        var tag = "scrollTimer" + uniqueCntr++;
        this.scroll(function () {
            var self = $(this);
            var timer = self.data(tag);
            if (timer) {
                clearTimeout(timer);
            }
            timer = setTimeout(function () {
                self.removeData(tag);
                fn.call(self[0]);
            }, waitTime);
            self.data(tag, timer);
        });
    }
})(jQuery);

Working demo: http://jsfiddle.net/jfriend00/KHeZY/

Your code would then be implemented like this:

$(window).scrolled(function() {
    var headerBottom = 165;
    var fcHeight = $("#pnlMainNavContainer").height();

    var ScrollTop = $(window).scrollTop();
    if (ScrollTop > headerBottom) {
        $("#HeaderContentBuffer").attr("style", "margin-top:" + (fcHeight) + "px;");
        $("#AddFieldsContainer").attr("style", "position:fixed;width:320px;top:70px;left:41px;");
    } else {
        $("#HeaderContentBuffer").attr("style", "margin-top: 0px;");
        $("#AddFieldsContainer").removeAttr("style");
    }
});
长发绾君心 2024-12-11 05:31:49

我发现这种方法对于 $(window).scroll() 来说更有效,

var userScrolled = false;

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

setInterval(function() {
  if (userScrolled) {

    //Do stuff


    userScrolled = false;
  }
}, 50);

请查看 John Resig 关于此主题的帖子

一个更高效的解决方案是设置一个更长的间隔来检测您是否
靠近页面底部或顶部。这样,您甚至不必使用 $(window).scroll()

I have found this method to be much more efficeint for $(window).scroll()

var userScrolled = false;

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

setInterval(function() {
  if (userScrolled) {

    //Do stuff


    userScrolled = false;
  }
}, 50);

Check out John Resig's post on this topic.

An even more performant solution would be to set a a longer interval that detects if you are
close to the bottom or top of the page. That way, you wouldn't even have to use $(window).scroll()

绳情 2024-12-11 05:31:49

使您的功能更加高效。

只需在删除/添加样式之前检查样式属性是否存在/不存在即可。

$(window).scroll(function () {
    var headerBottom = 165;
    var fcHeight = $("#pnlMainNavContainer").height();

    var ScrollTop = $(window).scrollTop();
    if (ScrollTop > headerBottom) {
        if (!$("#AddFieldsContainer").attr("style")) {
            $("#HeaderContentBuffer").attr("style", "margin-top:" + (fcHeight) + "px;");
            $("#AddFieldsContainer").attr("style", "position:fixed;width:320px;top:70px;left:41px;");
        }
    } else {
        if ($("#AddFieldsContainer").attr("style")) {
            $("#HeaderContentBuffer").attr("style", "margin-top: 0px;");
            $("#AddFieldsContainer").removeAttr("style");
        }
    }
});

Making your function a little more efficient.

Just check if the style attribute is present/absent before removing/adding the styles.

$(window).scroll(function () {
    var headerBottom = 165;
    var fcHeight = $("#pnlMainNavContainer").height();

    var ScrollTop = $(window).scrollTop();
    if (ScrollTop > headerBottom) {
        if (!$("#AddFieldsContainer").attr("style")) {
            $("#HeaderContentBuffer").attr("style", "margin-top:" + (fcHeight) + "px;");
            $("#AddFieldsContainer").attr("style", "position:fixed;width:320px;top:70px;left:41px;");
        }
    } else {
        if ($("#AddFieldsContainer").attr("style")) {
            $("#HeaderContentBuffer").attr("style", "margin-top: 0px;");
            $("#AddFieldsContainer").removeAttr("style");
        }
    }
});
寒尘 2024-12-11 05:31:49

在这里设置一些逻辑。实际上,您需要在向上和向下设置一次 atts。所以:

var checker = true;
$(window).scroll(function () {

    .......

    if (ScrollTop > headerBottom && checker == true) {
        $("#HeaderContentBuffer").attr("style", "margin-top:" + (fcHeight) + "px;");
        $("#AddFieldsContainer").attr("style", "position:fixed;width:320px;top:70px;left:41px;");
        checker == false;
    } else if (ScrollTop < headerBottom && checker == false) {
        $("#HeaderContentBuffer").attr("style", "margin-top: 0px;");
        $("#AddFieldsContainer").removeAttr("style");
        checker == true;
    }   
});

Set some logic here. You actually need to set atts once on up, and once on down. So:

var checker = true;
$(window).scroll(function () {

    .......

    if (ScrollTop > headerBottom && checker == true) {
        $("#HeaderContentBuffer").attr("style", "margin-top:" + (fcHeight) + "px;");
        $("#AddFieldsContainer").attr("style", "position:fixed;width:320px;top:70px;left:41px;");
        checker == false;
    } else if (ScrollTop < headerBottom && checker == false) {
        $("#HeaderContentBuffer").attr("style", "margin-top: 0px;");
        $("#AddFieldsContainer").removeAttr("style");
        checker == true;
    }   
});
陌伤浅笑 2024-12-11 05:31:49

以下答案不使用 jQuery,但使用现代浏览器功能实现了相同的结果。 OP还询问是否有不同且更有效的方法。以下方法比 jQuery 甚至使用 onScroll 事件侦听器的纯 JavaScript 解决方案性能更高。

我们将使用两个伟大的东西的组合:

  • position: Sticky' CSS 属性
  • IntersectionObserver API

来实现 header 变成的效果卡住了,我们可以使用 position: Sticky; 的惊人组合。顶部:0px; CSS 属性。这将允许元素随页面滚动并在到达页面顶部时卡住(就像固定)。

要更改卡住元素或任何其他元素的样式,我们可以使用 IntersectionObserver API - 它允许我们观察两个元素交集的变化。

为了达到我们想要的效果,我们将添加一个 sentinel 元素,该元素将在 header 元素到达顶部时充当指示器。换句话说:

  • 当哨兵不与视口相交时,我们的 header 位于顶部
  • 当哨兵与视口相交时,我们的 header 不在

顶部2个条件下,我们可以将任何必要的样式应用于header或其他元素。

const sentinelEl = document.getElementById('sentinel')
const headerEl = document.getElementById('header')
const stuckClass = "stuck"

const handler = (entries) => {
  if (headerEl) {
    if (!entries[0].isIntersecting) {
      headerEl.classList.add(stuckClass)
    } else {
      headerEl.classList.remove(stuckClass)
    }
  }
}

const observer = new window.IntersectionObserver(handler)
observer.observe(sentinelEl)
html,
body {
  font-family: Arial;
  padding: 0;
  margin: 0;
}

.topContent,
.pageContent,
#header {
  padding: 10px;
}

#header {
  position: sticky;
  top: 0px;
  transition: all 0.2s linear;
}

#header.stuck {
  background-color: red;
  color: white;
  
}

.topContent {
  height: 50px;
  background-color: gray;
}

.pageContent {
  height: 600px;
  background-color: lightgray;
}
<div class="topContent">
  Content above our sticky element
</div>
<div id="sentinel"></div>
<header id="header">
  Sticky header
</header>
<div class="pageContent">
  The rest of the page
</div>

The following answer does not use jQuery, but achieves the same result using modern browser features. The OP also asked if there is a different and more efficient method. The following method is much more performant than jQuery or even a plain JavaScript solution using an onScroll event listener.

We'll use a combo of 2 great things:

  • the position: sticky' CSS property
  • the IntersectionObserver API

To achieve the effect of the header becoming stuck we can use the amazing combo of position: sticky; top: 0px; CSS properties. This will allow an element to scroll with the page and become stuck (as if it's fixed) when it reaches the top of the page.

To change the styles of the stuck element, or any other element, we can use the IntersectionObserver API - which allows us to observe changes in the intersection of 2 elements.

To achieve the effect we want, we'll add a sentinel element which will serve as an indicator when the header element reaches the top. In other words:

  • when the sentinel is not intersecting the viewport, our header is at the top
  • when the sentinel is intersecting the viewport, our header is not at the top

With these 2 conditions, we can apply any necessary styles to the header or other elements.

const sentinelEl = document.getElementById('sentinel')
const headerEl = document.getElementById('header')
const stuckClass = "stuck"

const handler = (entries) => {
  if (headerEl) {
    if (!entries[0].isIntersecting) {
      headerEl.classList.add(stuckClass)
    } else {
      headerEl.classList.remove(stuckClass)
    }
  }
}

const observer = new window.IntersectionObserver(handler)
observer.observe(sentinelEl)
html,
body {
  font-family: Arial;
  padding: 0;
  margin: 0;
}

.topContent,
.pageContent,
#header {
  padding: 10px;
}

#header {
  position: sticky;
  top: 0px;
  transition: all 0.2s linear;
}

#header.stuck {
  background-color: red;
  color: white;
  
}

.topContent {
  height: 50px;
  background-color: gray;
}

.pageContent {
  height: 600px;
  background-color: lightgray;
}
<div class="topContent">
  Content above our sticky element
</div>
<div id="sentinel"></div>
<header id="header">
  Sticky header
</header>
<div class="pageContent">
  The rest of the page
</div>

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文