Hardcore CSS calc( )

Sometimes things seem complex in a design but turn out to be easy to implement in code. Other times, elements of a design that appear simple at first glance turn out to be very tricky to implement.

This is the story of one of the latter cases…

The challenge

A couple of years ago I was tasked with implementing a web page featuring an arrangement of images like this:

The designer had diligently redlined all the dimensions. Even more helpfully, as we’ll soon see, they also indicated the aspect ratio of the images — 4:3 for all three of them.

Additional relevant design details for this particular project were as follows:

  • The 20px gutter between the images was a standard gutter size that was used in many places throughout the website’s design
  • The image files served up by our CMS were guaranteed to have a 4:3 aspect ratio, as they were being cropped server-side
  • This was a responsive website design, so images were fluid — i.e. they scaled up or down to fit the horizontal space they were given

Armed with that knowledge, I suspected that the intended responsive design behaviour was as follows:

As the viewport width changes…

  • the gutters between the images should remain fixed at 20px
  • the images should scale up or down proportionally, retaining their aspect ratio
  • the grid-like arrangement of the images should always be maintained

Clear as mud? Fear not! Here’s an animated demo to illustrate how this block of three images should scale up or down:

Fixed gutters, but fluid images

A quick conversation with the designer confirmed my assumptions. This was indeed the behaviour they had envisaged.

The trouble with redlines

For the purpose of writing the CSS code, the pixel image dimensions shown by the design’s redlines were useless. They would only ever work in scenarios where the user’s viewport happened to match the designer’s canvas size — for any other viewport size they were incorrect. In all likelihood, the designer had laid out the images as desired and then measured them for the annotations.

Aside from the fixed gutters, all other width and height measurements were variable and thus unpredictable

This is by no means a critique of the designer. As a developer, I’d much rather have some measurements than none. The trick is understanding which ones to take literally and which others need to be taken with a healthy dose of salt.

As a brief aside, I see a lot of tools nowadays that aim to automate or simplify the handover of visual designs to developers. A common feature is the ability to automatically extract and measurements from visual designs and expose them via redlines or some kind of inspector tool. Zeplin and InVision are perhaps the best known examples, but there are plenty more tools that do this.

However, regardless of whether measurements were done manually or automatically, care must be taken when interpreting those values. Absolute pixel numbers are rarely and adequate description of the designer’s intent. At the end of the day, there’s no substitute for a regular dialogue between designers and developers.

Applying maths

Positioning the images wasn’t an issue —some floats here and some margins there would get the job done. Considering that the images had predictable aspect ratio, I only needed to set their widths, because the default height value of auto would ensure they scaled proportionally, preserving their aspect ratio. The only question therefore was:

What, exactly, were the widths of the images?

At this point, some dusty, old brain cells at the back of my brain creaked into action. This was just like a maths exercise at school. What I had here was a set of equations that I could solve for my width values!

First, I labelled the various lengths in the design:

  • aw : Aspect ratio width
  • ah : Aspect ratio height
  • w1 : Large image’s height
  • h1 : Large image’s width
  • w2 : Small images’ height (note that both small images have the exact same size)
  • h2 : Small images’ width
  • m : Margin between images

Looking at the diagram, there were a few simple relationships I could begin to express as formulas.


1) The width of the large image + the margin + the width of the small images was equal to 100% of the design’s width:

w1 + m + w2 = 100%


2) The height of both small images + the margin was equal to the height of the large image:

2 × h2 + m = h1


3) The width of the large image divided by the aspect ratio width, multiplied by the aspect ratio height was equal to its height:

w1 / aw × ah = h1


4) The small images’ widths had the same relationship to their heights:

w2 / aw × ah = h2


Now the hard work of solving for w1 began (and then for w2). I began by substituting formulas 3) and 4) for h1 and h2 respectively in formula 2), and then solving it for w2:

Please excuse my scruffy handwriting!

