Flexbox and Positioning¶
We're going to drastically revise the layout for Ottergram. Take a look:
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 torow
but can becolumn
. - 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 toflex-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 contentsflex: 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 parentflex-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:
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 positionposition: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
orbottom
and giving a signed distance by which to move the top or bottom of the element. - It's shifted horizontally using either
left
orright
and another signed distance. - The starting location is where it would have been if it had been
position:static
(the default) rather thanposition: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.
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 fromtop
, because a positive value forbottom
moves the element up, while a negative value fortop
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
orright
- exactly one vertical coordinate, either
top
orbottom
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, usingjustify-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
andleft:distance
for some specified distance, but you can usebottom
instead oftop
orright
instead ofleft
.
-
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 dowidth:100%
for images, so that the browser can resize them. ↩