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!

8 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

  3. Scott
    Posted December 10, 2010 at 10:20 am | Permalink

    How do I set $.support.WebKitAnimationEvent to true for Android?

  4. Scott
    Posted December 10, 2010 at 10:40 am | Permalink

    Figured it out. At the beginning section of jqtouch.js I made the following change to force true.

    //$.support.WebKitAnimationEvent = (typeof WebKitTransitionEvent == “object”);
    $.support.WebKitAnimationEvent = true;

  5. Posted January 10, 2011 at 9:09 pm | Permalink

    Thank you x3 ! :) Just what I was looking for, and am still wondering why this isn’t fixed in the current version. Been going through the Jonathan Stark book.

  6. Posted January 11, 2011 at 11:49 pm | Permalink

    Oooooh. I get it. I didn’t realize that one needs to slide “panels” (“cards”) into place inside a DIV via AJAX in an index.html file. Previously I had thought I would call some animate function on a hyperlink click that would go to another page. Instead, we need to intercept and prevent that click’s event bubble, and use the animation to slide the DIV into place. On load of index.html, it can use setTimeout() after load in order to asynchronously load the other “cards” (HTML files loaded with jQuery’s $(‘#mydiv’.load() call) into the DOM and keep them hidden until ready to animate into place.

  7. nylon
    Posted January 28, 2011 at 2:58 am | Permalink

    Resovle jqtouch goBack() event.

    @-webkit-keyframes slideinfromright{
    from{-webkit-transform: translate3d(100%,0px,100px);}
    to{-webkit-transform: translate3d(0,0px,0px);}
    }

    @-webkit-keyframes slideinfromleft{
    from{-webkit-transform: translate3d(-100%,0px,-100px);}
    to{-webkit-transform: translate3d(0,0px,0px);}
    }

  8. Posted December 1, 2011 at 8:07 pm | Permalink

    I could fix flash problem for Android2.2 and 2.3!
    Very Very thank you from tokyo!!!

One Trackback

  1. [...] This post was mentioned on Twitter by Tablet PC Info, junichi_y. junichi_y said: jQTouch slide transition fix for Android 2.0 –: http://bit.ly/hzOnp9 [...]

Post a Comment

Your email is never shared. Required fields are marked *

*
*