Fluid layouts have been a normal part of front-end development for years. The idea of fluid typography, however, is relatively new and has yet to be fully explored. Up until now, most developers’ idea of fluid typography is simply using Viewport units maybe with some minimum and maximum sizes.

In this article, we are going to take it to another level. We are going to examine how to create scalable, fluid typography across multiple breakpoints and predefined font sizes using well-supported browser features and some basic algebra. The best part is that you can automate it all by using Sass.

Further Reading on SmashingMag: Link

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:

  1. The h1 at the small layout could be 22px
  2. The h1 at the medium layout could be 24px
  3. The h1 at the large layout could be 34px

The bare minimum CSS for this uses media queries5:

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;
  }
}
6

This is a good first step, 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. You could throw in a CSS transition to smooth out that jump:

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

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

Viewport Units To The Rescue? Link

Viewport units8 are a step in the right direction. They allow your text to fluidly resize with your layouts. And the browser support9 is great these days.

Can I Use Viewport?10
(View large version11)

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;
}
12

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*100 = 3.82vw
24px size @ 768px wide = 24/768*100 = 3.13vw
34px size @ 992px wide = 34/992*100 = 3.43vw

They are close but they aren’t all the same. So you would still need to use media queries to transition between text sizes and 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. As you can see in the animation, it’s weird:

13

So how do we solve this problem?

Statistical Linear Regression? Link

Wait. What? Yes, this is an article about CSS, but some basic math can go a long way towards an elegant solution to our problem.

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

Scatter plot of font-size and corresponding Viewport width14
Scatter plot of font-size and corresponding Viewport width (Google Spreadsheets) (View large version15)

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

The Trendline Is The Key To All Of This Link

If you could set your font-size according to this trendline, you would have an h1 that smoothly scales on all resolutions that would come close to matching what the designer intended. First, let’s look at the math. The straight line is defined by this equation:

Linear equation definition
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. When multiple values are involved, a common method is the Least Squares16 fit:

Least Squares

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

How Do I Use This In CSS? Link

Okay, this is getting pretty heavy on the math. How do we actually use this stuff in front-end web development? The answer is CSS calc()! Once again, a fairly new CSS technology that is very well supported17.

Can I Use Calc?18
(View large version19)

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!

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? Link

I ported the least squares fit method into an easy-to-use Sass function:

/// least-squares-fit
/// Calculate the least square fit linear regression of provided values
/// @param {map} $map - A Sass map of viewport width and size value combinations
/// @return Linear equation as a calc() function
/// @example
///   font-size: least-squares-fit((576px: 24px, 768px: 24px, 992px: 34px));
/// @author Jake Wilson <jake.e.wilson@gmail.com>
@function least-squares-fit($map) {
  
  // Get the number of provided breakpoints
  $length: length(map-keys($map));
  
  // Error if the number of breakpoints is < 2
  @if ($length < 2) {
    @error "leastSquaresFit() $map must be at least 2 values"
  }
    
  // Calculate the Means
  $resTotal: 0;
  $valueTotal: 0;
  @each $res, $value in $map {
    $resTotal: $resTotal + $res;
    $valueTotal: $valueTotal + $value;
  }
  $resMean: $resTotal/$length;
  $valueMean: $valueTotal/$length;

  // Calculate some other stuff
  $multipliedDiff: 0;
  $squaredDiff: 0;
  @each $res, $value in $map {
    
    // Differences from means
    $resDiff: $res - $resMean;
    $valueDiff: $value - $valueMean;
    
    // Sum of multiplied differences
    $multipliedDiff: $multipliedDiff + ($resDiff * $valueDiff);
    
    // Sum of squared resolution differences
    $squaredDiff: $squaredDiff + ($resDiff * $resDiff);
  }

  // Calculate the Slope
  $m: $multipliedDiff / $squaredDiff;

  // Calculate the Y-Intercept
  $b: $valueMean - ($m * $resMean);

  // Return the CSS calc equation
  @return calc(#{$m*100}vw + #{$b});

}

Does this really work? Open up this CodePen20 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.

Least Squares Fit SCSS test user=”jakobud”]See the Pen Least Squares Fit SCSS test21 by Jake Wilson (@jakobud3622) on CodePen3723.

Least Squares Fit SCSS test

Now, admittedly, it’s not perfect. The values are close to the original design but they do not quite match up. 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, keep in mind, the more varied your text sizes are, the more error there will be in your trendline.

Can we do better than this?

Polynomial Least Squares Fit Link

In order to get a more accurate trendline, you need to look at more advanced topics, like a polynomial regression24 trendline that might look something like this:

