Sunday, November 20, 2011

Differential Background Scrolling

A quick one about this technique quite common in Flash sites but rarely seen on Web.
Have a look at the example first so you can understand what I am talking about ... got it ?

What Is This About

Let's say we have a background, a big massive graphic background surely not suitable for mobile phones, due data roaming, but maybe cool for desktop and fast ADSL.
The background-size CSS property is able to let us decide if the image used as background should fit the whole element or only a portion of it.
In this case the image should fit, by default, the whole height of the document with an auto width in order to let the browser adjust the scale.
A differential scrolling is visible the moment we scroll the page ... please resize the window into a smaller one if you are in an HD monitor and start scrolling the page.
At the very beginning, the default height of the image is 100%, as body background, and with some padding in order to let space for few important image parts such the header, with clouds and enough space for a H1 tag, and the bottom, with the stylish logo of this game from elderscrolls.com, the one which page inspired this little experiment. Bear in mind no code has been even read from that website ... I have seen the effect, I have used it many times ages ago via ActionScript, I decided to do something similar for most advanced browsers, and here is ...

The Code


(function (document) {
// (C) WebReflection - Mit Style Licence
var
ratio = .85, // 0 to 1 where 1 is 100%

// shortcuts
styleSheets = document.styleSheets,
documentElement = document.documentElement,
ceil = Math.ceil,
scroll = "scroll",
scrollHeight = scroll + "Height",
scrollTop = scroll + "Top",
body, sHeight, sTop, y, last
;
styleSheets = styleSheets[styleSheets.length - 1];
// redefine the rule for the height
styleSheets.insertRule(
"body{background-size:auto " + ceil(
ratio * 100
) + "%;}",
styleSheets.cssRules.length
);
// get the rest of the ratio
ratio = 1 - ratio;
// attach a scroll listener
addEventListener(scroll, function (e) {
if (body || (body = document.body)) {
sHeight = documentElement[scrollHeight] ||
body[scrollHeight];
sTop = documentElement[scrollTop] ||
body[scrollTop];
y = ceil(
ratio * sHeight * sTop / (sHeight - innerHeight)
);
// this avoid some redundant assignment
// hopefully creating less flicking effect
if (last != y) {
body.style.backgroundPosition = "center " + (last = y) + "px";
}
}
}, false);
// you may want to try this for Chrome Browsers
//documentElement.style.WebkitTransform = "translateZ(0)";
}(document));


The Problem

Many of them ... to start with the fact this technique does not scale as showed in this example since for mobile phones, or generally speaking smaller screens, it does not make sense to use such big image: use media queries for this.
Opera 12 is almost there but something goes terribly wrong during background reposition ... it's screwed up by N pixels even if the rest of the logic works and no error is shown on console.
Firefox Nightly goes quite well but it is still flicking a bit while Safari, and even better WebKit Nightly, are the smoothest in this Mac.
The disaster is Chrome Canary, which is not able to handle this background repositioning.
You can see the effect if you scroll fast in both inspiration site and my experiment and, as commented out in the code, the only way to make it better is to force HW acceleration in the whole document 'cause in the body only the background looks broken ... it's really cool to see how DOM is able to mess up with GPUs, isn't it?

As Summary

Nothing much to add to this post, it was just a quick example over a cool effect but as it is, since ever, in this Web field, almost everything went terribly wrong :D
Have fun with CSS and graceful JS enhancements!

Saturday, November 12, 2011

Few JavaScript Patterns

Just to be clear and once again, JavaScript can emulate:
  • classes
  • public and public static methods or properties
  • private and private static methods or properties
  • public and private constants
  • protected methods
  • ... you name it ...

// duck typing ( maybe all you need )
var me = {name: "WebReflection"};

// basic class
function Person() {}
Person.prototype.getName = function () {
return this.name;
};
Person.prototype.setName = function (name) {
this.name = name;
};

// module pattern + private properties / methods
function Person(_name) {
function _getName() {
return _name;
}
return {
getName: function () {
// redundant, example only
return _getName();
},
setName: function (name) {
_name = name;
}
};
}

// private shared methods via this
var Person = (function () {
function Person() {}
function _getName () {
return this.name;
}
function _setName (name) {
this.name = name;
}
Person.prototype.getName = function () {
return _getName.call(this);
};
Person.prototype.setName = function (name) {
_setName.call(this, name);
};
return Person;
}());

// private shared method Python style
var Person = (function () {
function Person() {}
function _getName (self) {
return self.name;
}
function _setName (self, name) {
self.name = name;
}
Person.prototype.getName = function () {
return _getName(this);
};
Person.prototype.setName = function (name) {
_setName(this, name);
};
return Person;
}());

// public static
function Person() {
Person.TOTAL++;
}
Person.TOTAL = 0;

// private static / constant
var Person = (function () {
var TOTAL = 0;
return function Person() {
TOTAL++;
};
}());

// public constant
function Person() {}
Object.defineProperty(Person, "RACE", {
writable: false, // default
configurable: false,// default
enumerable: true,
value: "HUMAN"
});

// public inherited constant
function Person() {}
Object.defineProperty(Person.prototype, "RACE", {
writable: false, // default
configurable: false,// default
enumerable: true,
value: "HUMAN"
});

// protected method
function Person() {}
Person.prototype.getName = function () {
return this instanceof Person ?
this.name :
throw "protected method violation"
;
};
Person.prototype.setName = function (name) {
this instanceof Person ?
this.name = name :
throw "protected method violation"
;
};

