我怎样才能提高我的视差滚动脚本的性能? [英] How can I improve performance on my parallax scroll script?

查看:111
本文介绍了我怎样才能提高我的视差滚动脚本的性能?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在使用Javascript& jQuery构建一个视差滚动脚本,该脚本使用 transform:translate3d 来操作图中的图像元素,并基于我已经完成了阅读(保罗爱尔兰的博客等),我已经被告知这个任务的最佳解决方案是使用 requestAnimationFrame 出于性能原因。

虽然我明白如何编写Javascript,但我总是发现自己不确定如何编写好的 Javascript。特别是,虽然下面的代码似乎正常和平稳地运行,但我想解决一些我在Chrome开发工具中看到的问题。

  $(document).ready(function(){
function parallaxWrapper(){
//获取视口尺寸
var viewportDims = determineViewport();
var parallaxImages = [];
var lastKnownScrollTop;

//包含视差的foreach图
$('figure.parallax')。each(function(){
//保存每个视差图像的信息
var parallaxImage = {};
parallaxImage.container = $(this) ;
parallaxImage.containerHeight = $(this).height();
//图元素中包含的图像
parallaxImage.image = $(this).children('img.lazy ');
parallaxImage.offsetY = parallaxImage.container.offset()。top;

parallaxImages.push(parallaxImage);
});

$(window).on('scroll',function(){
lastKnownScro llTop = $(window).scrollTop();
});

函数animateParallaxImages(){
$ .each(parallaxImages,function(index,parallaxImage){
var speed = 3;
var delta =((lastKnownScrollTop + ((viewportDims.height - parallaxImage.containerHeight)/ 2)) - parallaxImage.offsetY)/ speed;
parallaxImage.image.css({$ b $'transform':'translate3d(0,'+ delta + 'px,0)'
});
});
window.requestAnimationFrame(animateParallaxImages);
}
animateParallaxImages();
}

parallaxWrapper();
});

首先,当我进入Chrome Dev Tools的'时间轴'选项卡并开始录制时,在没有对页面执行任何操作的情况下,动作记录叠加计数继续以每秒约40次的速率攀升。第二,为什么动画帧被触发每16ms执行一次,即使当我没有滚动页面或与页面交互时,如下图所示? / p>

第三,为什么Used JS Heap的大小没有我与页面交互的规模?如下图所示。我已经消除了可能导致此问题的所有其他脚本。





任何人都可以帮助我解决上述问题,并就如何改进我的代码给我一些建议吗?

解决方案

编辑:我当时没有看到@ user1455003和@mpd的答案写这个。
$ b

requestAnimationFrame 类似于 setTimeout ,除非浏览器不会触发回调函数,直到它处于呈现循环中,通常每秒发生约60次。另一方面, setTimeout 可以像CPU一样快速处理。



