Warning: file_get_contents(/data/phpspider/zhask/data//catemap/9/javascript/395.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Javascript 如何提高视差滚动脚本的性能?_Javascript_Html_Performance_Requestanimationframe - Fatal编程技术网

Javascript 如何提高视差滚动脚本的性能?

Javascript 如何提高视差滚动脚本的性能?,javascript,html,performance,requestanimationframe,Javascript,Html,Performance,Requestanimationframe,我正在使用Javascript和jQuery构建一个视差滚动脚本,该脚本使用transform:translate3d在figure元素中操纵图像,并基于我所做的阅读(Paul Irish的博客等),我被告知,出于性能原因,此任务的最佳解决方案是使用requestAnimationFrame 虽然我知道如何编写Javascript,但我总是不确定如何编写好的Javascript。特别是,虽然下面的代码似乎运行正常、流畅,但我希望解决我在Chrome开发工具中看到的一些问题 $(document)

我正在使用Javascript和jQuery构建一个视差滚动脚本,该脚本使用
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();
});