Gleðileg jól! God jul! Merry Christmas! Happy holidays! Feliz navidad! Christmas eve, or julaften as we indigenous Norwegians call it, is upon us, and I have the honor of wrapping this calendar up!
The subject of today's post is typography, especially, how to implement typography in CSS how the designer wants it.
Background
In CSS, the text in a given element will sit centered within its own line-height. In the design world the line height is measured from the baseline of the text. As a result, the interfaces often deviate from the designers intentions, requiring a heap of pixel-nudging to get things to line up again.
A summary of this, and to illustrate the problem we face trying to implement designs, this video explains it clearly:
How do we solve this?
Well, facing this problem at out current client, we have a design that is really tied up to the custom font that the client is using. Nailing the typography implementation would greatly improve the design, and improve the readability.
To do this, we need to calculate some font metrics to adjust the text so it will operate on the baseline of the font itself. We need to know the "cap height", "x-height", "baseline" and "descender height".
"Cap Height" is the height of the capital letters. "Baseline" is the bottom of the letters. "X-Height" is the height of the lower case x. "Descender Height" is the height of dangling characters like y, j, p and g.
So, we needed a tool that could solve this issue for us, instead of guessing the correct margins and line-heights and other adjustments. Enter basekick. Basekick provides a mixin to handle this for us, but we needed to figure out the actual values of the font metrics to get this to work as intended. I found a codepen that halfway got us there, but the app did not provide us with everything we needed, so I improved it:
That was the first iteration. But I felt that I could do this better and more automated, so I created this codepen:
The codepen above takes screenshots, thanks to html2canvas, and then I output the screenshot to a canvas. The canvas data is then processed to find the first occurring non-white pixel, and then the last occurring non-white pixel to get the first values that is needed. And to get the last piece of the puzzle, we do the same with the lower case character "x".
The values in this file that is not in a variable, is the grid-row-span, which basically is the number of rows the font goes in the line-height. Ordinarily, the grid-row is a lesser value of 2-6pt/px, but here, we use 31px, the same as the "base" line-height.
Implementation
When all variables are found, we use the basekick mixin to make it a bit easier to apply good typography:
basekick($bk-font-size-in-px, $bk-descender-height-scale, $bk-type-row-span, $bk-grid-row-height, $bk-line-height = false, $bk-prevent-collapse = false, $bk-cap-height = 0){
font-size: unit($bk-font-size-in-px, 'px')
calculate-type-offset($line-height){
$line-height-scale = ($line-height / $bk-font-size-in-px)
$offset = (($line-height-scale - 1) / 2) + $bk-descender-height-scale
transform: translateY(unit($offset, 'em'))
}
if($bk-line-height){
line-height: unit($bk-line-height, 'px')
calculate-type-offset($bk-line-height)
if($bk-prevent-collapse){
$top-space = $bk-line-height - $bk-cap-height * $bk-font-size-in-px;
$height-correction = $top-space > $bk-type-row-span ? $top-space - ($top-space % $bk-type-row-span) : 0;
padding-top: unit($bk-prevent-collapse, 'px')
&:before {
content: ""
margin-top: unit(-($height-correction + $bk-prevent-collapse), 'px')
display: block
height: 0
}
}
} else {
$bk-line-height = ($bk-type-row-span * $bk-grid-row-height)
line-height: unit($bk-line-height, 'px')
calculate-type-offset($bk-line-height)
}
}
We then use that mixin in our stylus file:
.text.flow
& > .heading.medium
basekick($font-size-xlarge-px, $descender-height, 1.548387097, $grid-row-height, $line-height-xlarge-px)
& > .text.lead
basekick($font-size-large-px, $descender-height, 1.096774194, $grid-row-height, $line-height-large-px)
& > .text.body
basekick($font-size-base-px, $descender-height, 1, $grid-row-height, $line-height-base-px)
.text.flow.normalized
& > .heading.medium
basekick($font-size-xlarge-px, $descender-height, 1, $grid-row-height, $line-height-base-px)
& > .text.lead
basekick($font-size-large-px, $descender-height, 1, $grid-row-height, $line-height-base-px)
& > .text.body
basekick($font-size-base-px, $descender-height, 1, $grid-row-height, $line-height-base-px)
.text.flow.prevent-collapse
& > .heading.medium
basekick($font-size-xlarge-px, $descender-height, 1.548387097, $grid-row-height, $line-height-xlarge-px, 1, $cap-height)
& > .text.lead
basekick($font-size-large-px, $descender-height, 1.096774194, $grid-row-height, $line-height-large-px, 1, $cap-height)
& > .text.body
basekick($font-size-base-px, $descender-height, 1, $grid-row-height, $line-height-base-px, 1, $cap-height)
.text.flow.normalized.prevent-collapse
& > .heading.medium
basekick($font-size-xlarge-px, $descender-height, 1, $grid-row-height, $line-height-base-px, 1, $cap-height)
& > .text.lead
basekick($font-size-large-px, $descender-height, 1, $grid-row-height, $line-height-base-px, 1, $cap-height)
& > .text.body
basekick($font-size-base-px, $descender-height, 1, $grid-row-height, $line-height-base-px, 1, $cap-height)
The result
When we combine all of this, we end up with a typography as close as possible to the designer's sketches:
The result looks pretty good, and it's a bit more clear when we have line-guides to actually see the difference.
To wrap it up
In an everyday scenario, this approach might seem overkill. But this method actually bridges one of the few last gaps betwen a designer's sketch and the technical implementation of it.
It's been one hell of a ride folks. From all of us to all of you, a very merry Christmas!