Getting Started With CSS calc ()

Quick Summary

I first discovered the calc() function more than four years ago, thanks to CSS3 Click Chart, and I was absolutely delighted to see that basic mathematical computations — addition, subtraction, multiplication and division — had found their way into CSS.
A lot of people think preprocessors fully cover the realm of logic and computation, but the calc() function can do something that no preprocessor can: mix any kind of units. Preprocessors can only mix units with a fixed relation between them, like angular units, time units, frequency units, resolution units and certain length units.

Table of Contents

I first discovered the calc() function more than four years ago, thanks to CSS3 Click Chart, and I was absolutely delighted to see that basic mathematical computations — addition, subtraction, multiplication and division — had found their way into CSS.

A lot of people think preprocessors fully cover the realm of logic and computation, but the calc() function can do something that no preprocessor can: mix any kind of units. Preprocessors can only mix units with a fixed relation between them, like angular units, time units, frequency units, resolution units and certain length units.

Further Reading on SmashingMag:

1turn is always 360deg, 100grad is always 90deg, and 3.14rad is always 180deg. 1s is always 1000ms, and 1kHz is always 1000Hz. 1in is always 2.54cm or 25.4mm or 96px, and 1dppx is always equivalent to 96dpi. This is why preprocessors are able to convert between them and mix them in computations. However, preprocessors cannot resolve how much 1em or 1% or 1vmin or 1ch is in pixels because they lack context.

No need for silly advertising. At this point we just want to thank you for sticking around. This lil' website wouldn't exist without you. You are indeed quite... ahem, smashing. Happy reading! ;-)

We need to keep a few things in mind to ensure that the calc() function works. First, division by zero obviously won’t work. Having a space between the function name and the parenthesis is not allowed. And the plus and minus operators must be surrounded by white space. This means that the following are not valid:

calc(50% / 0)
calc (1em + 7px)
calc(2rem+2vmin)
calc(2vw-2vh)

The calc() function should work as a value in all places where a number value, with or without specified units, works. However, while basic support is really good, we might run into trouble depending on where we use it. Let’s look at a few examples, including what support problems they have (if any) and whether they’re ultimately the best solution.

Easier To Understand Computed Values

Let’s say we want a rainbow gradient. The CSS for this is really simple:

New job openings

This should work fine in all browsers that support calc() and gradients, and because it involves mixing units, it isn’t something that preprocessors have an equivalent for. We can, however, make it more maintainable by using variables:

In this case, the width of the stripe would depend on the element’s dimensions. Sometimes, that’s exactly what we want. For example, this is how we’d do things if we wanted to reproduce a flag in CSS. Adding a bit of green, yellow and blue to the gradient above gives us a flag that fine-chocolate lovers probably recognize — the flag of Tanzania.

But what if we want our diagonal stripe to have a fixed width that doesn’t depend on the element’s dimensions? Well, we’d use calc() and put the stops at 50% minus half of the fixed stripe’s width and at 50% plus half of the fixed stripe’s width. If we want the stripe’s width to be 4em, then we’d have this:

Note that, while using offsets (top, left) for initial positioning is safe, if you plan on animating the position of the element afterwards, you should use transforms instead. This is because changing transforms requires only compositing, whereas changing offsets also triggers a relayout and repaint — thus, impairing performance.

System Of Coordinates And Grid With Origin In The Middle

Since discovering the four-value background-position, I haven’t been too keen on using calc() to position backgrounds relative to the right or bottom side of the element. But calc() turned out to be a great solution for positioning a certain point of the background relative to the middle of the element.

A couple of years ago, I found myself wanting to create a background that represents a system of coordinates with a grid behind and whose origin would be dead in the middle of the screen.

But how do we make the origin of the background dead in the middle and not in the top-left corner? First, background-position: 50% 50% won’t work because it makes the 50% 50% point of the gradient coincide with the 50% 50% point of the element, but the lines are at the top and at the left of the gradients, respectively. The solution is to use calc() and position the gradients so that their top left is almost in the middle of the viewport, just offset to the top and left by half the axis or the grid line’s width:

Maintaining Aspect Ratio And Covering A Viewport Dimension

One thing I’ve always wanted when creating HTML slides was for each slide to be a box of a certain fixed-aspect ratio that always covers at least one dimension of the viewport and that is, of course, in the middle along the other axis.

Proportional box animation

Let’s start by assuming that the desired aspect ratio for the slides is 4:3 and that I’m on a widescreen display. This means the slides cover the viewport vertically but still have some space on the left and the right.

Covering the viewport vertically means a height of 100vh. Knowing the height and the aspect ratio, we can get the width, which is 4/3*100vh. And to get it in the middle, we need to offset it from the left by half the viewport’s width (100vw/2) minus half the slide’s width (4/3*100vh/2). This is where we need the calc() function because we have to mix units.

Covering the viewport horizontally means a width of 100vw. Knowing this and the aspect ratio will give us the height, which is 3/4*100vw. Finally, the top offset is half the viewport’s height minus half the slide’s height; so, 100vh/2 - 3/4*100vw/2.

We can, of course, make things more flexible by not hardcoding the aspect ratio and using two variables instead (one for width and one for height). Here is the Sass version, which you can also test live by resizing the window:

This is supported in all current versions of major browsers. However, WebKit browsers didn’t support the use of viewport units in the calc() function until recently. This has been fixed in Safari 8 and Chrome 34, respectively, with Opera trailing.

Short Slide Title In The Middle

I wanted two more things for slide presentations.

The first was for the slides not to really cover the entire viewport because the edges might get cut off. This was an easy fix. I simply set their box-sizing to border-box and also set a border on them.

