My JavaScript book is out! Don't miss the opportunity to upgrade your beginner or average dev skills.

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!

11 comments:

Ben said...

Happy Monday! :)

The thing that makes JS different from every other programming language is that there's a fucking browser in the way. Oh, and a user. And rather more UX / madcap designers to deal with than in just about any other programming environment.

You put UX, Creatives, an (unknown) browser and a User (and heaven help us, a mobile device!) in the way of your code, it's always going to be a bit of a guessing game.

Which is why, you'll probably agree, "graceful degredation" is the keyword of our art. And at least CSS3 transitions, when they break, mostly break in a non breaking fashion.

So, a bunch of users don't get to see our slidey, fadey interesting stuff, but they do get to see it. It just magically appears. It's not cool, but if we code it right and let the browser boys and girls catch up, it'll look better one day, without us having to write another line of code. Is it worse than our friends on IE not seeing rounded corners? :)

gregers said...

Guess you're asking for a standardized method, and I totally agree that would be useful. I've used div.clientHeight to force a redraw in WebKit for the exact same purpose, but haven't tested if it works in other browsers yet. If clientHeight doesn't trigger redraw cross-browser there are lots of other methods to test out:
http://functionsource.com/post/dont-be-trigger-happy-how-to-not-trigger-layout

Andrea Giammarchi said...

@gregers and none of them standard

Andrea Giammarchi said...

@Ben, this blog is for all but users ;-) I don't care about UX as long as I can make it consistent, regardless who decided it ... this is about having the DOM under control, and in this case we are predicting, without controlling, behaviors. This is guessing, as you say, not programming, imho,

Dude said...

The purpose of requestAnimationFrame IS to force a repaint, except it's performed in an asynchronous manner and enables the rendering engine (WebKit) the ability to optimize and repaint / relayout when IT knows the RenderTree is dirty. See:

GPU Accelerated Compositing in Chrome

"clearRequestAnimationFrame" IS implemented as cancelRequestAnimationFrame, per the spec:

W3C Animation Timing spec: cancelRequestAnimationFrame

Additionally, while I would rather they "broke" setTimeout( ... , 0), the reason for setImmediate was to NOT break existing sites the depended upon the 4ms timeout clamping, From the timer documentation:

This is intended to allow user agents to pad timeouts as needed to optimise the power usage of the device. For example, some processors have a low-power mode where the granularity of timers is reduced; on such platforms, user agents can slow timers down to fit this schedule instead of requiring the processor to use the more accurate mode with its associated higher power usage.

W3C Timers
Chrome Issue: setTimeout( ... , 0) fires too quickly

and it IS shimmable via window.postMessage. Here's one:

https://github.com/NobleJS/setImmediate

For details on the shim technique see:

Faster Timeouts
Beating 60fps in Javascript

Hope all that alleviates at least some of your frustrations.

Cheers,
Adam Crabtree

Felix said...

What about a simple getCalculatedStyle on the margin? I think this enforces a repaint(?)

Dude said...

getCalculatedStyle will force a reflow/relayout, but not a repaint.

Andrea Giammarchi said...

Adam Crabtree, as I have said FireFox 5.0 has mozRequestAnimationFrame but no mozCancelRequestAnimationFrame, my mistake to write clear, in any case it is not there as well.

About this:
enables the rendering engine (WebKit) the ability to optimize and repaint / relayout when IT knows the RenderTree is dirty
So you are saying that the browsers knows better than me, developer, when it is time to repaint ... correct? Fair enough generally speaking, but I would like to be able to rule the engine as well, when and if necessary.

setTimeout(0) is broken by definition, being 0 totally inconsistent ... setImmediate is still asynchronous and a postMessage to shim it sounds ridiculous ... I would rather use postMessage for what it has been created for without making logic dirty.

Last, but not least, these are all asynchronous features but none of them is synchronous and can help to simplified or control the usage of transitions.

Thanks in any case for your links and your time.

Andrea Giammarchi said...

@Felix exactly, you guess, and no standards to do that in a proper manner cross browser.

Just consider projects such PhantomJS where nothing is rendered at all and you realize that what we get from an engine is not necessarily what we see on the screen ;-)

I will try one of these tricks to create a document.redraw() ... let's see if it makes sense

Anonymous said...

I was sceptic about CSS3 transitions from beginning, we can't control them as animations in JS.
I think they're good only for purely decorative stuff things that don't need to be controlled e,g. gradual color change on hover.

I think I wouldn't use CSS for animated elements repositioning. How would you know when animation started, when ended, how would you chain animations if needed (?)

as said...

in regards to the detection of finished renedering of inserted DOM elements.
we found out the best approach would be to reset a style property of the node before triggering the css transform

domObject.style.display = document.defaultView.getComputedStyle(domObject)['display'];

that helps without bothering about timers etc