Javascript 如何提高视差滚动脚本的性能?
我正在使用Javascript和jQuery构建一个视差滚动脚本,该脚本使用Javascript 如何提高视差滚动脚本的性能?,javascript,html,performance,requestanimationframe,Javascript,Html,Performance,Requestanimationframe,我正在使用Javascript和jQuery构建一个视差滚动脚本,该脚本使用transform:translate3d在figure元素中操纵图像,并基于我所做的阅读(Paul Irish的博客等),我被告知,出于性能原因,此任务的最佳解决方案是使用requestAnimationFrame 虽然我知道如何编写Javascript,但我总是不确定如何编写好的Javascript。特别是,虽然下面的代码似乎运行正常、流畅,但我希望解决我在Chrome开发工具中看到的一些问题 $(document)
transform:translate3d
在figure
元素中操纵图像,并基于我所做的阅读(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();
});
首先,当我进入Chrome Dev Tools中的“Timeline”选项卡并开始录制时,即使没有在页面上执行任何操作,“actions recorded”(已录制的操作)覆盖计数仍以每秒约40次的速度继续攀升
其次,为什么“动画帧触发”每16毫秒执行一次,即使我没有滚动或与页面交互,如下图所示
第三,为什么在没有我与页面交互的情况下,使用过的JS堆的大小会增加?如下图所示。我已经消除了可能导致这种情况的所有其他脚本
有谁能帮我解决上述问题,并就如何改进我的代码给我一些建议吗 (1&2——相同的答案)您正在使用的模式会创建一个重复的动画循环,该循环尝试以与浏览器刷新相同的速率触发。这通常是每秒60次,因此您看到的活动是大约每1000/60=16ms执行一次循环。如果没有工作要做,它仍然每16毫秒发射一次
(3) 浏览器会根据动画的需要消耗内存,但不会立即回收内存。相反,它偶尔会在一个称为垃圾收集的过程中回收任何孤立内存。所以你的内存消耗应该会上升一段时间,然后下降一大块。如果它不这样做,则表示内存泄漏。尝试摆脱动画循环,并将滚动更改置于“滚动”功能中。当lastKnownScrollTop保持不变时,这将阻止脚本执行转换
$(window).on('scroll', function() {
lastKnownScrollTop = $(window).scrollTop();
$.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)'
});
});
});
$(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();
});
@马克的答案对1和2是正确的 (3) 是由于动画循环是无限递归的:
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); //recursing here, but there is no base base
}
animateParallaxImages(); //Kick it off
如果您查看以下示例:
编辑:在我写这篇文章的时候,我还没有看到@user1455003和@mpd的答案。我正在写下面的书时,他们回答了
requestAnimationFrame
类似于setTimeout
,不同之处在于浏览器在进入“渲染”循环之前不会启动回调函数,该循环通常每秒发生60次<另一方面,如果您需要,code>setTimeout可以以CPU能够处理的速度启动
requestAnimationFrame
和setTimeout
都必须等待下一个可用的“勾号”(因为没有更好的术语),直到它运行。因此,例如,如果您使用requestAnimationFrame
它应该每秒运行60次,但是如果浏览器帧速率下降到30fps(因为您试图旋转带有大框阴影的巨大PNG),则回调函数将每秒仅启动30次。类似地,如果使用setTimeout(…,1000)
它应该在1000毫秒后运行。但是,如果某个繁重的任务导致CPU忙于工作,那么在CPU有足够的周期之前,您的回调不会启动。约翰·雷斯格有一个好朋友
那么为什么不使用setTimeout(…,16)
而不是请求动画帧呢?因为当浏览器的帧速率下降到30fps时,CPU可能有足够的空间。在这种情况下,您将每秒运行60次计算,并尝试渲染这些更改,但浏览器只能处理一半。如果你这样做的话,你的浏览器会一直处于追赶状态。。。因此,requestAnimationFrame
具有性能优势
为了简洁起见,我将所有建议的更改都包含在下面的一个示例中。
您之所以经常看到动画帧被触发,是因为您有一个“递归”动画函数,该函数不断被触发。如果您不想让它不断启动,可以确保它只在用户滚动时启动
内存使用率上升的原因与垃圾收集有关,垃圾收集是一种清除陈旧内存的方法。每次定义变量或函数时,浏览器都必须为该信息分配一块内存。浏览器足够聪明,可以知道何时使用了某个变量或函数,并释放出内存供重用——然而,它只会在有足够的陈旧内存值得收集时收集垃圾。我在屏幕截图中看不到内存图的大小,但如果内存以千字节大小的数量增加,浏览器可能在几分钟内无法清理它。您可以通过重用变量名和函数来最小化新内存的分配。在您的示例中,每个动画帧(60秒)定义一个新函数(用于$。每个)和两个变量(速度和增量)。它们很容易重用(参见代码)
如果您的内存使用继续无限增加,那么代码中的其他地方就存在内存泄漏问题。喝杯啤酒,开始做研究,因为你在这里发布的代码是无泄漏的。最大的罪魁祸首是引用一个对象(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();
});
如何避免1和2发生的情况?我知道风
$(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();
});