Relative Units - CSS Fundamentals

Relative Units - CSS Fundamentals

In the web environment, the user can have their browser window set to any number of sizes, and the CSS has to apply to it. Also, users can resize the page after it’s opened, and the CSS needs to adjust to new constrains. This means that the browser must calculate those when the page is rendered on-screen. Relative units are one of the tools CSS provides to work for the responsive design.

Pixels, Points, and Picas

CSS supports several absolute length units, the most common for which, and the most basic, is the pixel (px). Less common absolute units are mm (millimetre), cm (centimetre), in. (inch), pt (point — typographic term for 1/72 of an inch), and pc (pica — typographic term for 12 points). Any of these units can be translated directly to another if you want to work out the math:

1 in.=25.4 mm=2.54 cm=6 pc=72 pt=96 px1\ in. = 25.4\ mm = 2.54\ cm = 6\ pc = 72\ pt = 96\ px

therefore, 16 px is the same as 12 pt (1696×72\frac{16}{96}\times{72}). Designers are often more familiar with the use of points, where developers are more accustomed to pixels.

Pixel is a slightly misleading name — a CSS pixel does not strictly equate to a monitor’s pixel. This is notably the case on high-resolution displays. Although the CSS measurements can be scaled a bit, depending on the browser, the operating system, and the hardware, 96 px is usually in the ballpark of 1 physical inch on-screen, though this can vary on certain devices or with a user’s resolution settings.

Ems and Rems

Ems, the most common relative length unit, are a measure used in typography, referring to a specified font size. In CSS, 1 em means the font size of the current element; its exact value varies depending on the element you’re applying it to.

For example:

.padded {
  font-size: 16px;
  padding: 1em;
}

This ruleset specifies a font size of 16 px, which becomes the element’s local definition for 1 em. Then the code uses ems to specify the padding of the element, producing a rendered padding of 16 px. If another selector targets the same element and overrides it with a different font size, it’ll change the local meaning of em, and the computed padding will change to reflect that.

Values declared using relative units are evaluated by the browser to an absolute value, called computed value.

Using Ems to Define Font-size

When it comes to the font-size property, ems have behave a little differently. If you declare font-size: 1.2em, what does that mean? A font size can’t equal 1.2 times itself. Instead, font-size ems are derived from the inherited font size.

If you know the pixel-based font size you’d like, but want to specify the declaration in ems, here’s a simple formula: divide the desired pixel size by the parent (inherited) pixel size. For example, if you want a 10 px font and your element is inheriting a 12 px font, 10/12=0.8333 em10/12 = 0.8333\ em. If you want a 16 px font and the parent font is 12 px, 16/12=1.3333 em16/12 = 1.3333\ em.

It’s helpful to know that, for most browsers, the default font size is 16 px. Technically, it’s the keyword value medium that calculates to 16 px.

Ems for Font-size Together with Ems for Other Properties

What makes ems tricky is when you use them for both font size and any other properties on the same element. When you do this, the browser must calculate the font size first, and then it uses that value to calculate the other values. Both properties can have the same declared value, but they’ll have different computed values. For example:

body {
  font-size: 16px;
}

.padding {
  font-size: 1.2em; /* evaluates to 19.2px */
  padding: 1.2em; /* evaluates to 23.04px */
  background-color: #ccc;
}

In this example, padding has a specified value of 1.2 em. This multiplied by 19.2 px (the computed font size of the element) produces a calculated value of 23.04 px.

The Shrinking Font Problem

Ems can produce unexpected results when you use them to specify the font sizes of multiple nested elements. To know the exact value for each element, you’ll need to know its inherited font size, which, if defined on the parent element in ems, requires you to know the parent element’s inherited size, and so on up the tree.

This becomes quickly apparent when you use ems for the font size of lists and then nest lists several levels deep, like this:

lists using ems as font-size

with CSS defined like:

body {
  font-size: 16px;
}

ul {
  font-size: .8em;
}

By now, it should be clear that ems are nice for element sizing, but when it comes to font size, they can get complicated. Thankfully, there is a better option — rems.

