My JavaScript book is out! Don't miss the opportunity to upgrade your beginner or average dev skills.
Showing posts with label setTimeout. Show all posts
Showing posts with label setTimeout. Show all posts

Monday, July 11, 2011

Random Rant On CSS3 Transitions

Update
Thanks to @gregersrygg for his link and hints, this is a hackish way to solve the problem apparently cross browser and trustable ... still a hack!

// this save one char, how cool is that!
!function(document){

// (C) WebReflection (As It Is) - Mit Style

// we all love function declarations
function redraw() {

// clientHeight returns 0 if not present in the DOM
// e.g. document.removeChild(document.documentElement);
// also some crazy guy may swap runtime the whole HTML element
// this is why the documentElement should be always discovered
// and if present, it should be a node of the document
// In all these cases the returned value is true
// otherwise what is there cannot really be considered as painted

return !!(document.documentElement || 0).clientHeight;
}

// ideally an Object.defineProperty
// unfortunately some engine will complain
// if used against DOM objects
document.redraw = redraw;

}(document);
// tested with Opera, Firefox, Chrome, WebKit

... at the end of the day, nothing changed about this post because the summary is still that we do not have an official/standard way to do things properly and we need to use hacks to simplify our daily tasks.



so here the summary: there is no fucking way to use CSS3 transitions properly!

The Problem

We put everything on the DOM and we let nodes come in and out all the time or we are screwed!
CSS3 transitions are freaking cool and freaking painful at the same time.
The classic scenario is to pollute the whole DOM once or many times with our random appended/inserted HTMLElements.
The goal is to keep the DOM as clean as possible still being able to let things land on the DOM in our desired way.

setImmediate bullshit

Rather than improve the single setTimeout(callback, 0) classic case, the classic hack that supposes to make things work as expected and at the same time the classic timeout rarely stored as integer to clearTimeout later does not scale.
This is the reason vendors/HTML5 people/whoever decided to add another unshimmable function: setImmediate, which supposes to be used the same way "setTimeout with zero delay" is commonly used.
If vendors screw a bit up the same way FireFox did, where somebody pushed in some release a requestAnimationFrame without thinking about the counterpart clearRequestAnimationFrame, setImmediate becomes useless as well as setTimeout 0 is if nobody takes care of clearTimeout or clearImmediate function.

The Problem, Once Again

We appendChild a node with margin-left: -100px, a transition property equal to margin, and we add the className that supposes to put the margin-left: 0.
To solve this we need to appendChild the node then abstractly wait for the browser to paint and after, eventually, set the new class.

var div = document.body.appendChild(
document.createElement("div")
);
div.style.marginLeft = "-1000px";
// now we want the transition to marginLeft = 0 ... HOW?????

If by CSS3 above div has transition margin monitored, the only way to trigger the transition is to set a random timeout expecting that the browser renders in the meanwhile ... caus if it does not happen, we won't simply see it.

Why It Is So Hard

Unfortunately is most likely Web Developers fault, in the meaning that browsers are doing everything possible to optimize callbacks.
What we think is synchronous is just a matter of asynchronous, impossible to control, redraw actions behind the scene, where of course if we change 3 times classes to the same node browsers just consider last status and that's what they draw.

The Missing API

The more we have illusion of powerful features the more we screw up our architectures and layouts .... there's not much to do here except a bloody synchronous document.redraw() method, something that supposes to tell the engine: hey, I wanna you to consider the current layout so that after I can change it and you can consider these changes.

Will it be too hard for the CPU/GPU? Who cares, I mean, with all uncontrolled and theoretically optimized redraws/repaint operations we have every N milliseconds, how can a developer choice hurt so much?

mozAfterPaint

About Events, the cool part is that we can create them on JavaScript side and dispatch them whenever we want. Now, this paragraph notification is something we cannot fire in a meaningful way but it supposes to tell us ... exactly what? No idea, because the problem is that I do not want to control the status of the DOM via unpredictable setTimeout behavior, I wanna be able to tell the browser that status is OK now, would be so kind to draw the whole fucking thing for me, please?

As Summary

I hate lack of power under the HTML5 is for developers flag, I see setImmediate as a joke that I will be forced to use at some point for who knows which reason, but I still miss the possibility to properly control all these CSS3 transitions power via JavaScript, and once again, messing up between the view, and the controller.

Told'ya it was a rant!

Tuesday, May 24, 2011

setTimeout and setInterval with extra arguments ... once again!

Funny discussion today on twitter about "why on Earth IE still does not support extra arguments with setTimeout and setInterval" ... oh, well ...

The execScript Behaviour

Somebody in IE team thinks that the rest of the world should avoid extra arguments because of a bloody edge case as the third argument in IE is:

// ... seriously ...
setTimeout("Msgbox('WTF')", 0, "VBScript");


What IE Users Could Do