The second thing I wanted was to mark sections by starting each with a slide that had nothing but a short and memorable title right in the middle.

In case the slide, including the borders, covers the viewport horizontally (and was vertically in the middle), its height would be $b/$a*100vw. So, the line-height for the title would be that minus twice the slide’s border-width:

line-height: calc(#{$b/$a*100vw} - #{2*$slide-border-width});

This was my initial idea, which, in theory, should work just fine. And it does in WebKit browsers and IE. However, it turns out that calc() values don’t work for line-height (and some other properties) in Firefox — they work now; so, calc() is not the best solution there. Luckily, there are a lot of other ways to solve this problem (flexbox, absolute positioning and more).

Fixed Point Of View

One thing I enjoy playing with a lot is CSS 3D — creating geometric 3D shapes with CSS in particular. If I create just one shape, then I’d normally position it in the middle of the scene it’s contained in. The scene is the element on which I set the perspective and also the parent of the shape element. The shape element will have its own descendants, which are the shape faces, but we won’t go into detail about them here; if you want to learn how they are positioned, then check out my guest article on CSS-Tricks.

Setting a perspective on a scene ensures we’ll see everything that is closer as being bigger and everything that is further away as being smaller. The perspective property accepts length values, and the smaller these values are, the greater the contrast between what’s closer to us and what’s further away.

Now, let’s say we have a very simple 3D shape — a cube, for example — right in the middle of our scene. It doesn’t look very 3D: It’s way too symmetrical, and if the faces are fully opaque, we can only see the front one.

We could rotate it a bit, let’s say by 30°, around its y axis (i.e. the vertical axis passing through the middle of the cube) or around its x axis. This looks better, but we can only see two faces. Plus, the cube is now visibly rotated, which was not the intention.

Something else we could do is change our point of view. We do this via a property called perspective-origin. Its initial value is 50% 50%. This is relative to the scene, and we know that the 50% 50% point of the scene is where the central point of the shape is positioned. Now, let’s say we want to move this up and to the right. The simplest way to do this is to set perspective-origin: 100% 0. But this creates a problem: How we now see the cube depends on the dimensions of the scene (you can test this live by resizing the viewport).

Changing scene dimensions changes how we see the cube.

A perspective-origin of 100% 0 is measured from the top right corner of the scene, while the cube is always in the middle of the scene. Because of this, changing the scene’s dimensions will change the distance between the 50% 50% point (where the cube is positioned) and the 100% 0 point (where we have set the perspective-origin).

A solution for this to use calc() for perspective-origin, of course, is simply to add or subtract a fixed value from the initial 50%:

perspective-origin: calc(50% + 15em) calc(50% - 10em);

Solution
You can [test this live](http://codepen.io/thebabydino/pen/avZVQL) by resizing the viewport.
The second thing I wanted was to mark sections by starting each with a slide that had nothing but a short and memorable title right in the middle.
Desired result. (View large version)
I didn’t want to use absolute positioning, so I thought I’d go with setting an appropriate `line-height`.
In case the slide’s height, including the border, covers the entire height of the viewport, I would have a `line-height` of `100vh` minus twice the slide’s `border-width`:

In case the slide, including the borders, covers the viewport horizontally (and was vertically in the middle), its height would be $b/$a*100vw. So, the line-height for the title would be that minus twice the slide’s border-width:

line-height: calc(#{$b/$a*100vw} - #{2*$slide-border-width});

This was my initial idea, which, in theory, should work just fine. And it does in WebKit browsers and IE. However, it turns out that calc() values don’t work for line-height (and some other properties) in Firefox — they work now; so, calc() is not the best solution there. Luckily, there are a lot of other ways to solve this problem (flexbox, absolute positioning and more).

Fixed Point Of View

One thing I enjoy playing with a lot is CSS 3D — creating geometric 3D shapes with CSS in particular. If I create just one shape, then I’d normally position it in the middle of the scene it’s contained in. The scene is the element on which I set the perspective and also the parent of the shape element. The shape element will have its own descendants, which are the shape faces, but we won’t go into detail about them here; if you want to learn how they are positioned, then check out my guest article on CSS-Tricks.

Setting a perspective on a scene ensures we’ll see everything that is closer as being bigger and everything that is further away as being smaller. The perspective property accepts length values, and the smaller these values are, the greater the contrast between what’s closer to us and what’s further away.

Now, let’s say we have a very simple 3D shape — a cube, for example — right in the middle of our scene. It doesn’t look very 3D: It’s way too symmetrical, and if the faces are fully opaque, we can only see the front one.

We could rotate it a bit, let’s say by 30°, around its y axis (i.e. the vertical axis passing through the middle of the cube) or around its x axis. This looks better, but we can only see two faces. Plus, the cube is now visibly rotated, which was not the intention.

Something else we could do is change our point of view. We do this via a property called perspective-origin. Its initial value is 50% 50%. This is relative to the scene, and we know that the 50% 50% point of the scene is where the central point of the shape is positioned. Now, let’s say we want to move this up and to the right. The simplest way to do this is to set perspective-origin: 100% 0. But this creates a problem: How we now see the cube depends on the dimensions of the scene (you can test this live by resizing the viewport).

Changing scene dimensions changes how we see the cube.

A perspective-origin of 100% 0 is measured from the top right corner of the scene, while the cube is always in the middle of the scene. Because of this, changing the scene’s dimensions will change the distance between the 50% 50% point (where the cube is positioned) and the 100% 0 point (where we have set the perspective-origin).

A solution for this to use calc() for perspective-origin, of course, is simply to add or subtract a fixed value from the initial 50%: