CSS Poly Fluid Sizing using calc(), vw, breakpoints and linear equations

Jake Wilson
Apr 24, 2017 · 8 min read

When working with creative designers on web page designs, it’s fairly common to receive multiple Sketch or Photoshop artboards/layouts, one for each breakpoint.

In that design, elements (like an h1 heading) will usually be different sizes at each breakpoint. For example:

  • The h1 at the small layout could be22px
  • The h1 at the medium layout could be 24px
  • The h1 at the large layout could be 34px

The bare minimum CSS for this uses media queries:

h1 {
font-size: 22px;
}
@media (min-width:576px) {
h1 {
font-size: 22px;
}
}
@media (min-width:768px) {
h1 {
font-size: 24px;
}
}
@media (min-width:992px) {
h1 {
font-size: 34px;
}
}

This is good, but it’s a little bit jolting when you resize your browser window. At each breakpoint, the text size instantly jumps up/down in size. It would be great if the resizing of fonts between layouts was completely fluid. Your layout is fluid. Why not your font-sizes too? You could throw in a CSS transition to smooth out that jump:

h1 {
font-size: 22px;
transition: font-size 0.2s;
}

It’s not so jarring now, but the jump is still clearly there. What else can we do?

Viewports Units to the rescue?

Image for post
Image for post

But the viability of Viewport units is very dependent on the original creative designs for a web page. It would be great to just set your font-size using vw and be done:

h1 {
font-size: 2vw;
}

But this only works if your creative art-boards take this into account. Did the designer choose a text size that was exactly 2% of the width of each of his art-boards? Of course not. Let’s calculate what the vw value would need to be for each of our breakpoints:

22px size @ 576px wide = 22/576 = 3.82% of the width
24px size @ 768px wide = 24/768 = 3.13% of the width
34px size @ 992px wide = 34/992 = 3.43% of the width

They are close but they aren’t all the same. So you would still need to use media queries to transition between text sizes but there would still be jumps. And consider this weird side-effect:

@ 767px, 3.82% of the viewport width is 29px. Resizing your browser 1-pixel wider and the font-size sudden drops back down to 24px. That would be weird.

So how do we solve this problem?

Statistical Linear Regression?

First, lets plot our resolutions and corresponding text sizes on a graph:

Image for post
Image for post
Scatter plot of font-size and corresponding Viewport width (Google Spreadsheets)

Here you can see a scatter plot of the designer’s specified text sizes at the defined viewport widths. See that line? That‘s’ called a trendline. It’s sort of a way for you to find an interpolated font-size value for any viewport width, based on the data we provided.

The trendline is the key to all of this

Image for post
Image for post
Linear equation definition
  • m = slope
  • b = the y-intercept.
  • x = the current viewport width
  • y = the resulting font-size

The are several methods for determining the slope and y-intercept. A common method is the Least Squares fit:

Image for post
Image for post

Once you run those calculations, you have your trendline equation.

How do I use this in CSS?

Image for post
Image for post

You can use the trendline equation like this:

h1 {
font-size: calc({slope}*100vw + {y-intercept}px);
}

Once you find your slope and y-intercept you just plug them in and Viola!

Note: You have to multiply the slope by 100 since you are using it as a vw unit which is 1/100th of the Viewport width.

Can this be automated?

Does this really work? Open up this CodePen and resize your browser window. It works! The font sizes are fairly close to what the original design was asking for and they smoothly scale with your layout.

Now, admittedly, it’s not perfect. The values are close to the original design but not quite perfect. This is because a linear trendline is an approximation of specific font sizes at specific viewport widths. This is inherit of linear regression. There is always some error in your results. It’s a trade-off of simplicity vs. accuracy.

Also, the more varied out your text sizes are, the more error that will be in your trendline. Can we do better than this?

Polynomial Least Squares Fit

Image for post
Image for post
Polynomial regression trendline (Google Spreadsheets)

Now that is more like it! Much more accurate than our straight line. A basic polynomial regression equation looks like this:

Image for post
Image for post
A 3rd degree polynomial equation

The more accurate you want your curve, the more complicated the equation gets. Unfortunately, you can’t do this in CSS. calc() simply cannot do this type of advanced math. Specifically, you can’t calculate exponents:

font-size: calc(3vw * 3vw); /* This doesn't work in CSS */

So until calc() supports this type of non-linear math, we are stuck with linear equations only. Is there anything else we can do to improve upon this?

Breakpoints + Multiple Linear Equations

Image for post
Image for post
Linear Regression trendlines between multiple pairs of values (Google Spreadsheets)

So in this example we would calculate the straight line between 22px and 24px and then another time between 24px and 34px. The SASS would look like this:

// SCSSh1 {
@media (min-width:576px) {
font-size: calc(???);
}
@media (min-width:768px) {
font-size: calc(???);
}
}

We could use the least squares fit method for those calc() values but since it’s just a straight line between 2 points, the math could be greatly simplified. Remember the equation for a straight line?

Image for post
Image for post
Linear equation definition

Since we are talking about just 2 points now, finding the slope (m) and y-intercept (b) is trivial:

Image for post
Image for post
Finding the slope and y-intercept of a linear equation

Here is a SASS function for this:

Now, just use the linear interpolation function on multiple breakpoints in your SASS. Also, lets throw in some min and max font-sizes:

// SCSSh1 {
// Minimum font-size
font-size: 22px;
// Font-size between 576 - 768
@media (min-width:576px) {
$map: (576px: 22px, 768px: 24px);
font-size: linear-interpolation($map);
}
// Font-size between 768 - 992
@media (min-width:768px) {
$map: (768px: 24px, 992px: 34px);
font-size: linear-interpolation($map);
}
// Maximum font-size
@media (min-width:992px) {
font-size: 34px;
}
}

And it generates this CSS:

h1 {
font-size: 22px;
}
@media (min-width: 576px) {
h1 {
font-size: calc(1.04166667vw + 16px);
}
}
@media (min-width: 768px) {
h1 {
font-size: calc(4.46428571vw - 10.28571429px);
}
}
@media (min-width: 992px) {
h1 {
font-size: 34px;
}
}

The Holy Grail of CSS sizing?

This SASS mixin requires the following SASS functions

First of all, obviously this method applies not only to font-size but to any unit/length property (margin, padding, etc). You pass the desired property name into the mixin as a string.

Next, you pass any number of mapped viewport width + size value pairs in any order into the poly-fluid-sizing() mixin. It will automatically sort the map according to Viewport width from lowest to highest. So you could pass a crazy map like this to it and it would work out just fine:

h1 {
$map: (576px: 22px, 320px: 18px, 992px: 34px, 768px: 24px);
@include poly-fluid-sizing('font-size', $map);
}

It will then perform linear interpolation on each pair of viewport widths and then generate your CSS. You can import this into any SASS project and easily utilize it. Here is a final CodePen of this method:

The only downside that I’m seeing at the moment is that you can’t throw mixed units into the mixin, for example 3em @ 576px width. SASS just won’t really know what to do mathematically there. I’m going to continue to think about a way to handle that short of passing in a base em value into it.

Conclusion

CSS currently supports non-linear animation and transition timing functions, so maybe there is a chance that calc() will also support it someday.

I began exploring this idea in early 2017 and eventually developed the above solution. Since then, I’ve seen a few dev’s come up with similar ideas and different pieces of this puzzle. I thought it was time for me to share my method and how I got there.

Here are some of those references:

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch

Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore

Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store