code> requestAnimationFrame 和 setTimeout 必须等到下一个可用的tick(因缺少更好的术语),直到它运行。因此,例如,如果您使用 requestAnimationFrame ,它应该每秒运行约60次,但是如果浏览器帧速率下降到30fps(因为您'试图旋转一个巨大的PNG和一个巨大的盒子阴影)你的回调函数每秒只能激发30次。同样,如果使用 setTimeout(...,1000)它应该在1000毫秒后运行。但是,如果一些繁重的任务导致CPU忙于工作,则只有CPU有循环时才会触发回调。 John Resig拥有关于JavaScript计时器的优秀文章



那么为什么不使用 setTimeout(...,16)代替请求动画帧呢?因为你的CPU可能有足够的空间,而浏览器的帧速率已经下降到30fps。在这种情况下,您将每秒运行60次计算并尝试渲染这些更改,但浏览器只能处理这么多的一半。如果你这样做的话,你的浏览器会处于追赶状态......因此, requestAnimationFrame 的性能优势。



为简洁起见,我在下面的一个示例中包含了所有建议的更改。

您看到动画的原因经常发射的帧是因为你有一个递归动画函数,它不断的发射。如果你不希望它不断地发射,你可以确保它只在用户滚动时触发。



你看到内存使用率攀升的原因必须做与垃圾收集,这是浏览器的方式来清理陈旧的记忆。每次定义变量或函数时,浏览器都必须为该信息分配一块内存。浏览器足够聪明,可以知道何时使用某个变量或函数,并释放内存以供重用 - 但是,只有当存在足够的过期内存值时,它才会收集垃圾。我无法在屏幕截图中看到内存图的大小,但如果内存以千字节为单位增加,浏览器可能无法清理它几分钟。您可以通过重用变量名称和函数来最小化新内存的分配。在你的例子中,每个动画帧(60x秒)定义了一个新函数(用于 $。each )和2个变量( speed delta )。这些都很容易重复使用(请参阅代码)。



如果您的内存使用量持续增加,您的代码中会出现内存泄漏问题。因为你在这里发布的代码是无泄漏的,所以拿起啤酒开始做研究。最大的罪魁祸首是引用一个对象(JS对象或DOM节点),然后被删除,引用仍然挂起。例如,如果您将点击事件绑定到DOM节点,请删除该节点,并且永远不要解除绑定事件处理程序...那里,一个内存泄漏。

 $(document).ready(function(){
function parallaxWrapper(){
//获取视口尺寸
var $ window = $(window ),
速度= 3,
viewportDims =判断视口(),
parallaxImages = [],
isScrolling = false,
scrollingTimer = 0,
lastKnownScrollTop ;

//包含视差的foreach图
$('figure.parallax')。each(function(){
//浏览器应该清理这个函数,$这个变量 - 不需要重用
var $ this = $(this);
//保存每个视差图像的信息
parallaxImages.push({
container = $ this,
containerHeight:$ this.height(),
//图元素中包含的图像
image:$ this.children('img.lazy'),
offsetY:$ this.offset()。top
});
});

//这可能有点矫枉过正,可能在
下面内联定义//我只是想说明重用...
函数onScrollEnd(){
isScrolling = false; ($!

$ b $ window.on('scroll',function(){
lastKnownScrollTop = $ window.scrollTop();
if(!isScrolling){
isScrolling = true;
animateParallaxImages();
}
clearTimeout(scrollingTimer);
scrollingTimer = setTimeout(onScrollEnd,100);
});
$ b $ function transformImage(index,parallaxImage){
parallaxImage.image.css({$ b $'transform':'translate3d(0,'+(

lastKnownScrollTop +
(viewportDims.height - parallaxImage.containerHeight)/ 2 -
parallaxImage.offsetY
)/ speed
)+'px,0)'
});
}

函数animateParallaxImages(){
$ .each(parallaxImages,transformImage);
if(isScrolling){
window.requestAnimationFrame(animateParallaxImages);
}
}
}

parallaxWrapper();
});


I'm using Javascript & jQuery to build a parallax scroll script that manipulates an image in a figure element using transform:translate3d, and based on the reading I've done (Paul Irish's blog, etc), I've been informed the best solution for this task is to use requestAnimationFrame for performance reasons.

Although I understand how to write Javascript, I'm always finding myself uncertain of how to write good Javascript. In particular, while the code below seems to function correctly and smoothly, I'd like to get a few issues resolved that I'm seeing in Chrome Dev Tools.

$(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();
});

Firstly, when I head to the 'Timeline' tab in Chrome Dev Tools, and start recording, even with no actions on the page being performed, the "actions recorded" overlay count continues to climb, at a rate of about ~40 per second.

Secondly, why is an "animation frame fired" executing every ~16ms, even when I am not scrolling or interacting with the page, as shown by the image below?

Thirdly, why is the Used JS Heap increasing in size without me interacting with the page? As shown in the image below. I have eliminated all other scripts that could be causing this.

Can anyone help me with some pointers to fix the above issues, and give me suggestions on how I should improve my code?

解决方案

Edit: I had not seen the answers from @user1455003 and @mpd at the time I wrote this. They answered while I was writing the book below.

requestAnimationFrame is analogous to setTimeout, except the browser wont fire your callback function until it's in a "render" cycle, which typically happens about 60 times per second. setTimeout on the other hand can fire as fast as your CPU can handle if you want it to.

Both requestAnimationFrame and setTimeout have to wait until the next available "tick" (for lack of a better term) until it will run. So, for example, if you use requestAnimationFrame it should run about 60 times per second, but if the browsers frame rate drops to 30fps (because you're trying to rotate a giant PNG with a large box-shadow) your callback function will only fire 30 times per second. Similarly, if you use setTimeout(..., 1000) it should run after 1000 milliseconds. However, if some heavy task causes the CPU to get caught up doing work, your callback won't fire until the CPU has cycles to give. John Resig has a great article on JavaScript timers.

So why not use setTimeout(..., 16) instead of request animation frame? Because your CPU might have plenty of head room while the browser's frame rate has dropped to 30fps. In such a case you would be running calculations 60 times per second and trying to render those changes, but the browser can only handle half that much. Your browser would be in a constant state of catch-up if you do it this way... hence the performance benefits of requestAnimationFrame.

For brevity, I am including all suggested changes in a single example below.

The reason you are seeing the animation frame fired so often is because you have a "recursive" animation function which is constantly firing. If you don't want it firing constantly, you can make sure it only fires while the user is scrolling.

The reason you are seeing the memory usage climb has to do with garbage collection, which is the browsers way of cleaning up stale memory. Every time you define a variable or function, the browser has to allocate a block of memory for that information. Browsers are smart enough to know when you are done using a certain variable or function and free up that memory for reuse - however, it will only collect the garbage when there is enough stale memory worth collecting. I can't see the scale of the memory graph in your screenshot, but if the memory is increasing in kilobyte size amounts, the browser may not clean it up for several minutes. You can minimize the allocation of new memory by reusing variable names and functions. In your example, every animation frame (60x second) defines a new function (used in $.each) and 2 variables (speed and delta). These are easily reusable (see code).

If your memory usage continues to increase ad infinitum, then there is a memory leak problem elsewhere in your code. Grab a beer and start doing research as the code you've posted here is leak-free. The biggest culprit is referencing an object (JS object or DOM node) which then gets deleted and the reference still hangs around. For example, if you bind a click event to a DOM node, delete the node, and never unbind the event handler... there ya go, a memory leak.

$(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();
});

这篇关于我怎样才能提高我的视差滚动脚本的性能?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