Rea*_*ues 9 html javascript performance requestanimationframe
我正在使用Javascript和jQuery构建一个视差滚动脚本来操作figure元素中的图像transform:translate3d,并根据我所做的阅读(Paul Irish的博客等),我被告知这个任务的最佳解决方案是requestAnimationFrame出于性能原因使用.
虽然我理解如何编写Javascript,但我总是发现自己不确定如何编写好的 Javascript.特别是,虽然下面的代码看起来运行正常且顺畅,但我想解决一些我在Chrome开发工具中看到的问题.
$(document).ready(function() {
function parallaxWrapper() {
// Get the viewport dimensions
var viewportDims = determineViewport();
var parallaxImages = [];
var lastKnownScrollTop;
// Foreach figure containing a parallax
$('figure.parallax').each(function() {
// Save information about each parallax image
var parallaxImage = {};
parallaxImage.container = $(this);
parallaxImage.containerHeight = $(this).height();
// The image contained within the figure element
parallaxImage.image = $(this).children('img.lazy');
parallaxImage.offsetY = parallaxImage.container.offset().top;
parallaxImages.push(parallaxImage);
});
$(window).on('scroll', function() {
lastKnownScrollTop = $(window).scrollTop();
});
function animateParallaxImages() {
$.each(parallaxImages, function(index, parallaxImage) {
var speed = 3;
var delta = ((lastKnownScrollTop + ((viewportDims.height - parallaxImage.containerHeight) / 2)) - parallaxImage.offsetY) / speed;
parallaxImage.image.css({
'transform': 'translate3d(0,'+ delta +'px,0)'
});
});
window.requestAnimationFrame(animateParallaxImages);
}
animateParallaxImages();
}
parallaxWrapper();
});
Run Code Online (Sandbox Code Playgroud)
首先,当我前往Chrome开发工具中的"时间轴"选项卡并开始录制时,即使没有对正在执行的操作执行操作,"记录的操作"叠加计数也会继续攀升,速度约为每秒约40个.
其次,为什么"动画帧被触发"每隔约16ms执行一次,即使我没有滚动或与页面交互,如下图所示?
第三,为什么在没有我与页面交互的情况下,使用的JS堆大小增加了?如下图所示.我已经删除了可能导致此问题的所有其他脚本.

任何人都可以帮我解决上述问题,并就如何改进我的代码给出建议吗?
(1和2 - 相同的答案)您使用的模式会创建一个重复的动画循环,尝试以与浏览器刷新相同的速率触发.这通常是每秒60次,所以你看到的活动是大约每1000/60 = 16ms执行的循环.如果没有工作要做,它仍然会每16ms发射一次.
(3)浏览器根据动画的需要消耗内存,但浏览器不会立即回收该内存.相反,它偶尔会在一个名为垃圾收集的过程中回收任何孤立的内存.所以你的内存消耗应该会持续一段时间然后大量下降.如果它不那样,那么你有内存泄漏.
编辑:在我写这篇文章时,我还没有看到@user1455003 和@mpd 的答案。当我写下面的书时,他们给出了答案。
requestAnimationFrame与 类似setTimeout,只是浏览器不会触发您的回调函数,直到它处于“渲染”周期(通常每秒发生约 60 次)。 setTimeout另一方面,如果你愿意的话,它可以像你的CPU处理的那样快。
两者requestAnimationFrame都setTimeout必须等到下一个可用的“tick”(由于缺乏更好的术语)才能运行。因此,例如,如果您使用requestAnimationFrame它应该每秒运行大约 60 次,但如果浏览器帧速率下降到 30fps(因为您试图旋转带有大框阴影的巨型 PNG),您的回调函数只会触发每秒30次。同样,如果使用setTimeout(..., 1000)它应该在 1000 毫秒后运行。但是,如果某些繁重的任务导致 CPU 无法完成工作,则回调将不会触发,直到 CPU 有可用的周期。John Resig 有一篇关于 JavaScript 计时器的精彩文章。
那么为什么不使用setTimeout(..., 16)请求动画帧来代替呢?因为当浏览器的帧速率下降到 30 fps 时,您的 CPU 可能有足够的空间。在这种情况下,您将每秒运行计算 60 次并尝试呈现这些更改,但浏览器只能处理一半的量。如果您这样做,您的浏览器将处于持续追赶的状态......因此requestAnimationFrame.
为简洁起见,我将所有建议的更改包含在下面的一个示例中。
您看到动画帧如此频繁地触发的原因是因为您有一个不断触发的“递归”动画函数。如果您不希望它不断触发,您可以确保它仅在用户滚动时触发。
您看到内存使用量攀升的原因与垃圾收集有关,这是浏览器清理陈旧内存的方式。每次定义变量或函数时,浏览器都必须为该信息分配一块内存。浏览器足够聪明,可以知道您何时使用完某个变量或函数,并释放该内存以供重用 -但是,只有当有足够的陈旧内存值得收集时,它才会收集垃圾。我在屏幕截图中看不到内存图的比例,但如果内存以千字节大小的量增加,则浏览器可能在几分钟内无法清理它。您可以通过重用变量名和函数来最大限度地减少新内存的分配。在您的示例中,每个动画帧(60x 秒)定义一个新函数(在 中使用$.each)和 2 个变量(speed和delta)。这些很容易重用(参见代码)。
如果您的内存使用量继续无限增加,则代码中的其他地方存在内存泄漏问题。喝杯啤酒并开始研究,因为您在此处发布的代码是无泄漏的。最大的罪魁祸首是引用一个对象(JS 对象或 DOM 节点),然后该对象被删除,并且引用仍然存在。例如,如果您将单击事件绑定到 DOM 节点,然后删除该节点,并且永远不会取消绑定事件处理程序...那么,内存泄漏。
$(document).ready(function() {
function parallaxWrapper() {
// Get the viewport dimensions
var $window = $(window),
speed = 3,
viewportDims = determineViewport(),
parallaxImages = [],
isScrolling = false,
scrollingTimer = 0,
lastKnownScrollTop;
// Foreach figure containing a parallax
$('figure.parallax').each(function() {
// The browser should clean up this function and $this variable - no need for reuse
var $this = $(this);
// Save information about each parallax image
parallaxImages.push({
container = $this,
containerHeight: $this.height(),
// The image contained within the figure element
image: $this.children('img.lazy'),
offsetY: $this.offset().top
});
});
// This is a bit overkill and could probably be defined inline below
// I just wanted to illustrate reuse...
function onScrollEnd() {
isScrolling = false;
}
$window.on('scroll', function() {
lastKnownScrollTop = $window.scrollTop();
if( !isScrolling ) {
isScrolling = true;
animateParallaxImages();
}
clearTimeout(scrollingTimer);
scrollingTimer = setTimeout(onScrollEnd, 100);
});
function transformImage (index, parallaxImage) {
parallaxImage.image.css({
'transform': 'translate3d(0,' + (
(
lastKnownScrollTop +
(viewportDims.height - parallaxImage.containerHeight) / 2 -
parallaxImage.offsetY
) / speed
) + 'px,0)'
});
}
function animateParallaxImages() {
$.each(parallaxImages, transformImage);
if (isScrolling) {
window.requestAnimationFrame(animateParallaxImages);
}
}
}
parallaxWrapper();
});
Run Code Online (Sandbox Code Playgroud)