// generic protected methods
Function.prototype.protectedVia = function (Class) {
var method = this;
Class || (Class = Object);
return function () {
if (this instanceof Class) {
return method.apply(this, arguments);
}
throw "protected method violation on " + (
Class.name || Class
);
};
};
function Person() {}
Person.prototype.getName = function () {
return this.name;
}.protectedVia(Person);
Person.prototype.setName = function (name) {
this.name = name
}.protectedVia(Person);

// private shared variables
function Person() {}
Person.prototype.getName = function () {
return this.name;
};
(function (PersonPrototype) {
var changes = 0;
PersonPrototype.setName = function (name) {
changes++;
this.name = name;
};
PersonPrototype.howManyChangedName = function () {
return changes;
};
}(Person.prototype));

// getters / setters on public property
var person = Object.defineProperty({}, "name", {
get: function () {
return this._name;
},
set: function (_name) {
this._name = _name;
}
});

// getters setters on public property via prototype
function Person() {}
Object.defineProperty(Person.prototype, "name", {
get: function () {
return this._name;
},
set: function (_name) {
this._name = _name;
}
});

// getters / setters on private property
var person = Object.defineProperty({}, "name", (function () {
var _name;
return {
get: function () {
return _name;
},
set: function (name) {
_name = name;
}
};
}()));

// singleton
var me = {name: "WebReflection"};

// singleton via anonymous __proto__
// plus private properties
var me = new function () {
var _name;
this.getName = function () {
return _name;
};
this.setName = function (name) {
_name = name;
};
};

// generic singleton cross browser
Function.prototype.singleton = (function () {
function anonymous(){};
function create(Class, args) {
anonymous.prototype = Class.prototype;
Class.apply(
Class.__singleton__ = new anonymous,
args
);
return Class.__singleton__;
}
return function singleton() {
return this.__singleton__ || create(this, arguments);
};
}());

// generic singleton ES5
Function.prototype.singleton = (function () {
function create(Class, args) {
Class.apply(
Class.__singleton__ = Object.create(
Class.prototype
), args
);
return Class.__singleton__;
}
return function singleton() {
returh this.__singleton__ || create(this, arguments);
};
}());

// per function private singleton
var Person = (function () {
var instance;
return function Person () {
return instance || (instance = this);
// or ... ot be more than sure ...
return instance || (
(instance = true) && // avoid infinite recursion
(instance = new Person)
);
};
}());

// generic factory
Function.prototype.factory = (function () {
function anonymous() {};
return function factory() {
anonymous.prototype = this.prototype;
var instance = new anonymous;
this.apply(instance, arguments);
return instance;
};
}());

// generic factory ES5 + ruby style
Function.prototype.new = function factory() {
var instance = Object.create(this.prototype);
this.apply(instance, arguments);
return instance;
};

Tuesday, November 8, 2011

Function.prototype.notifier

There are way too many ways to stub functions or methods, but at the end of the day all we want to know is always the same:
  • has that function been invoked ?
  • has that function received the expected context ?
  • which argument has been passed to that function ?
  • what was the output of the function ?

Update thanks to @bga_ hint about the output property in after notification, it made perfect sense

The Concept

For fun and no profit I have created a prototype which aim is to bring a DOM like interface to any sort of function or method in order to monitor its lifecycle:
  • the "before" event, able to preventDefault() and avoid the original function call at all
  • the "after" event, in order to understand if the function did those expected changes to the environment or to a generic input object, or simply to analyze the output of the previous call
  • the "error" event, in case we want to be notified if something went wrong during function execution
  • the "handlererror" event, just in case we are the cause of an error while we are monitoring the original function
The reason I have chosen an addEventListener like interface, called in this case addListener, is simple: JavaScript works pretty well with event driven applications so what else could be better than an event driven approach?

Basic Example


var nFromCharcode = String.fromCharCode.notifier({
before: function (e) {
if (e.arguments.length > 2048) {
throw "too many arguments";
e.preventDefault(); // won't even try to execute it
}
// in case you want to remove this listener ...
e.notifier.removeListener("before", e.handler);
},
after: function (e) {
if (e.output !== "PQR") {
throw "expected PQR got " + e.output + " instead";
}
},
handlererror: function (e) {
testFramework.failBecause("" + e.error);
}
});

// run the test ...
nFromCharcode(80, 81, 82); // "PQR"
nFromCharcode.apply(null, arrayOf2049Codes); // testFramework will fail

The notifier itself is a function, precisely the original function wrapper with enriched API in order to monitor almost every aspect of a method or a function.
The event object passed through each listener has these properties:
  • notifier: the object create to monitor the function and notify all listeners
  • handler: the current handler to make the notifier remove listener easier
  • callback: the original function that has been wrapped by the notifier
  • type: the event type such before, error, after, handlererror
  • arguments: passed arguments transformed already into array
  • context: the "this" reference used as callback context
  • error: the optional error object for events error and handlererror
  • preventDefault: the method able to avoid function execution if called in the before listener
  • output: assigned only during "after" notification and if no error occurred, handy to compare expected results

I guess there is really nothing else we could possibly know about a notifier, and its callback, lifecycle, what do you think?

The Code




As Summary


I have also a full test coverage for this notifier and I hope someone will use it and will come back to provide some feedback, cheers!