在下面的代码中,我正在检查窗口是否滚动过某个点,如果是,则更改元素使用固定位置,以便它不会从页面顶部滚动。唯一的问题是,这似乎是高度客户端内存密集(并真正陷入了滚动速度),因为在每一个滚动像素我更新的样式属性一遍又一遍的元素。
在尝试更新之前检查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使用相同类型的功能,在这个页面的右手边有黄色的"类似问题"one_answers"帮助"菜单。我想知道他们是怎么做到的。
您可以使用的一种技术是在滚动事件上设置计时器,并且仅在滚动位置在短时间内没有更改时才执行主要工作。我将这种技术用于具有相同问题的调整大小事件。您可以尝试一下什么超时值似乎是正确的。较短的时间更新与较短的滚动暂停,因此可能在滚动期间更频繁地运行,较长的时间要求用户实际上暂停所有运动一段有意义的时间。您将不得不尝试什么超时值最适合您的目的,最好在相对较慢的计算机上进行测试,因为这是滚动延迟问题最明显的地方。
这是如何实现的总体思路:
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");
}
});
我发现这种方法对$(window).scroll()
更有效
var userScrolled = false;
$(window).scroll(function() {
userScrolled = true;
});
setInterval(function() {
if (userScrolled) {
//Do stuff
userScrolled = false;
}
}, 50);
查看John Resig关于这个主题的帖子。
一个更高效的解决方案是设置一个更长的间隔来检测您是否存在靠近页面底部或顶部的。这样,你甚至不需要使用$(window).scroll()
让你的函数更有效率。
在删除/添加样式之前检查style属性是否存在/不存在。
$(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");
}
}
});
在这里设置一些逻辑。实际上你需要设置一次向上,一次向下。所以:
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;
}
});
下面的答案不使用jQuery,而是使用现代浏览器功能实现相同的结果。OP还询问是否有一种不同的更有效的方法。下面的方法比jQuery更高效,甚至比使用onScroll
事件监听器的纯JavaScript解决方案更高效。
我们将使用两个伟大的东西的组合:
-
position: sticky'
CSS属性
IntersectionObserver
API为了达到header
卡住的效果,我们可以使用position: sticky; top: 0px;
CSS属性的惊人组合。这将允许一个元素随着页面滚动,并在到达页面顶部时卡住(就好像它是fixed
)。
要更改卡住元素的样式,或任何其他元素,我们可以使用IntersectionObserver
API -它允许我们观察两个元素交集的变化。
为了达到我们想要的效果,我们将添加一个sentinel
元素,当header
元素到达顶部时,它将作为一个指示器。换句话说:
- 当哨兵没有与视口相交时,我们的
header
在顶部 - 当哨兵与视口相交时,我们的
header
不在顶部
有了这两个条件,我们可以应用任何必要的样式到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>