Thursday, February 26, 2009

WebKit CSS Animation behavior weirdness

Okay, not weirdness. I guess more like (up until now) undocumented behavior. Due to a workaround that I needed to implement in order to properly clear out CSS properties when starting a new transition in my hacked-out S5 presentation, I filed Bug 23528: CSS transitions on opacity fail when used with visibility in certain circumstances.

When performing a transition, I had expected that a function like the following (simplified for demonstration) would work:

/**
 * 1. take the element,
 * 2. make it visible,
 * 3. yet transparent,
 * 4. then set the transition,
 * 5. and then have it fade from opacity of 0 to 1 over one second.
 */
function fadeIn() {
    // <span id="one" style="visibility: hidden;">testing</span>
    var theOne = document.getElementById("one");
    theOne.style.visibility = "visible";
    theOne.style.opacity = 0;
    theOne.style.WebkitTransition = "all 1s ease-in-out";
    theOne.style.opacity = 1;
}

Instead of fading in over one second, the span instantly appears, as if the last three lines of the function didn't even exist. This happens because CSS changes don't take effect when you assign new values. They queue up, taking effect once the JavaScript thread of execution ends. This makes sense for static changes, so that if you have a whole load of changes to apply to the DOM where many of the changes end up canceling out by the time the thread ends, the browser has no reason to apply each one of them to the DOM just in order to remove the change in the end. It also makes sense in a pure performance sense, since this keeps the JavaScript execution moving along at a nice, steady pace, rather than having to wait for the DOM to apply the change before moving on to the next step in the process.

Unfortunately for the code example above, it means that since the DOM node starts out completely opaque, the last three lines of the fadeIn function do effectively fall off and the span simply becomes visible. In order to get around this, the last couple of lines have to run on a different JavaScript "thread" so that it splits up the batched DOM changes into two parts and the node properly fades in:

function fadeIn() {
    // <span id="one" style="visibility: hidden;">testing</span>
    var theOne = document.getElementById("one");
    theOne.style.visibility = "visible";
    theOne.style.opacity = 0;
    setTimeout(function() {
        theOne.style.WebkitTransition = "all 1s ease-in-out";
        theOne.style.opacity = 1;
    }, 0);
}

Labels: , , , , ,

Tuesday, September 02, 2008

S5 with CSS Slide Transitions in WebKit

So, in an effort to have the ability to post my slides for my Zend/PHP Conference talk online without having to rely on Flash or typically awkward HTML exports from other formats, I've decided to go with S5, as mentioned previously. I've also decided to add a little of WebKit's recent additions of CSS transforms and animation support for transitions.

Since CSS CSS animation only occurs when attributes change, it just means that the current and next slides need to have the changes applied at different times, or at the same time, depending. To make managing these things easier, I started with a base object:

function Transition() { }
Transition.prototype = {
 "duration" : 1,
 "cslide" : null,
 "nslide" : null,
 "transition" : function(toggle) {
  this.cslide.style.WebkitTransition =
   this.nslide.style.WebkitTransition =
    (toggle
     ? 'all ' + this.duration + 's ease-in-out'
     : 'all 0s none');
 },
 "prepare" : function() { },
 "perform" : function() { },
 "cleanup" : function() { },
 "run" : function(cid, nid) {
  this.cslide = document.getElementById(cid);
  this.nslide = document.getElementById(nid);
  this.cslide.style.visibility = this.nslide.style.visibility = 'visible';
  this.transition(false);
  this.prepare();
  var dis = this;
  setTimeout(function() {
   dis.transition(true);
   setTimeout(function() { dis.perform.apply(dis); }, 0);
  }, 0);
  setTimeout(function() {
   dis.transition(false);
   dis.cslide.style.visibility = 'hidden';
   dis.cleanup.apply(dis);
  }, dis.duration * 1000);
 }
}

The code managing the switch of the slides now calls RunTransition(transitions[snum], cid, nid);, where transitions just holds an array of constructor references corresponding to the slide numbers (snum):

var transitions = [
 null,
 Fade,
 Wipe,
 Rotate,
 Rotate,
 Fade
]

function RunTransition(className, cid, nid) {
 var trans = new className();
 trans.run.call(trans, cid, nid);
}

Each transition just extends Transition and overrides the applicable methods in order to change the slide CSS properties at the correct time, like this Fade transition:

function Fade() { }
Fade.prototype = new Transition;
Fade.prototype.prepare = function() {
 this.cslide.style.opacity = '1';
 this.nslide.style.opacity = '0';
}
Fade.prototype.perform = function() {
 this.cslide.style.opacity = '0';
 this.nslide.style.opacity = '1';
}

I've uploaded a very quick demo of the transitions in action, which will work in whichever builds of WebKit support CSS transforms and animation. It worked a bit smoother in WebKit builds a few weeks ago than those current (bugs galore at the moment), but nightly builds always tend introduce unpredictable behavior.

Update (1/25): Between fixes in the WebKit trunk since this post, and fixes in my code (with many thanks to smfr in comments and on the bug itself), all of the transitions work in the current Webkit nightly! From my final note, just in case anyone else has issues with transforms when used with transformations:

"If you set the WebkitTransform using just matrix() in the first step, then set the WebkitTransition, then set the WebkitTransform using both matrix and scale, it (reasonably) will not apply the transformation. When I set both both functions for the start and end styles, it works beautifully."

Labels: , , , , ,

Tuesday, May 06, 2008

Opera Dragonfly (alpha) Released

Over a year ago, I wrote about the Opera Developer Console. This morning (for me, at any rate), Opera posted Dragonfly, which (two years in the making) offers a completely fresh look at browser-based debugging.

An Opera Dragonfly window showing a JavaScript console, stack trace, and active debugger, stepping through a call to add an event listener

It offers most of the familiar tools for DOM inspection (along with a nice DOM editing capability), error logging (with the same granularity as before wrapped in a more polished UI), a JavaScript debugger that rivals WebKit's Drosera, a JavaScript thread logger, and a lot more that I haven't explored yet.

Time will tell whether Dragonfly can get enough developers to use Opera and keep them there, and how much the developers behind the new developer tools listen to the community in the coming iterations, but so far this looks extremely promising.

Edit: Chris Mills from Opera gave me some additional info:

Part of the reason I haven't found the XMLHttpRequest logger/debugger apparently stems from it not getting exposed yet, though it will appear in an upcoming iteration, along with HTTP header inspection (not just for XMLHttpRequests, of course) and a new "single window mode" which sounds like it will make things much more usable! When using Safari's inspector, I almost always find myself attaching it to the window, and I always had the urge to do the same with Opera's developer console.

Chris also mentioned something which makes me very happy to hear: even though the Developer Tools (loaded via the Tools → Advanced → Developer Tools) download from Opera's server, it only does so the first time, and for each following update of the tools. This ensures that it not only saves Opera's servers when usage takes off, but it also ensures that developers can work offline, and the slow loading of the tools will only happen initially, loading from the local drive after installation.

Written in XML/CSS/JavaScript, Dragonfly will run in all browsers including Opera's Core-2.1 rendering engine (except Opera Mini for some reason), and even supports debugging on mobile devices by way of a proxy setup between the device and the desktop Dragonfly installation! This will prove invaluable for developers of web applications supporting devices, as they will have the ability to use their normal desktop tools to debug on the mobile browser without having to use emulation.

You can find more information in an Introduction to Opera Dragonfly and on the Opera Dragonfly product page (which has a very good start of fleshed-out documentation already, feedback and bugtracking, and of course a blog).

Edited to fix the Introduction to Opera Dragonfly link...

Labels: , , , ,

Monday, March 12, 2007

Opera Developer Console

It figures I would find this as I wrap up the initial draft of the chapter on client-side debugging, but it makes me extremely happy to see it: Opera has started work on a Developer Console, which (among many other things) logs XMLHttpRequest calls.

An Opera debugging window showing HTTP, Cookies, and an XHR logger.

The Opera Developer Community runs through some of the DOM, JavaScript, and CSS tools, and the Opera Desktop team posted about it over a month ago when they initially started putting it into their development builds.

From what I can tell by peaking at the source (since, like most Opera extensions, they wrote it in JavaScript), it looks like they made a simple wrapper object and replaced the native object (after storing a backup reference for when logging gets shut off again) in order to create hooks into each of the events so it can report everything as it happens.

This means that I can now debug XMLHttpRequest usage in more than one browser, which makes life much easier than either guessing how it went or using a standalone traffic sniffer.

Edited two minutes later to add: you can get it from the Opera Developer Tools page.

Labels: , , , , ,

Thursday, January 25, 2007

CSS3

I know we still have quite a bit of lobbying and pestering to go before IE7 version X supports even CSS2 in its entirety. That said, the Opera team has continually updated their progress in support for CSS3. Several browsers, at this point, have adapted rudimentary versions of CSS3 columns support, as well as transparency, shadows, and background manipulation.

The latest post on Opera's CSS3 support has some very promising aspects to it, considering that functionality such as alternating table row styles, special formatting of empty elements, all off the of-type rules, and many more have the potential to reduce the workload of designers and developers by quite a bit. I know that I, for one, have emulated most of those mentioned using extra markup, scripting, or both throughout the last few years...

Labels: ,

Saturday, January 06, 2007

Inline Image Replacement (warning: hack alert)

In the process of designing the UI for indicating user's progress in a tabbed form, I realized I needed inline image replacement in order to accomplish exactly what I needed. The pure CSS solution works in Mozilla, Safari, and Opera, and should work in IE7 with some tweaking. IE6 will only work with JavaScript in place to convince it of what it should do.

The hack comes in, since I have only tried this with one set of transparent PNGs and this will probably take a fair amount of pounding on before it can stand up to wide usage. It also (for now) requires a solid background color.

Nevertheless, IIR. Suggestions actively encouraged... So far it works transparently (no pun intended) with screen readers, though the replaced text does go missing without the image there.

Labels: ,