Then, I could substitute the result of that for w2 in formula 1), and solve it for w1:

I think I can see now why my teachers always used to complain about my handwriting!

Hurrah! I now had a formula that precisely calculated the width of the large image purely in terms of constant values —all things I’d be able to plug into a CSS calc() expression.

Note the expression at the end that I circled in red. It only contains the margin, the aspect ratio width and aspect ratio height — none of which will ever change at runtime. It is therefore a constant value that we can pre-calculate at build time. For convenience, I named this constant value c:

c = (m × aw) / (ah × 3)

Returning to the original formula 1) and substituting w1 with the above result, provided me with the corresponding w2 value:

I dread to think what a graphologist would make of this!

At long last, I had my two width values:

w1 = 2/3 × (100% − m) + c

w2 = 1/3 × (100% − m) − c

Coding time

At long last I was ready to write some code! I always like to start with the markup. In this case, it’s pretty simple. We have three images in some kind of container. For the site I was working on, the images happened to be thumbnail previews for a photo gallery, so I chose a <div> with a class name thumbnails:

<div class="thumbnails">
<img src="..." alt="...">
<img src="..." alt="...">
<img src="..." alt="...">
</div>

As mentioned before, the constant c, which appears in both image width formulas, is a good candidate to be pre-calculated. I’ll show how you can do it in SASS, but this could just as well be any other pre-processor (or you could work it out by hand and write the value into your CSS code):

// Using values from the original design
$margin: 20px;
$aspect-width: 4;
$aspect-height: 3;
// Calculate c
$constant: ($margin * $aspect-width) / ($aspect-height * 3);

Now all that’s left is to set the width on the images and arrange them. I’m going to use float to arrange them because it’s sufficient in this case and well supported in browsers dating back many years.

The width formula has to be worked out at runtime by the browser, because we have no way of knowing what our user’s viewport size will be and thus can’t pre-calculate what 100% of our <div class="thumbnails"> equates to. For this reason we need to use CSS calc().

Since we’re using SASS, we need to use #{...} interpolation when inserting our SASS variables into the formula. Otherwise SASS treats the variables as though they were strings and outputs them into the CSS rather than their values (see the difference in this SassMeister example).

Putting it all together, we get:

.thumbnails {
// Use a simple clearfix technique to ensure
// that this element's height expands to wrap
// the floated images within it.
overflow: hidden;


// First image becomes big one on left
> *:first-child {
display: block;
float: left;
margin-right: $margin;
    // Magic formula!
width: calc( (2 / 3 * (100% - #{$margin}) ) + #{$constant} );
}
  // 2nd & 3rd images become smaller ones
// on right
> *:nth-child(n + 2) {
display: block;
float: right;
    // Magic formula!
width: calc( (1 / 3 * (100% - #{$margin}) ) - #{$constant} );
}
  // 3rd image also has top margin
> *:nth-child(3) {
margin-top: $margin;
}
}

Now, for the moment of truth. Did all our hard calculations pay off? Does it work?

Of course it does! 😄

Resize that beauty all you want — you can’t break the layout. The images will always align themselves perfectly!

Boom! Mic drop.

Conclusion

Obviously this is a very specific layout. If you’re faced with exactly the same challenge, then by all means use this code! However, what I hope is a more useful takeaway is the approach that I used:

  1. Make sure you fully understand how the design should behave (take pixel measurements with a pinch of salt— the underlying ratios are often much more important!)
  2. Figure out which values are fixed upfront (in this example that’s the common aspect ratio and the margin)
  3. Figure out which ones are variable and thus need to be calculated at runtime (unless they are plain percentage values, this is where you’re likely to need calc())
  4. Look for relationships between sizes or values in your design that you can express mathematically
  5. Simplify things as far as you can
  6. Code it!

I’d love to see what tricky layouts, arrangements or effects you’ve managed to pull off with CSS calc() and some maths! Please share them and tell us all how you pulled it off!