Bubble Browser’s Fisheye or How to Make a Dock-like Control for iOS

When we decided to create Bubble Browser for the iPad, we didn’t want to blatantly copy the OS X version to iOS. We desired to create a brand new app, tailored specifically for touch screen. There’s no doubt that typing on the iPad is cumbersome, so it’s not uncommon for iOS apps to display alphabetical indexes that make narrowing a selection easier. Unfortunately, those indexes are mostly static and dull (as seen in the Contacts app)—no wonder we didn’t want to go that way. Instead, we’ve opted for something fresh, dynamic, and extremely interactive—the fisheye.

While the concept of the fisheye itself is not entirely new and everyone using OS X knows this effect from Dock, it seems completely underused in touch devices. We decided to bite the bullet and just roll out our own.

Fisheye in Action

You can checkout the fisheye from our GitHub repo, it will make following this post much easier.


The Two Spaces

After careful examination of OS X’s Dock I realised that the fisheye lives two disconnected lives, simultaneously, in two different spaces. The first one is a logical space in which measurement and touch events take place. The logical space determines which item is considered highlighted when the mouse cursor moves around.

The other space in which the fisheye lives is a visual space. This is what gets presented to the user; this is a space in which items are enlarged and laid out dynamically. The only time logical and visual space match up is when the fisheye is collapsed and all its items are shrunk down.

The division between logical and visual isn’t something new to iOS developers. Think of a UIButton with a small image but a large frame—it seems small, but its tap target doesn’t correspond to its visual representation. A more contrived example would be overwriting UIView‘s -pointInside:withEvent: and doing some special testing or even overwriting the -hitTest:withEvent: method and returning a completely arbitrary view.

Enlarging Items

One of the key features of the fisheye is that it enlarges its items near the touch location, making it easier to see the content. What determines the growth factor of nearby items is the expansion function—it describes scale adjustment for a given distance from the current touch location:

expansion function

An item whose center is exactly at the touch location will have the maximum enlargement, but the further away we go from the touch point, the smaller the enlargement is. Starting at a certain distance, the scale remains the same—those items don’t get enlarged.

By moving our finger around, we change the origin of the expansion function, causing the bulge to change its position. Since the items are static but the function is moving, the distance of the items from the function’s origin does change, causing different elements of the fisheye to enlarge. All the distance measuring happens in logical space. Visually, however, the items at the sides get pushed away from the center, and the total length of all the items in their expanded state is larger than it is in their collapsed state.

The Example

Let’s take a look at all this action in practice. I’m going to assume that, collapsed, the fisheye’s length is 450 points, and it has nine items. This means that in logical space, each item has a width of 50 points.

To make things simpler, let’s pick an enlargement function with a triangular shape. Positioning the origin exactly at the logical center of an item causes this item to receive a maximum enlargement. Its direct neighbors get enlarged as well, but on a smaller scale, and the rest of the items are kept at the 1.0 scale:

triangular shape

The total visual length of the items is easy to calculate: there are six items at a 1.0 scale, their length is 6*50 = 300, there are two semi-enlarged items at scale 1.5, which adds another 2 * 1.5 * 50 = 150, and one gigantic item at 2.5 scale adding to the final 2.5 * 50 = 125, for a grand total of 575.

Moving the finger slightly to the right, between items, causes two of them to get enlarged, and the rest remain small:

triangular shape moved

What’s the total length in this case? There are six non-enlarged items giving 7 * 50 = 350 points and two enlarged items at 2.0 scale adding 2 * 2.0 * 50 = 200 points. The grand total is 550.

The following four pictures depict what happens when your finger moves to the right at the interval of half the logical item’s width (25 points). Take a look at the position of the first and last item. It fluctuates a lot. It sucks.

moving finger around

Varying the total length of items in an expanded state is what causes this fluctuation. I was completely astonished when I realised that the shape of the expansion function can not be completely arbitrary! The function must have what I call a constant-discrete-probing property: given a set of discrete probing locations, the sum of the values of the expansion function at those points must remain constant, (assuming the bulge stays within those points). In other words, the sum of the heights of the blue bars must be constant:

enter image description here

If this condition is not met, then the fisheye’s items will swim around with every move of the finger, causing a very unpleasant visual effect. The simple triangle function doesn’t work since (as we’ve calculated) total length varies between 575 and 550.

I’m not aware of any rigorous mathematical requirements that a function must pass in order to maintain its “constant-discrete-probing” property, so I decided to put in some elbow grease and manually test some functions instead.

What I settled on for Bubble Browser is the trapezoid function. It has a nice property of keeping highlighted items at a constant scale, it looks well when scaling, and it can be easily tuned with just a few parameters.

Why Our Fisheye Doesn’t Work Like Dock

The difference between our fisheye and OS X’s Dock implementation is the “expanded passive” state in which a single item is selected and all the other items are collapsed. Initially, we simply wanted to mirror OSX’s behavior, namely keeping neighbors expanded, but it proved to be very unintuitive to use. The crucial difference between the Mac and the iPad is a mouse. On a desktop, the user can’t “lift” the cursor and magically put it at the other location—there are no discontinuities in its movement. On the iPad however, nothing prevents the user from lifting a finger in one place and placing it down on a completely arbitrary area.

As discussed before, the expanded fisheye’s visual representation does not match underlying logical placement. The difference between those two means that while it seems that an item is at a given place, touching that place would not select this item, as its visual position is merely an illusion that does not correspond to its logical placement:


This is why we’ve opted for collapsing items into their logical placement when the user lifts his finger. By doing so, we ensure that touching an item inside a fisheye will enlarge precisely this item.

Usability on Touch Devices

What’s so special about the fisheye is that, unlike table view, it displays all the content at once, without the need for scrolling items on and off screen.

Since fisheye interaction is based on panning instead of tapping, it doesn’t really matter if its items are small. While it’s really hard to tap an item that has a size of 10 points, it’s very easy to slap a finger on a screen in a nearby location and just drag it slightly to select the correct item, especially with instantaneous visual feedback of what’s going on.

Finally, the fisheye is so interactive and fluid that it’s a joy to use on its own, breaking the boring scheme of static user interface.

The Srcset Attribute