- Mastering Sass
- Luke Watts
- 2115字
- 2021-07-14 10:28:20
Heading line heights
Next, we need to take control of the line height of our headings. They too look disproportionate when we switch between the font families. The good thing with this, however, is we've already done the groundwork. Our base-heading-sizes-calc()
function will allow us to easily solve this problem. However, again we need to figure out the appropriate formula.
One way of setting the line height would be to simply use the font size of the next heading. For example, when we're using our sans font family:
h1 {font-size: 2rems;} h2 {font-size: 1.5rem;} h3 {font-size: 1.17rem;} // ...and so on
Well, it just so works out that line heights based on the next headings font-size
look about right.
h1 { font-size: 2rems; line-height: 1.5rem; } h2 { font-size: 1.5rem; line-height: 1.17rem } h3 { font-size: 1.17rem; line-height: 1rem; } // ...and so on
Therefore, we can almost copy our base-heading-sizes-calc()
function again to get our line heights with only two small differences:
// mastering-sass/ch02/scss/helpers/_functions.scss @function base-heading-line-height($heading: 2) { $h1-line-height: base-heading-sizes-calc(2); // We start at 2 instead of 1 $h2-line-height: base-heading-sizes-calc(3); $h3-line-height: base-heading-sizes-calc(4); $h4-line-height: base-heading-sizes-calc(5); $h5-line-height: base-heading-sizes-calc(6); $h6-line-height: (base-heading-sizes-calc(6) / $h1-line-height); $line-heights: ($h1-line-height, $h2-line-height, $h3-line-height, $h4-line-height, $h5-line-height, $h6-line-height); @return nth($line-heights, $heading); }
The first difference is straightforward enough. We simply start at 2 instead of 1, because we want to use the font size of the next heading down as the line height for the larger headings.
The second difference arises when we get to the h6
line-height. Obviously, we don't have a h7
font-size to use, so what should we do? Well, it turns out dividing our h6
font-size by 1.5 (or our $h1-line-height
) works out perfectly.
I considered using our global $base-line-height
; however, what happens when you set that to 1.25
? The line-height of the h6
grows slightly while everything else shrinks. For that reason, the $h1-line-height
is the best solution.
So now we can update our style.scss
as follows:
// mastering-sass/ch02/scss/style.scss h1, h2, h3, h4, h5, h6 { font-family: $base-font-family; } h1 { font-size: base-heading-sizes-calc(1); line-height: base-heading-line-height(1); } h2 { font-size: base-heading-sizes-calc(2); line-height: base-heading-line-height(2); } h3 { font-size: base-heading-sizes-calc(3); line-height: base-heading-line-height(3); } // ...and so on
Test the differences between sans and serif by switching your $base-font-family
and double check everything looks right in the browser.
But wait, there's more...
There is another scenario we need to account for now. What happens when you want to use a different font family for the headings that the paragraphs? It's fairly common to see headings in serif and the body text in sans, or vice-versa. So we'll need to add this capability to our current mixins and functions.
Initially, this might sound like a tricky feature to include. However, due to the fact we've made each of our functions to handle a single problem well, we only need to make a few very small changes. If we take a look at our very first function, we'll notice we've already added in the ability to specify which font-family
we want to use, it simply defaults to the current $base-font-family
.
So let's create a similar global variable for our headings called $base-headings-font-family
:
// mastering-sass/ch02/scss/style.scss $base-font-family: $base-font-family-sans; $base-headings-font-family: $base-font-family-serif;
Once we've done that we can simply go back into our functions and add our second parameter to each function, which will be passed through to each call of base-font-size-calc()
inside those functions:
// mastering-sass/ch02/scss/helpers/_function.scss @function base-heading-sizes-calc($heading: 2, $font-family: $base-headings-font-family) { $h4-font-size: base-font-size-calc($font-family); $h1-font-size: $h4-font-size * 2; $h2-font-size: $h1-font-size / 1.3333; $h3-font-size: $h2-font-size / 1.2821; $h5-font-size: $h4-font-size / 1.2048; $h6-font-size: $h5-font-size / 1.2388; $headings: $h1-font-size, $h2-font-size, $h3-font-size, $h4-font-size, $h5-font-size, $h6-font-size; @return nth($headings, $heading); } @function base-heading-line-height($heading: 2, $font-family: $base-headings-font-family) { $h1-line-height: base-heading-sizes-calc(2, $font-family); $h2-line-height: base-heading-sizes-calc(3, $font-family); $h3-line-height: base-heading-sizes-calc(4, $font-family); $h4-line-height: base-heading-sizes-calc(5, $font-family); $h5-line-height: base-heading-sizes-calc(6, $font-family); $h6-line-height: base-heading-sizes-calc(6, $font-family) / $h1-line-height; $line-heights: $h1-line-height, $h2-line-height, $h3-line-height, $h4-line-height, $h5-line-height, $h6-line-height; @return nth($line-heights, $heading); }
Then we simply update our headings in style.scss to use our new $base-headings-font-family
:
//mastering-sass/scss/style.scss
h1, h2, h3, h4, h5, h6 {
font-family: $base-headings-font-family;
}
What's great about this is due to the fact we've set the default value of $font-family
in our functions to already use $base-headings-font-family
we don't even need to make any changes to the places where we've used those two functions. You can imagine what a relief this would be on a large project which is already live. This is the benefit of having many smaller functions, each responsible for solving one problem completely, instead of fewer functions trying to do everything.
Allowing for different font families for each heading
The only issue really now is if we want to use serif for our h1
and sans for h2
, h3
and so on. Grouping them all together like we've done simply won't do if we want to allow for even more configuration. Really we should be adding the font family to each heading so we can set each one individually. However, simply adding the font family to each heading would be error prone. There's nothing stopping someone from doing this by accident:
h2 { font-family: $base-font-family; font-size: base-heading-sizes-calc(2); line-height: base-heading-line-height(2); }
It would be quite easy for an end user not to realize the default heading sizes and line heights are actually being set for $base-headings-font-family
and therefore need to be set accordingly to match the font family.
So we need to encapsulate these three properties together to prevent mistakes. We can do this quite easily with a small mixin that takes in two parameters, much like our functions in the previous examples. In our mixins file add the following:
// mastering-sass/ch02/scss/helpers/_mixins.scss @mixin base-headings-font-family-sizing($current-font-family: $base-headings-font-family, $heading: 2) { font-family: $current-font-family; font-size: base-heading-sizes-calc($heading, $current-font-family); line-height: base-heading-line-height($heading, $current-font-family); }
Now we can update each of our headings to use this mixin instead:
// mastering-sass/ch02/scss/style.scss h1 { @include base-headings-font-family-sizing($base-headings-font-family, 1); } h2 { @include base-headings-font-family-sizing($base-headings-font-family, 2); } // ...and so on
You'll notice I put the number second for our mixin, whereas in all of our functions it comes first. I actually wouldn't recommend doing this, seeing as it can be counter intuitive for someone who is used to the order things are in our functions. However, I wanted to point out that sass
mixins and functions can take in arguments in any order, as long as you specify the original parameters names. The following will work just fine:
// mastering-sass/ch02/scss/style.scss h3 { @include base-headings-font-family-sizing($heading: 2, $current-font-family: $base-font-family-code); }
It can be useful if you remember the names of what you need to put in but not the exact order. Now, that I've mentioned that, I'm actually going to update my mixin and headings so the heading number comes first and the font family second:
// mastering-sass/ch02/scss/helpers/_mixins.scss
@mixin base-headings-font-family-sizing($heading: 2, $current-font-family: $base-headings-font-family) {
font-family: $current-font-family;
font-size: base-heading-sizes-calc($heading, $current-font-family);
line-height: base-heading-line-height($heading, $current-font-family);
}
Don't forget to update everywhere you've called the mixin as well.
Remove repetition
We can now place all of our headings into a mixin to clean up our style.scss file even more. We could simply copy each rule and drop it into a mixin; however, I think this is a good chance to look at loops.
We will need to keep all of the functionality we've added so far, including the ability to choose separate font families for each heading. This will be the trickiest feature to include. We'll use nested lists to achieve this. There is a better data type called maps, for this purpose, but I'm saving those for later. I want us to get familiar with lists before moving onto maps.
So the best way to start is to actually take all of our headings from style.scss and put them into a mixin as is, and then call that mixin from our style.scss. This will just mean we can work entirely in our mixin and see the changes (or lack thereof) in our browser. So our mixin will look this:
// mastering-sass/ch02/scss/helpers/_mixins.scss @mixin base-headings { h1 { @include base-headings-font-family-sizing(1, $base-headings-font-family); } h2 { @include base-headings-font-family-sizing(2, $base-headings-font-family); } h3 { @include base-headings-font-family-sizing(3, $base-headings-font-family); } // ... h4, h5, h6 }
Then, back in our style.scss file we can replace where we had our headings with the following:
// mastering-sass/ch02/scss/style.scss @include base-headings;
So, the first loop we'll look at is called a for
loop. It looks like the following:
// mastering-sass/ch02/scss/helpers/_mixins.scss @mixin base-headings { @for $i from 1 to 6 { h#{$i} { @include base-headings-font-family-sizing($i, $base-headings-font-family); } } }
The for
loops simply start at the first number, which we set to one, and places it inside the variable $i
. It the runs the code inside the brackets. After it's done that it adds 1
to our variable $i
and runs again. So the second iteration, or loop would use $i: 2
and so on to the second number we set, at which point it stops.
Now, this is where it's important to know what you're actually trying to do. In the above example we said "I want to loop from 1 to 6". This means it will loop 1, 2, 3, 4, and 5 but not 6 itself. Essential the word to means "up to 6, but not 6 itself". So our mixin would only run 5 times, giving us h1
, h2
, h3
, h4
, and h5
...but no h6
.
You could say @for $i
from 1 to 7 but there is in fact, a better way. Instead of using the keyword "to" we say "through", like this:
// mastering-sass/ch02/scss/helpers/_mixins.scss @mixin base-headings { @for $i from 1 through 6 { h#{$i} { @include base-headings-font-family-sizing($i, $base-headings-font-family); } } }
This solves the problem of repetition; however, we can't choose which font family to use for each individual iteration. To do this we need another variable that can also be looped through and allows for configuring each headings font family, individually, allowing for any configuration. Before the arrival of maps in lists this was done with lists.
One way would simply create a list of font families in order from 1 to 6, which would match h1
to h6
. To do this, we would write our list with our other variables, after the $base-headings-font-family
variable:
// mastering-sass/ch02/scss/style.scss $base-headings: ($base-headings-font-family, $base-font-family, $base-headings-font-family, $base-headings-font-family, $base-headings-font-family, $base-headings-font-family)
Then we can use the nth()
function in Sass to access these by their position in the list, using our $i
counter variable:
// mastering-sass/ch02/scss/helpers/_mixins.scss @mixin base-headings { @for $i from 1 through 6 { h#{$i} { @include base-headings-font-family-sizing($i, nth($base-headings, $i)); } } }
While this works, it requires each item in $base-headings
has to be in that order. It would be better if we could indicate which font family is for which heading. To accomplish this, we can use nested lists:
$base-headings: ( (h1, $base-headings-font-family), (h2, $base-font-family), (h3, $base-headings-font-family), (h4, $base-headings-font-family), (h5, $base-headings-font-family), (h6, $base-headings-font-family) );
We can then use an @each
loop, which allows us to assign as many variables as we need to access the items in our lists. However, we still need a counter to be used in our base-headings-font-family-sizing
mixin:
// mastering-sass/ch02/scss/helpers/_mixins.scss @mixin base-headings { $i: 1; @each $heading, $family in $base-headings { #{$heading} { @include base-headings-font-family-sizing($i, $family); } $i: $i + 1; } }
So we instantiate our counter variable $i
outside of our each
loop. We can then add 1 to it on each successful loop by using $i + 1
and reassigning $i
the new value $i: $i + 1
.
Note
Currently Sass doesn't have incremental (++
) or decremental operators (--
) so we need to use the $i: $i + 1
previously mentioned.
So there are our mixins for creating our configurable base typography styles. Even though it works, it's not yet ready to be released for use by other designer or developers.
Error handling and type validation
There are a few simple things to keep in mind when writing any function or mixin which takes input from an external source. In our case, the external source is our configuration variables at the start of our style.scss file; however, this could also be other functions, other mixins inside mixins, and so forth. The aim is to prepare for times when things go wrong. Good code is pessimistic.
Usually you would do this as you write your functions and mixins the first time, so you don't need to revisit them later; however, I've left it to the end to keep the original functions and mixins from getting too complicated.
So let's start with our functions. We'll begin by sanitizing user input. That means checking the values passed in, making sure they are of the right type or format, and if not we will either convert them to the right format or providing an appropriate level of error message to the user that says where and why there was a problem.