Flexbox and Positioning

We're going to drastically revise the layout for Ottergram. Take a look:

flexbox Ottergram

This new layout has a "big" picture on the top and a bunch of thumbnails horizontally across the bottom. Try making the page wider and narrower. We'll call the "big" picture the detail image. The relevant HTML will have classes like detail-image-container, detail-image-frame and detail-image-title.

To achieve this layout, we'll learn about flexbox, which is a relatively new addition to CSS. It was added after mobile phones took over the device world, and flexbox allows the web developer to easily lay out elements either top-to-bottom or left-to-right. Later, we'll learn how to switch between the two.

Mobile First

We should first design our websites so that they work effectively on small devices, and only later take advantage of bigger displays. Some rules of thumb. You'll notice that a lot of these have to do with widths of elements, trying to avoid the dreaded horizontal scrolling.

  • don't lay things out horizontally; stack things vertically
  • give images CSS declarations like width:100% so that they scale up/down based on the size of the device
  • avoid table elements and such that might be wider than the device (you'll find that some code displays in these notes violate this rule). We haven't learned tables, yet, but some of you may know them
  • specify widths in percentages when feasible, since percentages adjust to the device size
  • don't specify widths of elements in pixels (or, at least, wide elements). A few pixels are fine for thicknesses of lines, but most elements should be either the default or some percentage.

Inline Blocks

We know about inline elements, like span, that are found in running text and would move around as the browser gets wider and narrower.

We also know about block elements, which use the box model, can (typically) contain other block elements, and stack vertically on the page.

In this version of Ottergram, we will make use of inline block elements, achieved with display:inline-block. Such elements act like inline elements in terms of filling out the width of their container (moving other inline elements up from the next line until there's no more space), but they act like block elements in that they can contain other block elements.

Note that the default width of a block or inline-block element is 100%, which means that there is never any room left to bring another one up from the next line. So, it only makes sense to use inline-block where you are setting the width1. Like this:

selector {
    display: inline-block;
    width: 20%;  /* five on a line */
    ...
}

New HTML

We've actually seen these elements before, but a reminder:

  • main The main content of a page. Bigger than an article
  • div A meaningless block container. Useful for structuring the page. We'll use div in structuring the big picture.

Flexbox

The main idea of flexbox is it involves a bunch of elements (flex items) and their parent (flex container). The container can specify whether the flex items are arranged horizontally or vertically. It can also specify what to do with leftover space and so forth.

With Flexbox, there is

  • a container specified by display:flex.
  • a set of flex-items to be displayed, all children of the container. This is automatic.
  • a direction or main axis that the children will be oriented in (horizontally or vertically). This is flex-direction, which defaults to row but can be column.
  • a cross axis that is perpendicular to the main axis
  • a specification for what happens with any leftover space in the flex container, specified by justify-content, which defaults to flex-start, which means that the items are at the beginning of the container, with leftover space, if any, at the end.

Flex containers can be nested, so you have rows within columns, columns within rows, even rows within rows and columns within columns.

Flex items can grow and shrink. There are two common uses of the flex shorthand property:

  • flex: 0 1 auto means don't grow me, shrink me if needed, and calculate my size from my contents
  • flex: 1 1 auto means grow me as much as possible, shrink me if needed, and calculate my size from my contents

Flexbox Simplified

Before we get to flexbox in Ottergram, which is sophisticated and powerful, let's start with a simple case. The simple cases have at least the following features:

  • a parent element and two or more children
  • the parent will set the following:
    • display:flex which is what makes it a flex parent
    • flex-direction which determines whether the children are in a row or a column
  • each child (flex-item) can (optionally) set
    • flex: grow shrink basis

So, at a minimum, you need two CSS properties on the flex parent to use the flexbox feature.

Take a few minutes to look at these flex examples

New CSS Properties

Pretty much all of these are related to the new flexbox layout

  • display:flex to specify that an element is a flex container/parent.
  • flex-direction row or column
  • flex how a flex item (child) shrinks or grows
  • justify-content what happens with leftover space in a flex container along the main axis?
  • align-items aligning (e.g. centering or stretching) flex-items on the cross axis. If the item doesn't fill the space allocated to it, where does it end up in that space. See more at MDN align-items
  • overflow set what happens when an element's contents is bigger than it is. Scroll? Hide? Show? The default is to show the content, which can sometimes cause annoying page scroll bars.
  • order what order does this flex item go in? source order? after others? before others? This is an optional property; the default is source order.
  • white-space what happens with white space in an element: normal? nowrap?

Flexbox in Ottergram

Here's an excerpt of the HTML we'll use for our new flexbox Ottergram:

  <body>
    <header>
      <h1 class="logo-text">ottergram</h1>
    </header>
    <main>
      <ul class="thumbnail-list">
        <li class="thumbnail-item">
        </li>
      </ul>
      <div class="detail-image-container">
        <div class="detail-image-frame">
          <img class="detail-image" src="imgs/otter1.jpg" alt="Barry the Otter">
          <span class="detail-image-title">Stayin' Alive</span>
        </div>
      </div>
    </main>
  </body>

Notice the detail-image stuff at the end; that's the larger image.

We going to use flexbox four times, nested three deep. The outermost flexbox container will be the body: the whole page is a flex container. The two children of body (the flex items) are header and main.

Then, main will also be a flex container. Its children are ul.thumbnail-list and div.detail-image-container.

Both of those will be flex containers. The ul.thumbnail-list will have its children arranged horizontally.

Here's a screenshot from the Firefox developer tools, where each flex container is marked as such:

flexbox in the developer tools

Height Allocation

Now let's look at height allocation. With Flexbox, you can configure how things grow and shrink. For the body as a whole, we want the whole vertical space to be 100% (so there's no scrolling), but we don't want to divide the space equally between its two children (header and main). We want header to be its natural size (shrinking if necessary, but not growing), but the main to grow to take up all the rest of the space.

Thus, we have:

html, body {
  height: 100%;
}

body {
  display: flex;
  flex-direction: column;
}

header {
  flex: 0 1 auto;
}

main {
  flex: 1 1 auto;
}

The body will be a flex container with its children laid out vertically as a column.

The flex CSS property is a shorthand for specifying flex-grow, flex-shrink and flex-basis. The last is what the size is computed from before any growing or shrinking. flex-basis: auto means to take its size from some other setting (e.g. height or width) or use the size of the content. auto is a good default.

The CSS properties flex-grow and flex-shrink have values that are numbers: zero means don't grow/shrink and one means to grow/shrink. If several items are allowed to grow, they'll grow at the same rate (until space runs out) if they are both flex-grow:1. If one of them is flex-grow:2, it grows twice as fast, so it gobbles up more of the extra space.

Here, we keep it simple: the header cannot grow but it can shrink. The main can both grow and shrink. So the header is its natural size, and the main takes all the rest of the space.

Main

Now let's leave the header and look at the main. Again, this will be a column, with one child allowed to grow (the div.detail-image-container) and the other not (ul.thumbnail-list). So we have the following CSS:

main {
  display: flex;
  flex-direction: column;
}

.thumbnail-list {
  flex: 0 1 auto;
  order: 2;
}

.detail-image-container {
  flex: 1 1 auto;
}

We threw something new in there. We put the ul.thumbnail-list first in the HTML file, but we actually want it below the div.detail-image-container. Rather than edit the HTML, we can say order:2 on the list, which means that it comes second.

Why specify the layout this way? Because we'll learn later that the CSS can dynamically change, so by doing the layout using CSS, we can change the layout dynamically. We couldn't do that by changing the HTML. (Well, we could but it's much harder.)

Thumbnails

We want the thumbnails laid out horizontally, so we specify the CSS for the ul.thumbnail-list (the flex parent) as follows:

.thumbnail-list {
  ...
  display: flex;
  flex-direction: row;
  justify-content: space-between;
  white-space: nowrap;
  overflow-x: auto;
}

The flex-direction:row is actually the default, so we didn't need to do that. But it's helpful to be clear in the code. There are additional properties like justify-content: space-between which puts any extra space between the items. If there's too little space, the overflow-x: auto property will get us a horizontal scroll bar. (To see this, try making the window really narrow.) The white-space: nowrap property says that if there is space between elements, don't wrap them onto a new line (instead, we'll use a scroll bar).

Detail Container

The detail container seems simple, but we want the big picture centered both horizontally and vertically. Before flexbox was added to the CSS language, that was hard. With flexbox, it's easy, just say you want centering on both the main axis (justify-content) and the cross axis (align-items).

.detail-image-container {
  flex: 1 1 auto;
  display: flex;
  justify-content: center;
  align-items: center;
}

All that flexbox CSS was long and complicated, but that's most of the difficult work with flexbox. A few more small touches.

Thumbnails

The li.thumbnail-item elements are li elements. We want them to have the ability to be laid out horizontally (like inline elements) but to act like block elements (meaning they can have a defined width and height), so we make the display: inline-block. We also want to make them small enough to act like thumbnails. So, we style them like this:

  display: inline-block;
  min-width: 120px;
  max-width: 120px;
  width: 120px;

You would think that only the width: 120px would be necessary, and in many cases it is, but sometimes that doesn't work, and the additional properties help.

Absolute and Relative Positioning

We have one more cool feature to discuss. Notice how the .detail-image-title of the detail picture (the phrase Stayin' Alive) overlaps the bottom left corner of the picture. It's not common to have HTML elements overlap, but it has its uses, both practical and aesthetic. We'll use it several times this semester.

Normally, we let the browser figure out where an element should land on the page, based on its size, the size of the container, whether it's a block or inline element, and so forth. This is because the default value of the position CSS property is static. Elements that have position:static cannot overlap. That's the normal behavior.

The position property has two other interesting values:

  • position:relative which allows you to shift the element from its static position
  • position:absolute which allows you to place the element within a Cartesian coordinate system

Here's a sentence in which a single red word has been moved from its usual place. Of course, we could use that class again, to raise another word out of its place.

Here's how that's done. First, the CSS:

.up_and_right {
    position: relative;
    top: -1em;
    left: 2em;
    color:red;
}

Here's the HTML:

<p>Here's a sentence in which a single red <span
class="up_and_right">word</span> has been moved from its 
usual place. Of course, we could use that class again, to 
raise another word out of <span class="up_and_right">its</span> 
place. </p> 

In general:

  • The element is shifted vertically using either top or bottom and giving a signed distance by which to move the top or bottom of the element.
  • It's shifted horizontally using either left or right and another signed distance.
  • The starting location is where it would have been if it had been position:static (the default) rather than position:relative.
  • Note that other elements don't move as a result of the movement of this one. (There's a gap left in the sentence above, where the word was moved from.) The moved element can even overlap other elements; that can't happen otherwise.

You might wonder why subtracting 1em from the top causes the word to move up. That's because the coordinate system for absolute and relative is a little different from the x,y Cartesian coordinate systems you learned in middle school or high school:

  • left increases as you go left-to-right, just like x does,
  • top increases as you go down, unlike y which decreases as you go down.

The reason for this weird coordinate system is that the beginning of the document is the main origin, and the beginning of the document is at the top of the page.

Position absolute is similar to position relative, except that the distances are measured not from the element's static position but in an absolute coordinate system, where the origin of the system is the first non-static ancestor and if no non-static ancestor is found, the browser window.

That last sentence is a little weird, so let's rephrase it to give us another chance to understand it. Suppose the element that we want to place is D (for "descendant"). Consider the whole chain of ancestors that D has, all the way up to the body. If any of those ancestors, call it A, has position:relative (or anything other than the default which is position:static) then D is placed relative to A. That is, A sets the coordinate system. If there is no such ancestor, then D is placed with respect to the browser window.

Positioning on a Grid

Therefore, another use of position:relative is to set the coordinate system for its descendants. I like to think of this as "my descendants are positioned relative to me". Here's an example. Each of the colored boxes is 100x100 px. The outer green box is 600px wide by 200px high.

pink child
green child
blue child

This is done by:

  • putting position:relative on the ancestor (here the box with gray background)
  • putting position:absolute; top:100px; left:50px on the pink child
  • putting position:absolute; top:0px; left:-50px on the green child
  • putting position:absolute; bottom:50px; right:100px on the blue child (well, it looks more purplish). Bottom works a little differently from top, because a positive value for bottom moves the element up, while a negative value for top moves the element up.

Use this feature sparingly. Sites where everything is positioned in absolute coordinates are incredibly inflexible. You want a site that is responsive to the size of the browser. Nevertheless, sometimes position:absolute can be useful.

Note that with position:static, elements can never overlap. In particular, text can't overlap. But with position:absolute and position:relative you can do that, if you want.

The Detail Image Title

Now that we've learned a lot about absolute positioning, we can see how the detail-image-title (the Stayin' Alive) is positioned. The HTML is straightforward:

      <div class="detail-image-container">
        <div class="detail-image-frame">
          <img class="detail-image" src="imgs/otter1.jpg" alt="Barry the Otter">
          <span class="detail-image-title">Stayin' Alive</span>
        </div>
      </div>

The CSS establishes the div.detail-image-frame as the coorinate system:

.detail-image-frame {
  position: relative;
  text-align: center;
}

And then positions the span.detail-image-title below the bottom and in from the left edge:

.detail-image-title {
  position: absolute;
  bottom: -0.3em;
  left: 0.4em;
  font-family: airstreamregular;
  color: white;
  text-shadow: rgba(0, 0, 0, 0.9) 1px 2px 9px;
  font-size: 4em;
}

That's the magic. Not a lot of code for a fancy result.

Positioning Checklist

It's easy to get confused about the CSS properties you need to use absolute positioning. Here is a checklist:

  • put position:relative on the desired ancestor
  • put position:absolute on the element you want to position
  • set two coordinates on the element you want to position.

The coordinates are usually top and left but they don't have to be. You need to have:

  • exactly one horizontal coordinate, either left or right
  • exactly one vertical coordinate, either top or bottom

Centering

Students often want to center things. Centering gets content away from the edges (though you can do that with padding) and people often think it looks nice (though that's a matter of taste, and a professional web designer wrote a book about design that denigrated centering). Still, if you'd like to learn more about centering and related issues, there is some additional Information on float, centering and text shadows that you might find interesting.

Summary

We started with Flexbox:

  • Flexbox is a useful way to manage the layout of a page
  • Flexbox uses main axes and cross axes: If the main axis is vertical, the cross axis is horizontal and if the main axis is horizontal, the cross axis is vertical.
  • The flex container is the parent element, and the flex items are its children
  • The flex container (parent) determines the direction (row or column) of the children using flex-direction and what is done with leftover space, using justify-content
  • Each flex item (child) can decide whether it can grow or shrink

We also learned about positioning:

  • The ancestor says position:relative and
  • each descendant can say position:absolute to be positioned with respect to that ancestor
  • The positioning is typically done with top:distance and left:distance for some specified distance, but you can use bottom instead of top or right instead of left.

  1. Certain elements, particularly images (img) are called replaced elements. For replaced elements, the width is derived from the contents (the intrinsic width of the image). So it's usually a good idea to do width:100% for images, so that the browser can resize them.