OdeToCode IC Logo

Zooming and Panning with CSS and Script (BYOSP Part 9)

Monday, March 5, 2012

I had very simple requirements for the zoom and pan style animations. I wanted the animations to work by zooming from element to element based on the presence of a data- attribute (indicating step 1, step 2 . . . step N).

<section data-animation="zoom">
    <h1>Zooming to Elements</h1>            
    <div data-zoom-step="1">
        <img src="images/colorful.jpg" />
    </div> 
    <div data-zoom-step="3" 
         style="-webkit-transform: rotate(10deg);">
        <p>Random words</p>
    </div>            
    <div data-zoom-step="2" >    
        <p>Vertical words</p>
    </div>            
</section>                

I also wanted the animations to rotate the viewport to match the rotation of a zoomed element. To see the final product, you can watch the following clip:

I thought my requirements were simple, but it's always the simple requirements that sneak up on you...

Rectangles and Matrixes

To make a long story a bit shorter, there are several things I learned in trying to hack out my own zoom and pan features.

1. getBoundingClientRect, part of the CSSOM View Model, is particularly useful for finding the precise boundaries of any given element.

2. Also useful is window.getComputedStyle, because getComputedStyle can give back a CSS transformation matrix which is easier to manipulate than the long-winded form of a CSS transform. In other words, given the following CSS . . .

#target
{
    -webkit-transform: rotateZ(10deg) scale(0.8) translateX(10px);
}

. . . then the following code . . .

var styles = window.getComputedStyle(target);
var transform = styles["-webkit-transform"];

. . . will give you a value in transform like [1]:

"matrix(0.7878, 0.1389, -0.1389, 0.7878, 7.8784, 1.389)"

 

From there you'd think everything is just multiplication and addition [2], but by the time you factor in the vendor prefixes, scroll offsets, aesthetic padding and easing, and how coordinates change as the viewport transforms, then the edge cases cut like knives.

Using Zoomooz.js

Zoomooz is an incredibly easy to use jQuery plugin for zooming to any element on a page. Once I found Zoomooz I turned over all responsibility for zooming, panning, and rotating to the library and focused on shimming it into my slideshow framework. After spending a few hours fiddling with stuff in the above section, the following code took 10 minutes or so.

var zoom = function (element) {
    var target = element;
    var signal = completionSignal();

    var zoomables = _.sortBy(
        target.children("[data-zoom-step]"),
        function (child) {
            return -($(child).data().zoomStep);
        }
    );

    var step = function () {
        var child = $(zoomables.pop());
        if (child.length > 0) {
            child.zoomTo(child.data());
        } else {
            signal.resolve();
        }
    };

    var reset = function () {
        $("body").zoomTo({ targetsize: 1.0 });
    };

    signal.promise.always(reset);

    return {
        step: step,
        done: signal.promise
    };
};

The code plugs into the animation infrastructure described in part 8. During initialization the code finds all the data-zoom-step elements and orders them into an array using Underscore. The zoomTo method is provided by Zoomooz.js. The code also uses the completion signal handler (also discussed in part 8) to register clean up code and announce when all zoomable elements have been visited and the animation is complete.

Although this isn't everything needed to pull off the animations, you can find all the source code on github.

Next up: pretty printing code.

[1] Least significant digits removed.

[2] Also see Sylvester.