When we set out to design the first two digital magazines at Yahoo, Yahoo Food and Yahoo Tech, we knew we wanted an immersive experience with the content front and center. We saw these traits in the design for Flickr, where large images are shown in an expansive grid view:
We decided to build the base of our magazines around this grid, but our designers wanted to take it one step further. Just as the content in our magazines is curated, we also wanted the layouts to feel hand chosen. Our designers devised a series of options and finally we came to a new row type where there would be one large tile that seamlessly punches through two normal rows (our team has become accostomed to calling these sorts of tiles “double talls”). Here’s an example of this new row type as seen in Yahoo Tech:
In order to maintain the feeling of a magazine-inspired layout, we knew we couldn’t take a shortcut and programmatically crop images to fit in our layout. Instead, we had to come up with a method to perfectly size and position tiles to fit in our grid.
I knew what my goal was, but I wasn’t quite sure how I was going to end up there. So I sat down and started drawing (I actually picked up a pen!) the double tall layout on paper. Our base grid was pretty easy to reason about and come up with a simple iterative algorithm: add tiles to the row until the row is too wide and then resize the tiles to fit perfectly. But this layout was different — there were too many interdependencies between the sizes of each tile. For example, increasing the size of the big one causes the rest of the tiles to change size, but by different amounts depending on which row they’re in and their particular aspect ratios. This new layout seemed complicated enough that I wouldn’t be able to stare at it and come up with a layout algorithm in my head.
I wanted to feel like I was making progress, so I made my next goal to write as many facts about the layout as I could. I looked at my diagram and started writing down equations, until I realized I had written down enough information to obtain a closed-form solution for the layout. A few hours later, I typed out these 5 lines of code:
var h_c = (q_b - q_a + r_a * p + (r_b + r_a) * ((p + q_a - w) / r_a - p)) / (-r_b - r_b / r_a * c - c); var h_b = (p + q_a - w) / r_a - p + (1 + c / r_a) * h_c; var h_a = -p + h_c - h_b; var w_c = c * h_c; var w_ab = q_a + r_a * h_a;
Don’t worry about the specific variables on the right side of the equations; the details are in the linked paper below. The important point is that the five equations represent the height of the double tall tile, the height of the bottom row, the height of the top row, the width of the double tall tile and the width of both of the subrows. With these equations solved, I could now implement the new layout using the same logic as in our existing Flickr-style layout code.
So how exactly did I get to these equations? I realized when I was sketching and writing down constraints, that I had enough constraints to solve the system of equations for all of the variables. I’ve written up this approach with a full explanation of the variables and constraints in a paper that you can find here (warning: it contains a small amount of linear algebra): Breaking Row Boundaries in a Fully Justified Tile Layout
An Alternative: a Constraint-Based Layout System
Instead of manually coming up with a closed form solution, I could have avoided solving these equations altogether and expressed the constraints directly in code using a constraint-based layout system. To make this easier to explain, here’s the diagram of our new layout from the paper:
For example, to enforce that all tiles of a row are a given height, we would say that a1.height must equal a2.height, and a2.height must equal a3.height, and so on. We can even introduce other quantities like padding into the equations: a1.left should equal c.right plus padding. Once you’ve declared all of these relationships, the constraint solver will tell you the numerical values of things like a2.height and a3.left.
This is a very powerful idea, and is particularly useful when there are many subtle layout variations and edge cases. In fact, Apple added a new constraint-based layout system in iOS 6 that’s based on several papers from the Cassowary constraint solving toolkit.
In the end, I decided that using a full constraint-based layout system would be overkill for this project. First of all, our default Flickr-like grid isn’t expressed well with constraints; we dynamically choose how many tiles to put in a row while we’re calculating the layout. A constraint-based approach would have us “guess” at different row compositions and then choose the best one. Second, we have extremely tight time budgets to calculate layout: we need our app to feel extremely responsive across a wide variety of devices and we need to lay out new rows while the user is scrolling.
As an experiment, I compared my closed-form layout solution to a constraint-based solution that used a Javascript implementation of Cassowary. On my local iMac, the constraint-based solution was able to lay out 171 rows per second, while my closed-form solution was able to lay out 735,000 rows per second. Results like these aren’t surprising; a constraint solver uses numerical methods to solve for arbitrary constraints, while our double tall rows have one very specific set of constraints. While 171 rows per second seems like plenty, we currently have plans to dynamically perform different candidate layouts and then choose the ideal layout. By ensuring that we use a very performant layout mechanism, we’ll be able to achieve more visually pleasing results in the future. Also, a constraint system is not a small dependency: minified, the Javascript version of Cassowary is 47 kb and adds substantial complexity to our codebase.
Results
We’re very happy with how this new type of layout looks in our apps. Go check out Yahoo Food and Yahoo Tech to see double talls in action! And if you’re interested in working on problems like these, we’re looking for more front-end developers at Yahoo Media.
Comments (0)
Sign in to post comments.