Polynomial regression trendline25
Polynomial regression trendline (Google Spreadsheets) (View large version26)

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

A 3rd degree polynomial equation
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 And Multiple Linear Equations Link

What if we were only calculating a straight line between each pair of breakpoints? Something like this:

Linear Regression trendlines between multiple pairs of values27
Linear Regression trendlines between multiple pairs of values (Google Spreadsheets) (View large version28)

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

// SCSS
h1 {
  @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?

Linear equation definition
Linear equation definition

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

 Finding the slope and y-intercept of a linear equation
Finding the slope and y-intercept of a linear equation

Here is a Sass function for this:

/// linear-interpolation
/// Calculate the definition of a line between two points
/// @param $map - A Sass map of viewport widths and size value pairs
/// @returns A linear equation as a calc() function
/// @example
///   font-size: linear-interpolation((320px: 18px, 768px: 26px));
/// @author Jake Wilson <jake.e.wilson@gmail.com>
@function linear-interpolation($map) {
  $keys: map-keys($map);
  @if (length($keys) != 2) {
    @error "linear-interpolation() $map must be exactly 2 values";
  }
  // The slope
  $m: (map-get($map, nth($keys, 2)) - map-get($map, nth($keys, 1)))/(nth($keys, 2) - nth($keys,1));
  
  // The y-intercept
  $b: map-get($map, nth($keys, 1)) - $m * nth($keys, 1);
  
  // Determine if the sign should be positive or negative
  $sign: "+";
  @if ($b < 0) {
    $sign: "-";
    $b: abs($b);
  }
  
  @return calc(#{$m*100}vw #{$sign} #{$b});
}

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

// SCSS
h1 {
  // 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;
  }
}
29

The Holy Grail Of CSS Sizing? Link

Lets wrap this all up in a nice Sass mixin (for the lazy and efficient!). I’m coining this method Poly Fluid Sizing:

/// poly-fluid-sizing
/// Generate linear interpolated size values through multiple break points
/// @param $property - A string CSS property name
/// @param $map - A Sass map of viewport unit and size value pairs
/// @requires function linear-interpolation
/// @requires function map-sort
/// @example
///   @include poly-fluid-sizing('font-size', (576px: 22px, 768px: 24px, 992px: 34px));
/// @author Jake Wilson <jake.e.wilson@gmail.com>
@mixin poly-fluid-sizing($property, $map) {
  // Get the number of provided breakpoints
  $length: length(map-keys($map));
  
  // Error if the number of breakpoints is < 2
  @if ($length < 2) {
    @error "poly-fluid-sizing() $map requires at least values"
  }

  // Sort the map by viewport width (key)
  $map: map-sort($map);
  $keys: map-keys($map);

  // Minimum size
  #{$property}: map-get($map, nth($keys,1));
  
  // Interpolated size through breakpoints
  @for $i from 1 through ($length - 1) {
    @media (min-width:nth($keys,$i)) {
      $value1: map-get($map, nth($keys,$i));
      $value2: map-get($map, nth($keys,($i + 1)));
      // If values are not equal, perform linear interpolation
      @if ($value1 != $value2) {
        #{$property}: linear-interpolation((nth($keys,$i): $value1, nth($keys,($i+1)): $value2));
      } @else {
        #{$property}: $value1;
      }
    }
  }
  
  // Maxmimum size
  @media (min-width:nth($keys,$length)) {
    #{$property}: map-get($map, nth($keys,$length));
  }
}

This Sass mixin requires a few Sass functions in the following Github gists:

The poly-fluid-sizing() mixin will perform linear interpolation on each pair of viewport widths and set a minimum and maximum size. You can import this into any Sass project and easily utilize it without needing to know any of the math behind it. Here is the final CodePen34 that uses this method.

Poly Fluid Sizing using linear equations, viewport units and calc() user=”jakobud”]See the Pen Poly Fluid Sizing using linear equations, viewport units and calc()”] Poly Fluid Sizing using linear equations, viewport units and calc()35 by Jake Wilson (@jakobud3622) on CodePen3723.

Poly Fluid Sizing using linear equations, viewport units and calc()

A Few Notes Link

  • 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.
  • The Sass map of viewport width + size value pairs can be passed 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 in a map like this and it would work out just fine:
  $map: (576px: 22px, 320px: 18px, 992px: 34px, 768px: 24px);
  @include poly-fluid-sizing('font-size', $map);
  • A limitation for this method is that you cannot pass in mixed units into the mixin. For example, 3em @ 576px width. Sass just won’t really know what to do mathematically there.

Conclusion Link

