The Zoom Effect

(Originally published on October 19th, 2009)

Before using Squarespace, I built my own front page. As I considered the best way to display series of pictures there, I came up with an interesting way to compress a lot of information onto fairly limited screen real estate. The idea was to have a kind of a slide show composed of small icons that turn larger as you hover over them; clicking on any icon would bring up with full-size image. That way I could fit a lot of small (32×32 pixels) icons of images on the screen, yet offer the users the ability to browse larger versions (67×67 pixels) easily just by moving the mouse around. The idea, of course, was inspired by what OS X does with the Dock (an effect which, sadly, I have disabled on my computer–but due to different use scenarios). Here is the effect in action (roll your mouse over the images):

 

The design process I went through is an interesting example of discovery (or serendipity, rather) and how taking an analytical approach doesn’t always yield the best results.

The desired effect will be very familiar to you if you’ve used OS X and the Dock. I want to display a series of small thumbnails of images in a row. If you hover over them, the image that your mouse is closest to gets larger, pushing out the other images if necessary. I wanted the effect to be smooth (so as you move your mouse over the row, images get bigger as they approach the mouse pointer, and then get smaller) and resemble something like this:

The Zoom Effect

The Zoom Effect

There are three variables that I need to be concerned about: how much to magnify the icons by (in my case, I wanted to go from 32 pixels to a maximum of 67 pixels), how far out the magnification should affect the icons (in the picture above, the icons two to the right of the center icon are no longer magnified), and how quickly the magnification should drop out (how “drastic” the magnification of the center icon should appear). For each of the icons in the row, I need to figure out how much to magnify them (by convention, let’s say that 1 is full magnification and α is the regular, small size) and where to place them horizontally (because they will push out other icons), subject to the constraint that the icons must remained aligned in a row.

An analytical solution was easy to get to, but very quickly spiraled out of control, and here is how. Let’s consider two configurations:

  • When the mouse cursor is exactly in the center of an icon, by symmetry that icon should have the maximum magnification:

Maximum magnification at center of icon

Maximum magnification at center of icon

  • When the mouse cursor is exactly in between two icons, also by symmetry both icons should be of equal size:

Equal magnification in between two icons

Equal magnification in between two icons

 Depending on β, the magnification will drop out quickly (if β is close to α) or slowly (if it’s close to 1).

Since we want the magnification of the icon to be a smooth curve (as the mouse pointer moves across the icons), we simply need to define a continuous function given the three points it goes through: (0, 1) (because atx=0 — i.e. when the mouse cursor is exactly over the icon’s center, we want the magnification to be maximum), (α/2, β) (because when we’re in between two icons — i.e. a distance α/2 away from the center of one — we want the magnification to be β) and (Z, α) (the distance at which all magnification ceases). An exponential curve is the simplest one that we can try:

Magnification as a function of distance from the icon's center

Magnification as a function of distance from the icon's center

 We will then be able to use this curve to determine how much to magnify each icon by. The icons will be sized such that their size given the distance between their center and the mouse pointer can be read off of that magnification curve:

Applying the magnification curve to each icon. Past the point Z all icons retain their original, small size

Applying the magnification curve to each icon. Past the point Z all icons retain their original, small size

 

First let’s figure out the full form of the magnification curve. The curve must go through the two endpoints we identified, and be exponentially decaying, so it is of the form

\[y = 1 - \left(\frac\right)^P\cdot(1-α)\]

(We can verify that at 0, y=1 and at Zy=α). We need to compute P based on the third point:

\[β = 1 – \left(\frac\right)^P\cdot(1-α) \Rightarrow P = \text\right)\]

The first icon is simple: determine the distance between the mouse pointer and the center of the icon and use the curve above to read off the magnification (it will be something between β and 1). The subsequent icons are a little more tricky, because in order to figure out the magnification you have to know how far its center is from the mouse pointer, but the position of the center is a function of magnification! At this point the easiest thing to do is to solve this numerically, by simply iterating over all possible positions of the center and determining the closest one (since we’re operating in a discrete space with the smallest effective resolution of 1 pixel).

While each step seems fairly straightforward, the end result is a pretty big hairball. Being lazy, I realized that there must be a better solution to this problem.

And then I realized that so long as the illusion of smoothness is preserved, some simplifying assumptions can be made. First of all, the exponential curve I used initially was too complicated and looked too discontinuous at large magnifications (because of a sharp spike near 0); there must have been something else that’s straightforward to compute. The parameters seemed complicated, too — α and Z could be replaced with just one — a measure of how quickly the magnification should decay — without much loss of the effect.

The Normal curve came to mind — with just one parameter (σ) it was much easier to experimentally determine a value that had a pleasing effect (plus, σ is by definition very close to our notion of “how quickly this should decay”). I also got rid of the self-referential problem (determining magnification requires knowing origin, but origin influences magnification) by looking at not the actual distance (how far is the icon from the mouse pointer after all icons have been magnified), but original distance (how far is the icon from the pointer before magnification).

The resulting algorithm is much more elegant — and produces a more visually pleasing effect:

  • For each icon in the original (i.e. before any magnification takes place) series, determine how far its center is from the mouse pointer (I experimented with just using the x-coordinate, but the nice thing about this algorithm is that any smooth function works, and the actual distance produced a nicer effect than just the horizontal distance)

  • Use the Normal curve to determine its magnification. We want the result to be 1 if the distance is 0 (i.e. the icon is directly under the mouse pointer) and α if the distance is infinite (since the Normal curve dies off quickly, the size would go down to α pretty quickly as well), i.e.

\[N = e^\]

\[M = N+α(1-N)\]

  • Place each icon with its magnified size on screen; keep track of how much space each icon took so that subsequent icons can be displayed after it and not on top of it

  • Technically this is enough for magnification. However, this doesn’t produce a smooth effect: since the icons are always pushed out to the right, the “tail” of icons keeps traveling back and forth. We want the entire series of move smoothly, slowly to the left as the mouse moves to the right (go here and watch the icons at the end of the series travel to the left as you move your mouse pointer left to right, across the icons). This is simple to correct, though: keep track of how much space all the icons take (by adding up each size as you go), and then offset all the icons by a fraction of that total space, depending on where the mouse pointer is: suppose the icons originally take d pixels, and expanded they all take D pixels, and the mouse pointer is at position x (between 0–at the beginning of the series–and d), we want to offset all icons by

\[x\cdot\frac\]

See also