Using Rems for Font-size

When the browser parses an HTML document, it creates a representation in memory of all the elements on the page. This representation is called DOM (Document Object Model). It’s a tree structure, where each element if represented by a node. The <html> element is the top-level (or root) node. Beneath it are its child nodes, <head> and <body>. And beneath those are their children, then their children, and so on.

The root node is the ancestor of all other elements in the document. It has a special pseudo-class selector (:root) that you can use to target it. This is equivalent to using the type selector html with the specificity of a class rather than a tag.

Rem is short for room em. Instead of being relative to the current element, rems are relative to the root element. No matter where you apply it in the document, 1.2 rem has the same computed value: 1.2 times the font size of the root element.

:root {
  font-size: 1em; /* set to browser's default of 16px */
}

ul {
  font-size: .8em; /* evaluated to be 12.8px */
}

Accessibility: use relative units for font size

Some browsers provide two ways for the user to customize the size of text:

  1. Zoom. By pressing ctrl+ or ctrl-, the user can zoom the page up or down. This visually scales all fonts and images and generally makes everthing on the page larger or smaller. In most browsers, this change is temporary and is only applied to the current tab.
  2. Set default font size. In browser’s settings, you can permanently change the default font size for text. The catch is that this setting does not resize fonts defined using pixels or other absolute units.

Rems simplify a lot of complexities involved with ems. In fact, they offer a good middle ground between pixels and ems by providing the benefits of relative units, but easier to work with. Does this mean you should use rems everywhere and abandon the other options?

In CSS, again, the answer is often, “it depends”. Rems are but one tool in your tool box. An important part of mastering CSS is learning when to use which tool. It’s nice to use rems for font sizes, pixels for borders, and ems for most other measures, especially paddings, margins, and border radius. This way, font sizes are predictable, but you’ll still get the power of ems scaling your paddings and margins. Pixels make sense for borders, particularly when you want a nice fine line.

Stop Thinking in Pixels

One pattern, or rather, antiparttern, that has been common for the past several years is to reset the font size at the page’s root to .625 em or 62.5%. This takes the browser’s default size, 16 px, and scales it down to 10 px. This practise simplifies the math.

Initially, this may be convenient, but there are two problems with this approach. First, it forces you to write a lot of duplicate styles. 10 px is too small for most text, so you’ll have to override it throughout the page. The second problem is that when you do this, you’re still thinking in pixels. You might type 1.4 rem into your CSS, but in your mind, you’re still thinking “14 pixels”. On a responsive web, you should get comfortable with “fuzzy” values. It doesn’t matter how many pixels 1.5 em evaluates to; all you need to know is that it’s a bit bigger than the inherited font size.

Setting a Sane Default Font Size

Let’s say you want your default font size to be 14 px. Instead of setting a 10 px default then overriding it through the page, set that value at the root. The desired value divided by the inherited value — in this case, the browser’s default — is 14/16, which equals 0.875:

:root {
  font-size: 0.875em;
}

Now your desired font size is applied to the whole page. You won’t need to specify it elsewhere. You’ll only need to change it in places where the design deviates from this, such as headings.

Media Query

Let’s make this a bit further. You can use some media queries to change the base font size, depending on the screen size.

media query — An @media rule used to specify styles that will be applied only to certain screen size or media types (e.g. print or screen). This is a key component of responsive design.

:root {
  font-size: 0.75em;
}

@media (min-width: 800px) { /* applies only to screens 800px and wider */
  :root {
    font-size: 0.875em;
  }
}

@media (min-width: 1200px) { /* applies only to screens 1200px and wider */
  :root {
    font-size: 1em;
  }
}

If you are disciplined enough to style your entire page in relative units like this, the entire page will scale up and down based on the viewport size.

Resizing a Single Component

For example, you have a panel and want a larger version:

panel, sizes

You can only add a large class to the second panel: <div class="panel large">.

.panel {
  font-size: 1rem;
  padding: 1em;
  border: 1px solid #999;
  border-radius: 0.5em;
}

.panel > h2 {
  margin-top: 0;
  font-size: 0.8em;
  font-weight: bold;
  text-transform: uppercase;
}

First, add the declaration font-size: 1rem to the parent element of each panel. This means each panel will establish a predictable font size for itself, no matter where it’s placed on the page. Second, redefine the heading’s font size using Ems rather than rems to make it relative to the parent’s font size that just established.

For the larger version of the panel, all you have to do is override the parent element’s 1 rem with another value. Because all the component’s measurements are relative to this, overriding it will resize the entire panel:

.panel.large {
  font-size: 1.2rem;
}

Viewport-relative Units

There are also viewport-relative units for defining lengths relative to the browser’s viewport.

viewport — The framed area in the browser window where the web page is visible. This excludes the browser’s address bar, toolbars, and status bar, if present.

  • vh — 1/100 of the viewport height
  • vw — 1/100 of the viewport width
  • vmin — 1/100 of the smaller dimension, height or width
  • vmax — 1/100 of the larger dimension, height or width

The viewport-relative lengths are great for things like making a large hero image fill the screen. You can place an image inside a long container, but setting the image height to 100 vh, makes it exactly the height of the viewport.

Using vw for Font Size

Consider what would happen if you applied font-size: 2vw to an element. On a desktop monitor at 1,200 px, this evaluates to 24 px (2% of 1,200). On a tablet with a screen width of 768 px, it evaluates to about 15 px (2% of 768). Unfortunately, it scales all the way down to 7.5 px on an iPhone 6.

Using calc() for Font Size

The calc() function lets you do basic arithmetic with two or more values. This is particularly useful for combining values that are measured in different units. This function supports addition (+), subtraction (-), multiplication (*) and division (/). The addition and subtraction operators must be surrounded by whitespace, for example, calc(1em + 10px).

Use calc() to combine ems with vw units, as:

:root {
  font-size: calc(0.5em + 1vw);
}

The 0.5 em here operates as a sort of minimum font size, and the 1 vw adds a repsonsive scalar. This’ll give you a base font size that scales from 11.75 px on an iPhone 6 up to 20 px in a 1,200 px browser window. It should automatically resize the font size when you resize the browser window, however, I found it was not working on Safari (Version 12.1.1 (14607.2.6.1.1)), you have to refresh the page to adjust the font size.

Unitless Numbers and Line-height

Some properties allow for unitless values. Properties that support this include line-height, z-index, and font-weight (700 is equivalent to bold; 400 is equivalent to normal, and so on). You can also use the unitless value 0 anywhere a length unit (such as px, em, or rem) is required because, in these cases, the unit does not matter.

A unitless 0 can only be used for length values and percentages, such as in paddings, borders, and widths. It can’t be used for angular values, such as degrees or time-based values like seconds.

The line-height property is unusual in that it accepts both units and unitless values. You should typically use unitless numbers because they’re inherited differently.

body {
  line-height: 1.2;
}

.element {
  font-size: 2em;
}

The .element inherits a line height of 1.2. Because the font size is 32 px (2 em×16 px2\ em\times16\ px), the line height is calculated locally to 38.4 px (32 px×1.232\ px\times 1.2). This will leave an appropriate amount of space between lines of text.

When an element has a value defined using a length (px, em, rem, and so forth), its computed value is inherited by child elements. When units such as ems are specified for a line height, their value is calculated, and that calculated value is passed down to any inheriting children.

length — The formal name for a CSS value that denotes a distance measurement. It’s a number followed by a unit, such as 5 px. Length comes in two flavors: absolute and relative. Percentages are similar to lengths, but strictly speaking, they’re NOT considered lengths.

When you use a unitless number, that declared value is inherited, meaning its computed value is recalculated for each inheriting child element. This will almost always be the result you want.

Custom Properties (aka. CSS Variables)

In 2015, a long-awaited CSS specification titled Custom Properties for Cascading Variables was published as a Candidate Recommendation. This specification introduced the concept of variables to the language, which enabled a new level of dynamic, context-based styles. You can declare a variable and assign it a value; then you can reference this value throughout your stylesheet. You can use this to reduce repetition in your stylesheet, as well as some other beneficial applications.

At present, support for custom properties has rolled out in all major browsers except IE, check https://caniuse.com/#feat=css-variables for up-to-date support information.

If you happen to use a CSS preprocessor that supports its own variables, such as Sass or Less, you may be tempted to disregard CSS variables. However, the new CSS variables are different in nature and are far more versatile than anything a preprocessor can accomplish.

To define a custom property, you declare it much like any other CSS property:

:root {
  --main-font: Helvetica, Arial, sans-serif;
}

The name must begin with two hyphens (--) to distinguish it from CSS properties, followed by whatever name you’d like to use. By itself, this variable declaration doesn’t do anything until we use it.

A function var() allows the use of variables.

p {
  font-family: var(--main-font);
}

The var() function accepts a second parameter, which specifies a fallback value. If the variable specified in the first parameter is not defined, then the second value is used. If a var() function evaluates to an invalid value, the property will be set to its initial value.

Changing Custom Properties Dynamically

You can define the same variable inside multiple selectors, and the variable will have a different value for various parts of the page.

For example, you can define a variable as black, and then redefine it as white inside a particular container. Then, any styles based on that variable will dynamically resolve to black if they are outside the container and to white if inside.

:root {
  --main-bg: #fff;
  --main-color: #000;
}

.panel {
  background-color: var(--main-bg);
  color: var(--main-color);
}

.dark {
  --main-bg: #333;
  --main-color: #fff;
}

For contexts like:

<div class="panel">
  ...
</div>

<div class="dark">
  <div class="panel">
    ...
  </div>
</div>

The second .panel will have a dark background and white text. This is because when the panel uses these variables, they’ll resolve to the values defined on the dark container, rather than on the root.

Changing Custom Properties with JavaScript

The following script shows how to access and set a property on an element.

let rootElement = document.documentElement;
let styles = getComputedStyle(rootElement);
let mainColor = styles.getPropertyValue('--main-bg');
console.log(String(mainColor).trim()); // trims whitespace, logs "#fff"

rootElement.style.setProperty('--main-bg', '#333'); // set "--main-bg"

With this technique, you can use only a few lines of JavaScript that make changes that’ll affect a large number of elements on the page.

Be aware that any declaration using var() will be ignored by old browsers that don’t understand it. Provide a fallback behaviour for those browsers when possible.

color: black;
color: var(--main-color);
THE END
Ads by Google

林宏

Frank Lin, PhD

Hey, there! This is Frank Lin (@flinhong), one of the 1.41 billion . This 'inDev. Journal' site holds the exploration of my quirky thoughts and random adventures through life. Hope you enjoy reading and perusing my posts.

YOU MAY ALSO LIKE

Using Liquid in Jekyll - Live with Demos

Web Notes

2016.08.20

Using Liquid in Jekyll - Live with Demos

Liquid is a simple template language that Jekyll uses to process pages for your site. With Liquid you can output complex contents without additional plugins.

Setup an IKEv2 server with strongSwan

Tutorials

2020.01.09

Setup an IKEv2 server with strongSwan

IKEv2, or Internet Key Exchange v2, is a protocol that allows for direct IPSec tunnelling between two points. In IKEv2 implementations, IPSec provides encryption for the network traffic. IKEv2 is natively supported on some platforms (OS X 10.11+, iOS 9.1+, and Windows 10) with no additional applications necessary, and it handles client hiccups quite smoothly.

Practising closures in JavaScript

JavaScript Notes

2018.12.17

Practising closures in JavaScript

JavaScript is a very function-oriented language. As we know, functions are first class objects and can be easily assigned to variables, passed as arguments, returned from another function invocation, or stored into data structures. A function can access variable outside of it. But what happens when an outer variable changes? Does a function get the most recent value or the one that existed when the function was created? Also, what happens when a function invoked in another place - does it get access to the outer variables of the new place?