Is this the best we can do? Is Poly Fluid Sizing the Holy Grail of fluid unit sizing in CSS? Maybe. CSS currently supports non-linear animation38 and transition39 timing functions, so maybe there is a chance40 that calc() will also support it someday. If that happens, non-linear, polynomial regression might be worth a look again. But maybe not… Linear scaling might be superior anyways.

I began exploring this idea41 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. Viewport units. Calc(). Sass. Breakpoints. None of these things are new. They are all browser features that have existing for years (with varying degrees of support). I’ve only used them together in a way that hadn’t been fully explored yet. Don’t ever be afraid to look at the tools you use every day and think out-of-the-box on how you can utilize them better and grow your skill set.

(vf, il)

  1. 1 https://www.smashingmagazine.com/2016/05/fluid-typography/
  2. 2 https://www.smashingmagazine.com/2015/08/typographic-patterns-in-html-newsletter-email-design/
  3. 3 https://www.smashingmagazine.com/2014/12/the-good-the-bad-and-the-great-examples-of-web-typography/
  4. 4 https://www.smashingmagazine.com/2016/03/meaningful-web-typography/
  5. 5 https://developer.mozilla.org/en-US/docs/Web/CSS/Media_Queries/Using_media_queries
  6. 6 http://designguru.info/wp-content/uploads/2017/05/media-query-opt.gif
  7. 7 http://designguru.info/wp-content/uploads/2017/05/css-transitions-opt.gif
  8. 8 https://developer.mozilla.org/en-US/docs/Web/CSS/length#Viewport-percentage_lengths
  9. 9 http://caniuse.com/#search=viewport
  10. 10 http://designguru.info/wp-content/uploads/2017/05/1-can-i-use-viewports-opt.png
  11. 11 http://designguru.info/wp-content/uploads/2017/05/1-can-i-use-viewports-opt.png
  12. 12 http://designguru.info/wp-content/uploads/2017/05/viewport-opt.gif
  13. 13 http://designguru.info/wp-content/uploads/2017/05/viewport-2-opt.gif
  14. 14 http://designguru.info/wp-content/uploads/2017/05/2-scatter-plot-font-size-opt.png
  15. 15 http://designguru.info/wp-content/uploads/2017/05/2-scatter-plot-font-size-opt.png
  16. 16 https://en.wikipedia.org/wiki/Least_squares
  17. 17 http://caniuse.com/#search=calc
  18. 18 http://designguru.info/wp-content/uploads/2017/05/5-can-i-use-calc-opt.png
  19. 19 http://designguru.info/wp-content/uploads/2017/05/5-can-i-use-calc-opt.png
  20. 20 http://codepen.io/jakobud/pen/LyZJRB
  21. 21 http://codepen.io/jakobud/pen/LyZJRB
  22. 22 http://codepen.io/jakobud
  23. 23 http://codepen.io
  24. 24 https://en.wikipedia.org/wiki/Polynomial_regression
  25. 25 http://designguru.info/wp-content/uploads/2017/05/6-polynomial-regression-opt.png
  26. 26 http://designguru.info/wp-content/uploads/2017/05/6-polynomial-regression-opt.png
  27. 27 http://designguru.info/wp-content/uploads/2017/05/8-linear-regression-opt.png
  28. 28 http://designguru.info/wp-content/uploads/2017/05/8-linear-regression-opt.png
  29. 29 http://designguru.info/wp-content/uploads/2017/05/holy-grail-opt.gif
  30. 30 https://gist.github.com/Jakobud/7414f91142e0f540f221a3e3cafdf856
  31. 31 https://gist.github.com/Jakobud/a0ac11e80a1de453cd86f0d3fc0a1410
  32. 32 https://gist.github.com/Jakobud/744b98b629abe018766f6d506a2e92ae
  33. 33 https://gist.github.com/Jakobud/ec056b52f3673cc369dc97f2c2428424
  34. 34 https://codepen.io/jakobud/pen/vmKLYb
  35. 35 https://codepen.io/jakobud/pen/vmKLYb
  36. 36 http://codepen.io/jakobud
  37. 37 http://codepen.io
  38. 38 https://developer.mozilla.org/en-US/docs/Web/CSS/animation-timing-function
  39. 39 https://developer.mozilla.org/en-US/docs/Web/CSS/transition-timing-function
  40. 40 http://i0.kym-cdn.com/photos/images/newsfeed/000/840/283/350.png
  41. 41 http://stackoverflow.com/questions/42014594/linear-scaling-between-2-font-sizes-using-calc-and-vw

↑ Back to top

Tweet itShare on Facebook



Source link