I am creating a header that acts like the Chrome for Android Address bar. The effect is that the header is a pseudo sticky header that scrolls out of view as you scroll down and then you you begin to scroll back up the header scrolls back into view.

Right now it works fine on the desktop (around 60fps) but on Chrome for Android (on Nexus 7 2013) it is full of jank.

Demo: jsFiddle

Both the header and content area are moved with transform translateY which are more performant than pos:top

I am also using requestAnimationFrame to debounce scrolling and only change properties when it is most convenient for the browser.

The header is position: fixed; top: 0; and then scrolled in and out of view with transform: translateY(...);. Also instead of using margin-top to get the content out from underneath the header, I am using transform: translateY(...);

The basic structure of my js looks like:

var latestScrollTop = 0;
var lastReactedScrollTop = 0;

var ticking = false;

function DoScroll()
{
    var builtUpScrollTop = latestScrollTop - lastReactedScrollTop;

    // Fold the top bar while we are scrolling (lock it to scrolling)
    $('header.main-header').css('transform', 'translateY(' ... 'px)');
    HeaderHeightChange();

    lastReactedScrollTop = latestScrollTop;
    ticking = false;
}

function HeaderHeightChange()
{
    // We need to update the margin-top for the content so we don't overlap it
    $('main.content-area').css('transform', 'translateY(' ... 'px)');

}

function requestTick() {
    if(!ticking) {
        requestAnimationFrame(function(){
            DoScroll();
        });
    }
    ticking = true;
}

$(window).on('scroll', function(e) {
    latestScrollTop = $(window).scrollTop();
    requestTick();
});

The effect is not complete as it needs to resolve the fold after you finish scrolling (and is coded) but I do not want to complicate the issue when just the scroll movement lock to header is causing jank. I see paint rectangles when scrolling up and down even though I am changing transform which I assume the gpu is handling and shouldn't be painting.

Edit: It seems when debugging with ADB that there is a a bunch of clear grey outlined time in each frame. Chrome Timeline screenshot. Lots of grey clear outlined frame bars.

有帮助吗?

解决方案

Turns out that even though I was using transform: translateY() that you still need to add translateZ(0) to see the benefit of layers and having it gpu accelerated.

But I did also update my code to use a object literal code style and got rid of the forced synchronous layout warning in the timeline by reading then writing. This is coupled along with requestAnimationFrame.

Demo: jsFiddle

var myUtils = {
    clamp: function(min, max, value) {
        return Math.min(Math.max(value, min), max);
    },

    getTranslateYFromTransform: function(rawTransform) {
        return parseFloat(rawTransform.match(/^matrix\((([+-]?[0-9]*\.?[0-9]*),\s*?){5}([+-]?[0-9]*\.?[0-9]*)\)$/)[3])
    }

};


var scrollHeader = {
    latestScrollTop: 0,
    lastReactedScrollTop: 0,

    headerHeight: 0,
    headerTransformTranslateY: 0,

    ticking: false,

    requestTick: function() {
        if(!scrollHeader.ticking) {
            requestAnimationFrame(function(){
                scrollHeader.doHeaderFold();
            });
        }
        scrollHeader.ticking = true;
    },

    doHeaderFold: function() {
        var header = $('header.main-header');

        var builtUpScrollTop = scrollHeader.latestScrollTop - scrollHeader.lastReactedScrollTop;

        scrollHeader.headerHeight = header.outerHeight();

        scrollHeader.headerTransformTranslateY = myUtils.clamp(-parseInt(scrollHeader.headerHeight), 0, (myUtils.getTranslateYFromTransform(header.css('transform')) - builtUpScrollTop));

        // Fold the top bar while we are scrolling (lock it to scrolling)
        header.css('transform', 'translateY(' + scrollHeader.headerTransformTranslateY + 'px) translateZ(0)');

        scrollHeader.headerHeightChange();

        scrollHeader.lastReactedScrollTop = scrollHeader.latestScrollTop;
        scrollHeader.ticking = false;
    },

    headerHeightChange: function() {
        // We need to update the margin-top for the content so we don't overlap it
        $('main.content-area').css('transform', 'translateY(' + (scrollHeader.headerHeight + scrollHeader.headerTransformTranslateY) + 'px) translateZ(0)');
    }
};

$(window).on('scroll', function(e) {
    //console.log(e);
    scrollHeader.latestScrollTop = $(window).scrollTop();

    scrollHeader.requestTick();

});

This makes the timeline debugging on ADB (Nexus 7 2013) look like(very smooth): Chrome Timeline nice buttery smooth results

Also to get rid of a small jump when first scrolling add transform: translateZ(0) to your element before animating it.

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top