3D-ifying Documents Using CSS Transforms

This article was originally posted on the Crocodoc blog. I am reposting here for persistence.

We recently launched a preview of Crocodoc’s newest document to HTML converter. If you haven’t checked it out yet, go play with our preview and see how we’re converting the pages of your documents to embeddable SVG and HTML.

What does the new converter mean to those of you building web applications using Crocodoc? Simple: your documents will load faster, look sharper, and be much easier to customize. Our preview page is full of interactive examples designed to help provide inspiration and showcase what is possible with the new Crocodoc: everything from a 3D page demo, showing off the many layers in a document, to a magnified view of an uploaded document, and a thumbnail that expands into a full-size inline document.

For this post, I’d like to focus on the 3D demo. I’ll explain how it was built, the various issues we ran into, and the workarounds we used to fix them.

Note: the demos in this blog post require a modern browser. If you're on a mobile device, you might need to click the open in new page button to view the demos properly.

Building the 3D Demo

The Crocodoc 3D Demo separates a document into discrete layers, which you can rotate so that you can see how the document is constructed. Not only does it look pretty cool, but it gives some insight into the processing Crocodoc is doing on each document that we convert to HTML.

For the purposes of this blog post, I broke the demo down into three steps: building a basic proof of concept (without SVG), getting it to work in our target browsers, and finally, adding the SVG layers.

Just want to play with some code? Fork the 3d-demo repo!

Step One: Proof of Concept

When starting the Preview project, the first step was to create a proof-of-concept for the 3D demo. The project needed to work on the target browsers (IE 9+, Firefox, WebKit, and most mobile browsers). I have worked with CSS 3D transforms in the past, but not to the extent that this project required, so it was time to do a bit of research! The WebKitCSSMatrix class that I discovered seemed like a good place to start. Fortunately, the proof-of-concept demo was actually quite simple to build using the WebKitCSSMatrix class. Let’s take a look at some code!

The Page

The Page class inserts layer divs into a jQuery-wrapped container element, and exposes a rotate(dx, dy, dz) method, which rotates the page in 3D space. Each layer div is positioned LAYER_SPACING apart on the z axis. From here, it’s easy to take it a step further, and add mouse/touch controls for rotating the page.

Demo #1: The Page

Adding Transitions

In browsers that support CSS transitions, adding transitions is very simple, and done almost entirely in CSS. The JavaScript changes are mainly adding and removing CSS classes. We can also add a nice implode/explode effect.

Demo #2: Transitions

Step Two: Browser Support

Awesome, we have a working, nice-looking proof-of-concept! Now, how do we get it to work on all browsers? It turns out, currently the only two implementations of W3C CSSMatrix interface (2D and 3D) are WebKitCSSMatrix (supported by at least Chrome, Safari, iOS, and Android) and MSCSSMatrix (IE 10 only). What now? Some googling lead me to a CSSMatrix shim, which looked promising, so I gave it a go. Unfortunately, the shim was actually broken when I found it, but hey–a chance to contribute to a useful open-source project? Sign me up! Long story short, after several hours of poring through Wikipedia articles and even the WebKit source, I finally got it working properly (for a browser-ready version to play with, check out the browserified branch and add CSSMatrix.js to your page).

CSSMatrix Example

With Firefox support handled, it’s time to tackle IE 9. Even though IE 9 can’t render matrix3d values in CSS, we can compute them in JavaScript and truncate a few numbers… which is exactly what we did. And it looks way better than you might expect! Check out the demo below to compare how it looks in IE 9 (which we’ll call affine mode) vs proper 3D mode.

Demo #3: Affine Mode

(NOTE: can’t tell the difference between the two modes? you’re probably using IE 9. No? It’s also possible that hardware acceleration is disabled on your machine, which you can check in chrome://gpu/.)

IE 9 also doesn’t support CSS transitions, but I’m not going to go into detail about how we did that, because there are already a lot of jQuery plugins out there that make it pretty seamless. (Here are a few places to start: jQuery.transition.js, jQuery-Animate-Enhanced.)

Now that the demo works well in all the browsers we’re targeting, we can just throw in the SVG file and we’re done! Right? Well, not exactly.

Step Three: Make it Work with SVG

After a bit of (failed) experimenting, I realized that, out of the box, SVG does not support CSS transforms with perspective. My solution was to load the SVG with AJAX (really CORS from AWS S3, which required yet another IE shim, and a cache-related hack for Chrome), fix linked assets (Crocodoc SVG links to assets relatively), apply a filter to split the SVG into several distinct SVG objects, and embed each object using HTML5 Inline SVG in a separate div elements. Voila, we have our layers!

Demo #4: SVG Layers

Since the SVG is now contained in HTML elements, we can apply CSS 3D transforms to the containers, and everything works swimmingly. Except in IE 9. Yep, IE 9 supports inline SVG, but it doesn’t support document.importNode(), which is necessary for creating the inline SVG in the first place. I found a nice shim, which I modified slightly, because it was producing some issues with namespaced attributes.

If you’re interested, also check out demos 5, 6, and 7 (warning, you might need a fast machine).

Note: I also ran into some very troubling issues with Firefox. Rotating the 3D demo would reproducibly cause a kernel panic on my MBP (Retina, Mid 2012). I unfortunately haven’t been able to create a reduced test case for this, but I’ll be sure to post about it when I do. It has something to do with applying 3D transforms to elements (possibly specifically SVG) in a overflow:hidden container.

Wrap Up

It’s true that SVG is still in its infancy in HTML, but I think our preview page alone proves that it’s already possible to do some incredible stuff. I hope this post inspires some great applications built on the new Crocodoc.

In my next post, I’ll explain the other demos on our preview page, as well as a few other issues we ran into.