Mittwoch, 11. Januar 2012

Polaroid Photo Stack with Animation

This "Show me code or it didn't happen" features the rotated photo stack and its animation as seen at Nachbarschaftsauto or in this video.

Right from the first time we discussed the idea to show featured cars in a polaroid photo stack with flipping animations I fell for it. This gently rotated stack reminded me on a project I've worked before where we represented photo-albums and folders with a pile of pictures. I always liked this way of represent content. But looking for such a jQuery plugin I couldn't find anything that was half-way decent looking nor programmed well. Most snippest were made for photo-albums and didn't work on div-elements or did some really bad canvas-hacking to rotate things, making it impossible to animate it nicely nor include links in the code.

So I took the only thing I could do: take the one that looks best, read the code and write it yourself. As most of them, this one wasn't using the latest technology nor was it backwards-compatible, but at least it had some rotation and the animation in a way I wanted it to be. Lucky me, I had a designer to my left, helping me out with the Polaroid and stuff. Most of that is very straight forward. But there are few pitfalls, which took me one hour or the other to figure out. This article is about those insights we've won working on this particular part of the project.

To make it easier for others to find and understand the code used for rotation and animation, I created a little gist containing only the relevant parts of the project. I assume you can read and understand HTML, CSS and Javascript and therefore won't explain every line of it, if you have questions for one line or the other just use the github comments features and I'll gladly answer them. Over here I want to go over some bigger and smaller issues, work-a-round or hacks (on HTML I am never sure which it really is) we had to do to make it look and work as it does today.

With this restructuring we also took the chance to replace the template with a fresh HTML5Boilerplate. You'll see later how this helps us fix the lack of css-transform in Internet Explorer < 9 by using the included Modernizr. Aside from that we also use less now to structure our CSS internally. I won't get into detail on how they work, but will explain all areas necessary to understand fix the particular bugs. Let's start from the top.

Position and Rotating
This first problem is to rotate the stacked photos.  For that we have a container around our div-elements, that we give a position: absolute css style so that we can position our elements inside above each-other using position: relative; top: 0; left: 0;.  Now all divs are "sitting" on top of each other. So far so easy. We could add the rotation here as well, but we wanted them to be stacked randomly otherwise it feels to fake, if they have the same position every time you reload the page. So the rotation happens in javascript.

For rotation we take the css-transform-feature called the same way. you specify it by using setting transform: rotate(13deg); in HTML5. As soo many other features of HTML this has been around for a while before it was part of the official specification. So for older firefox and gecko-based browsers you also want to specify -moz-transform, -webkit-transform for safari and chrome and -o-transform" for Opera. Even Internet explorer 9 knows this if you set it as -ms-transform. Unfortunately, jQuery checks the items passed to the css-function and the version we are using there (1.5.2) doesn't know this property. It allows msTransform though...

Old Webkit rendering issues
Now, this rotates our stack easily in a lot of modern browsers already. But on some old webkit browsers the edges appear like huge stairs, no smoothening happens. For that I found a little work-a-round. It seems there are multiple rendering modes in webkit browsers for css-transformations. The newer one supports is more advanced, it supports 3D-rendering and uses graphic acceleration. On old webkits you can smoothen out those edged by triggering that mode. This happens here by setting the style -webkit-backface-visibility: hidden.

And IE < 9.0
I wish we could ignore that one. Because it is nasty. But it turns out, almost 15% of your users use IE < 9. Sucks to be me, but what can I do about it? The most common method to get around the lack of css-transform support in InternetExplorer is by using the DXImageTransform-Filters. Though the name suggests otherwise they work on any DOM-Element, not images only. Sadly, the Rotate-Filter only rotates in 90° steps, so we have to use the much more general Matrix-Tranformation. But matrix-stuff is hard, so we - like many others, too - fallback to a libs to do the heavy lifting for us there. It is called pb.transformIE and uses the sylvester library to do some math. Both are included in the gist.

Now we only want to have these libs loaded, when cssTransform isn't there. For that we use Modernizr. That is very important because pb.Transforms is a bit older and the "am I run in IE"-Part on the top breaks on IE 7 and 8 for no good reason. By using modernizr, the code is only inlcuded if csstransform is missing in the first place. So we can outcomment that part and don't have to worry about that bug. Now, in case TransformIE is loaded, we tell it to look out for any changes on .polaroid elements. Now we can trigger transformIE by setting the -sand-transform style to our rotate, too. It understands and translates it into DXImage-Matrix-Transformation. And we have rotated images.

But the position...
Yeah. There is one major difference, though: when you rotate use the DXImage Matrix method, the point around which the rotation happens is on the top-left, while in css-transform it is in the center of the object. So the polaroids in InternetExplorer don't look stacked but fanned out. That is not what we want. To work a round that problem, we use another difference between those methods: In css-transform it is defined that the width and heights stay the same while in DXImage-Transformation the size changes as it is always calculating the pixels per row from the perspective of the screen and not of the element. This results in bigger elements when rotated with DXImage-Transform.

Using this information we don't only know whether it was rotated wrong, we also know pretty exactly where to move it to make it appear as if it was turned correctly. To use this, we calculate the size and position of the element before the transformation is applied, then we apply the transformation, check the size again and adapt the top and left style if necessary. All this happens, everytime $(x).rotate(deg) is called. This way it also takes care of transformations that changed the size before this one. That happens everytime the animation stops, turns the image and puts it back into the stack.

But this one text doesn't rotate
Looking closely at the rotated elements, one of the texts isn't rotated correctly. It isn't rotated at all actually. At least not as the other texts are. Though it is the same kind of child element of the div that gets rotated. And everything else rotates correctly. Turns out there is a a bug in the Internet Explorer 8 with DXImage-Tranforming: the child-elements can't be positioned or they aren't transformed. Using: margin-top: -1px instead of top: -1px fixes the problem. Weird though...

Now. Animate. Animate, I say. Stupid Internet Explorer...
What's now, internet explorer? Why you don't animate the photo stock. All others do. It is just a simple  margin-left and -top set with jQuery-Animate, then we replace the position in the DOM - to make it go to the back of the other polaroids - turn it back and animation the margin back to the original position. You can do margin-setting, can't you? Turns out, you can, but you can't properly read it. If it isn't set explicitly, you return auto instead of the correct 0. Well, then let's add a check for that, too (line 80/81).

Strechting the body
Eventually, we have our animation now. Simple. Elegant. Aside from the stupid body. Since we are moving the element a far way by using the margin, it runs out of the browser-window quickly. Totally okay, aside from the fact that the body stretches out during the animation so that the ugly scrollbar appears, grows with the animation, becomes smaller and disappears again. Gooosh... soo ugly.

But don't worry, uncle Ben has a solution for that problem, too: setting width: 100% and overflow-x: hidden for the body makes the elements move out without the stupid scrollbar. Bad though that it also means very small screens, like the iPhone don't allow you to scroll to the side though 60% of the content is located there. The better way is to set min-width: 960px instead of width. That way we receive all the space the screen gives us but at least 960px for scrolling. But after the screen we hide the content - no scrollbar for elements outside of that. As it happens during our animation.



Unbelievable that such a simple looking feature can trigger so many bugs and incompatibilities. Most of the code I wrote now really is for working around various problems and bugs, while the actual code is just  fraction of the code. Javascript really is a language written to work-a-round Browser bugs. The only way to make our lifes easier is by documenting them. I hope we save somebody some trouble with those insights. If you have any questions, just let us know.



Keine Kommentare:

Kommentar veröffentlichen