Well, rather than create a closure every bloody time we would like to reuse a function with different arguments, something posible 10 years ago via ActionScript 1, every web developer (and not only) misses the opportunity to avoid closures using a de-facto standard for some unknown reason not part yet of ECMAScript specifications.
For those interested I will show an example later, right now let's think about a solution compatible with VBScript for those mental developers, as I have been, brave enough to still use this language for some purpose.

setTimeout(function () {
// a closure for *this* edge case only
// rather than all cases "trapped" because of this!
execScript("Msgbox('WTF')", "VBScript");
}, 0);


Exactly The Same Behaviour!

Yes, if we use a string for setTimeout or setInterval this will be executed on the global scope, regardless where we defined this timer.
Accordingly, the latter example via execScript does exactly the same, since execScript executes synchronously on the global scope, and once trapped behind a timer, nothing change, same result .... happy? No, you are not!

The Classic Closure

The most common situation where we have problems is when we have a portable function defined somewhere else and we would like to use this function passing certain arguments there.

// somewhere else in the scope
function doStuff(obj) {
obj.stuffDone = true;
}


// later in our super cool application
setTimeout(
// the infamous closure
function () {
doStuff(myObj);
},
1000
);

The worst case scenario is where we would like to define timers inside a loop and unfortunately this is a truly common pattern that causes me repeated WTF tilt in my mind:

for (var i = 0; i < 10; i++) {
setTimeout(
// the double infamous closure pattern
(function(obj){
// the infamous closure
return function () {
doStuff(obj);
};
}(collection[i])),
1000
);
}


Closures And Scope Lookup Costs

Every time we access an outer scope variables we do a lookup in the ... well, outer scope. Every time we create a closure we pass through a function expression activation plus we create a nested scope that has to perform a scope lookup to access the outer function/variable.
Whenever this description makes sense or not, here the test you can try with not so powerful devices or mobile phones and tablet.
In my Atom N270 netbook that test is quite explicit: 50% less performances for each nested closure and its inline invoke.

Speed UP!!!

I have already described this pattern but I keep seeing too few developers adopting it.

for (var
createdOnce = function (obj) {
return function () {
doStuff(obj);
};
},
i = 0; i < 10; i++
) {
setTimeout(createdOnce(collection[i]), 1000);
}

Above example creates 11 functions rather than 20, which means we allocate and garbage collect loop + 1 functions rather than loop * 2.

Speed UP MORE!!!

The best part is that every browser I have tested but IE supports one or more argument with both setTimeout and setInterval.

for (var i = 0; i < 10; i++) {
setTimeout(doStuff, 1000, collection[i]);
}

How many extra/redundant/superflous closures and inline invoke we have created? 0.

How Difficult It Is

.. not at all.
It's pretty straight forward and it costs nothing for IE considering that you never bothered with this problem and you reached this point rather than skip this whole post at the beginning ... well, thanks for your attention :D , and this is your solution:

setTimeout(function (one) {
// only if not supported ...
if (!one) {
var
slice = [].slice,
// trap original versions
Timeout = setTimeout,
Interval = setInterval,
// create a delegate
delegate = function (callback, $arguments) {
$arguments = slice.call($arguments, 2);
return function () {
callback.apply(null, $arguments);
};
}
;
// redefine original versions
setTimeout = function (callback, delay) {
return Timeout(delegate(callback, arguments), delay);
};
setInterval = function (callback, delay) {
return Interval(delegate(callback, arguments), delay);
};
}
}, 0, 1);


Not Obtrusive

If we use above script at the very beginning of our web page there are extremely rare chances that the next script won't be able to use already the fixed version of setInterval and setTimeout for IE only.
If another script includes the same logic nothing will be redefined for the simple reason that variable one will be there so no double reassignment will be performed.
In the very safe scenario, considering we are inside our bigger outer scope created for our library, we can define those references as internal:

(function(){
// the beginning of our lib
var
setTimeout = window.setTimeout,
setInterval = window.setInterval
;


// the runtime check showed before ..

// .. the rest of the lib

// the end of our lib
}());

We may eventually decide to use some "isIE" check via conditional comments on our pages, since the solution costs nothing once minified, and have a normalized de-facto, fast, easier, behavior for every other browser.
Here the inline synchronous re-assignment for latter case:

(function (slice, Timeout, Interval) {
function delegate(callback, $arguments) {
$arguments = slice.call($arguments, 2);
return function () {
callback.apply(null, $arguments);
};
}
setTimeout = function (callback, delay) {
return Timeout(delegate(callback, arguments), delay);
};
setInterval = function (callback, delay) {
return Interval(delegate(callback, arguments), delay);
};
}([].slice, setTimeout, setInterval));


Update ... and As Summary

Of course the lookup is much faster than function creation, and this is the dedicated test but this post is about the summary of lookup and the classic closure creation historically used only because of this IE inconsistency.
Less lookup plus less closures around are faster, and numbers are there ( meaningful with slower devices )