Mobile Webkit CSS Sizing Bug: Hardware Acceleration Edition

It’s a well-known “secret” that you can force webkit engines into hardware accelerated rendering by applying what should be non-effectual[link] css3[link] transforms[link]. The reason you care is though HTML5 mobile apps are great, they still require effort to get that native feeling for simple tasks like scrolling[link to momentum scrolling post] and animations. As a result, if you google something like “momentum scrolling html5″ you’ll see posts that give you snippets like this:

*:not(html) {
     -webkit-transform: translateZ(0);
}

and variations thereof.

The main issue with css like this is that it can quickly crash your mobile app with an out of memory error by asking webkit to hardware accelerate every DOM element in the page, which it apparently wasn’t built to do. The other issue is that it’s just not obvious what else is affected when you switch on hardware acceleration all the way down the render tree.

Here’s a simple example where adding a transform modifies how webkit sizes an element:

What You Expected

Now With Extra Whitespace

And here’s the HTML/CSS to make it happen (you can see the same behavior in Chrome Mac 21.0.1180.89):

<!doctype html>
<html>

    <head>
        <style type="text/css">
            body {
                margin: 0;
            }
            #app {
                width: 100%;
                height: 100%;
                margin-left: -20px;
                background: #323232;
                -webkit-transform: translateZ(0);
            }
            .title-bar {
                position: fixed;
                top: 0;
                left: 0;
                right: 0;
                height: 48px;
                text-align: center;
                background: #323232;
                color: #FFF;
            }
            .title-bar h2 {
                margin: 8px 0;
                font-size: 26px;
            }
        </style>
    </head>

    <body>
        <div id="app">
            <div class="title-bar">
                 <h2>App Title</h2>

            </div>
        </div>
    </body>

</html>

There are three ways to fix the issue:

  1. remove “margin-left: -20px” from #app
  2. remove “width: 100%” from #app
  3. remove “-webkit-transform: translateZ(0)” from #app

Regardless of how you think this should render, the fact is that adding the webkit transform does change the sizing of the #app element in an unexpected (at least to me) way. Though easily diagnosed and fixed, it means I’ll have to spend more time scrutinizing the rendering layout leaving less time for building the actual functionality of an app, for now.

What? margin-left: -20px?

So how did I happen across this randomness? I was playing around with using Bootstrap and the built-in responsive design for mobile site development and it’s grid system uses negative margins as part of its column alignment. Hence, this post.

JS Hacks: Dead Simple Javascript Variable Change Watchers

Writing boilerplate to change variables, re-calculate aggregate values and update views sucks and is error-prone.

De-coupleing the management of your data with whatever other parts of your application that affects makes your application more flexible, more testable, and actually easier to understand because there’s less munging of concerns within a single function.

Most of the newer Javascript frameworks have some sort of pattern for variable binding, in Backbone it’s model change events [http://backbonejs.org/#Model] , in Ember it’s controller bindings [http://emberjs.com/documentation/#toc_setting-up-bindings] and Ember.Observable [http://emberjs.com/api/classes/Ember.Observable.html], in Angular [http://angularjs.org/] it’s done declaratively but in the HTML directly and the same is true with KnockoutJS [http://knockoutjs.com/].

However, if you feel like some of these solutions are overly complex, don’t dig the get()/set() syntax or are interested in building a purpose-driven micro-framework you might be interested to know that Javascript actually supports transparent getter/setter functions just like ruby, java, actionscript and others. Here is a simple trio of functions that exposes this to you in a cross-browser manner and even notifies you of modifications done on an array (IE9+):

function watch(target, prop, handler) {
    if (target.__lookupGetter__(prop) != null) {
        return true;
    }
    var oldval = target[prop],
        newval = oldval,
        self = this,
        getter = function () {
            return newval;
        },
        setter = function (val) {
            if (Object.prototype.toString.call(val) === '[object Array]') {
                val = _extendArray(val, handler, self);
            }
            oldval = newval;
            newval = val;
            handler.call(target, prop, oldval, val);
        };
    if (delete target[prop]) { // can't watch constants
        if (Object.defineProperty) { // ECMAScript 5
            Object.defineProperty(target, prop, {
                get: getter,
                set: setter,
                enumerable: false,
                configurable: true
            });
        } else if (Object.prototype.__defineGetter__ && Object.prototype.__defineSetter__) { // legacy
            Object.prototype.__defineGetter__.call(target, prop, getter);
            Object.prototype.__defineSetter__.call(target, prop, setter);
        }
    }
    return this;
};

function unwatch(target, prop) {
    var val = target[prop];
    delete target[prop]; // remove accessors
    target[prop] = val;
    return this;
};

// Allows operations performed on an array instance to trigger bindings
function _extendArray(arr, callback, framework) {
    if (arr.__wasExtended === true) return;

    function generateOverloadedFunction(target, methodName, self) {
        return function () {
            var oldValue = Array.prototype.concat.apply(target);
            var newValue = Array.prototype[methodName].apply(target, arguments);
            target.updated(oldValue, motive);
            return newValue;
        };
    }
    arr.updated = function (oldValue, self) {
        callback.call(this, 'items', oldValue, this, motive);
    };
    arr.concat = generateOverloadedFunction(arr, 'concat', motive);
    arr.join = generateOverloadedFunction(arr, 'join', motive);
    arr.pop = generateOverloadedFunction(arr, 'pop', motive);
    arr.push = generateOverloadedFunction(arr, 'push', motive);
    arr.reverse = generateOverloadedFunction(arr, 'reverse', motive);
    arr.shift = generateOverloadedFunction(arr, 'shift', motive);
    arr.slice = generateOverloadedFunction(arr, 'slice', motive);
    arr.sort = generateOverloadedFunction(arr, 'sort', motive);
    arr.splice = generateOverloadedFunction(arr, 'splice', motive);
    arr.unshift = generateOverloadedFunction(arr, 'unshift', motive);
    arr.__wasExtended = true;

    return arr;
}

Now you can register a handler that will be called every time any part of your code updates a bound variable. For example:

var data = {
     quantity: 0
     , products:  []
}
, watcher = function(propertyName, oldValue, newValue){ … update some other pieces of the application … };

watch(data, 'quantity', watcher);
watch(data, 'products', watcher);

If you’re stepping through your javascript console as you add these you can see that after each call to watch, the properties on data are redefined as getter/setters. So now, you can do things like:

data.quantity = 7;
data.products.push('beer')

and notice that your watcher function is called automatically with the new property values. Hooray!

Now your watcher function can manage propagating the changes to the rest of your application instead of you manually doing that in every place you might update your variables. If we take this one step further, we can get a [sweet way to de-couple concerns even further by providing multiple watch functions per variable - different blog post].

This code is adapted from @eligrey’s original gist which itself is originally modeled after Gecko’s built in watch() functionality.