jQTouch slide transition fix for Android 2.0

A client of Blazing Cloud recently wanted to support animated screen transitions in their mobile platform and came to us for help. We decided to integrate the latest jQTouch (version 1, beta 2) into their framework since we had heard good things about it. Unfortunately, many animated transitions in Android don’t work very well. Worst is that slide transitions don’t work at all on Android 2.0 devices.

After poking around a bit, it seemed that the @-webkit-keyframes styles in jqtouch.css defined for the sliding transitions were being applied in parallel when running in Android 2.0. For example, a slide-in-from-right link  has these webkit animated styles applied to it:

.in, .out {
    -webkit-animation-timing-function: ease-in-out;
    -webkit-animation-duration: 350ms;
}
.slide.in {
    -webkit-animation-name: slideinfromright;
}
@-webkit-keyframes slideinfromright {
    from { -webkit-transform: translateX(100%); }
    to { -webkit-transform: translateX(0); }
}

On  iPhone and Android 1.0 devices,  a slide-in-from-right screen slides in correctly from 100% to 0 in about 350ms. But on Android 2.0 devices,  the screen appears immediately as if no animation occurred.

To work around this problem,  you have to manually set the start position first, then the end position and animation style. That is, instead of letting the @-webkit-keyframes do the work, you have to manually position the screen at 100%, then set the animation and final position style after a small time interval. For example, the key portion of the code to consider  is:

toPage.css("webkitTransform", "translate3d(100%,0px,0px)");
setTimeout(function() {
    toPage.css({webkitTransitionDuration: "350ms",
    webkitTransitionTimingFunction: "ease-in-out"});
    toPage.css("webkitTransform", "translate3d(0px,0px,0px)");
}, 5);

Note that we first set the starting position then wait about 5 milliseconds before applying the animated style (webkitTransitionDuration and webkitTransitionTimingFunction). After the transition completes, I clear the style from the page:

toPage.css({webkitTransitionDuration: null,
            webkitTransitionTimingFunction: null})

The sliding animation is handled in the animatePages function. Here’s the code with workaround (my changes in bold):

function animatePages(fromPage, toPage, animation, backwards) {
    // Error check for target page
    if(toPage.length === 0){
        $.fn.unselect();
        console.error('Target element is missing.');
        return false;
    }
    // Collapse the keyboard
    $(':focus').blur();
    // Make sure we are scrolled up to hide location bar
    scrollTo(0, 0);
    // Define callback to run after animation completes
    var callback = function(event){
        if (animation)
        {
            if (animation.name == "slide") {
                var css = {webkitTransitionDuration: null,
                    webkitTransitionTimingFunction: null};
                toPage.css(css);
                fromPage.css(css);
            }
            toPage.removeClass('in reverse ' + animation.name);
            fromPage.removeClass('current out reverse ' + animation.name);
        }
        else
        {
            fromPage.removeClass('current');
        }
        toPage.trigger('pageAnimationEnd', { direction: 'in' });
        fromPage.trigger('pageAnimationEnd', { direction: 'out' });
        clearInterval(dumbLoop);
        currentPage = toPage;
        location.hash = currentPage.attr('id');
        dumbLoopStart();
        var $originallink = toPage.data('referrer');
        if ($originallink) {
            $originallink.unselect();
        }
        lastAnimationTime = (new Date()).getTime();
        tapReady = true;
    }
    fromPage.trigger('pageAnimationStart', { direction: 'out' });
    toPage.trigger('pageAnimationStart', { direction: 'in' });
    if ($.support.WebKitAnimationEvent &&
        animation &&
        jQTSettings.useAnimations) {
        tapReady = false;
        if (animation.name == "slide") {
            toPage.one('webkitTransitionEnd', callback);
            toPage.addClass('in current');
            fromPage.addClass('out');
            toPage.css("webkitTransform",
                       "translate3d(" + (backwards ? "-100%" : "100%") + ",0px,0px)");
            fromPage.css("webkitTransform",
                         "translate3d(0px,0px,0px)");
            setTimeout(function() {
                var css = {webkitTransitionDuration: "350ms",
                    webkitTransitionTimingFunction: "ease-in-out"};
                toPage.css(css);
                fromPage.css(css);
                toPage.css("webkitTransform",
                           "translate3d(0px,0px,0px)");
                fromPage.css("webkitTransform",
                             "translate3d(" + (backwards ? "100%" : "-100%") + ",0px,0px)");
            }, 5);
        } else {
            toPage.one('webkitAnimationEnd', callback);
            toPage.addClass(animation.name + ' in current ' + (backwards ? ' reverse' : ''));
            fromPage.addClass(animation.name + ' out' + (backwards ? ' reverse' : ''));
        }
    } else {
        toPage.addClass('current');
        callback();
    }
    return true;
}

Note that jQTouch sets $.support.WebKitAnimationEvent to false for Android 2.0 devices even though it does support WebKit animation. Make sure you set that to true. Replace the animatePage function in your jQtouch with the function in this post and now  you should be golden with iPhone and Android!

2 Comments

  1. wip
    Posted August 13, 2010 at 12:35 am | Permalink

    I hope Your brain compilator is working:

    for(i=0; i<=10e10; i++) {
    print("Thank You!");
    }

  2. wip
    Posted August 13, 2010 at 3:18 am | Permalink

    after updating this function I get the problem with jumping form, issued here: http://code.google.com/p/jqtouch/issues/detail?id=398

    It works only when I comment all lines with “translate3d” value, but then again the animations doesn’t work ;/

    Tested on HTC Desire, Android 2.2

Post a Comment

Your email is never shared. Required fields are marked *

*
*