teaching machines

CS 268: Lecture 7 – Flexbox Continued

February 25, 2020 by . Filed under lectures, spring-2020, webdev.

Dear students:

Last week we saw how we can use Flexbox to size and distribute our boxes of content across a container. Today we continue to explore Flexbox and see how to recreate some common layouts found across the web.

But first, let’s review Flexbox by completing an exercise.

Flexbox

Last time we looked at only the properties of the flex container. Let’s round out our discussion of Flexbox by looking at the flex children. The flex children may already be sized and position the way we want them by the properties set on the container. But if not, we can tweak them with several child properties.

By default, the children appear along the direction of flow in their order of expression. The order property can be used to override this. But certain tasks like tabbing through the elements will still traverse them in expression order, and in general, letting the visual order diverge from the structural order will lead to a poor user experience.

We alter the cross-axis alignment per child with the align-self property. For example, the parent might have each child stretch, but we override this for one of the children by setting align-self to center.

When the flex container has leftover space, that space by default will be portioned out according to the justify-content algorithm. Sometimes, however, we want the children to grow into the extra space. For that, we use the flex-grow property. Each element requests a number of “units” of this extra space. By default, each child requests 0 units. All the requests are summed together, and each child is awarded its computed proportion.

If only one child sets its flex-grow property to a non-zero number N, then it will be awarded N / N = 100% of the space. If there are two children each with flex-grow set to 1, each will be awarded 1/2 of the space.

Note that the flex-grow values are not the same as the relative sizes of the children—because before the same extra space is divided, the children are sized according to their flex-basis property. By default, flex-basis is set to auto, which means the children are sized according to their width or height properties first and only then given any extra space. If we truly want flex-grow to determine the relative size of each child, we can set flex-basis to 0 and let flex-grow expand the children.

If there’s not enough space for the children, space is taken away from according to their flex-shrink values. By default the shrink values are 1, meaning that each element loses the same amount of extra space. If we want a child to not shrink, we set it’s flex-shrink to 0.

There’s this shorthand rule for setting all three of the properties that size a child along the main axis:

.child {
  flex: GROW SHRINK BASIS;
}

Let’s apply these ideas to create some common layouts.

N-column

As an example of an n-column layout, consider CodePen. In fact, let’s just crudely recreate CodePen. We use this HTML structure:

<body>
  <div id="editor-root">
    <textarea id="html-editor" class="editor"></textarea>
    <textarea id="css-editor" class="editor"></textarea>
  </div>
  <iframe id="preview"></iframe>
</body>

We split the editor-root into two columns that get an equal share of the leftover space using these rules:

#editor-root {
  display: flex;
  flex-direction: row;
}

.editor {
  flex: 1;
}

We split the body into two rows, one of fixed height and one that grows using these rules:

html, body {
  height: 100%;
}

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

#editor-root {
  flex: 0 1 300px;
  /* ... */
}

#preview {
  flex: 1;
}

Let’s sneak in this Javascript so that edits to the text boxes actually generate a preview in the iframe:

let htmlEditor = document.getElementById('html-editor');
let cssEditor = document.getElementById('css-editor');
let preview = document.getElementById('preview');

function updatePreview() {
  preview.srcdoc = `
<html>
  <head>
    <title>Preview</title>
    <style>${cssEditor.value}</style>
  </head>
  <body>${htmlEditor.value}</body>
</html>`;
}

htmlEditor.addEventListener('input', updatePreview);
cssEditor.addEventListener('input', updatePreview);

Holy Grail

Another common layout we find on the web is the holy grail, which consists of five elements, much like Swing’s BorderLayout. There’s a header, footer, two sidebars, and a central area for content. This pattern is commonly seen on blogs.

We use the following structure:

<div id="root">
  <header>...</header>
  <div id="chester">
    <div id="left-arm">...</div>
    <div id="heart">...</div>
    <div id="right-arm">...</div>
  </div>
  <footer>...</footer>
</div>

Note that this layout is two-dimensional. Flexbox is mostly a tool for one-dimensional layouts. But we can nest them.

First, let’s get the content filling the window with these rules:

html, body {
  height: 100%;
}

body {
  margin: 0;
}

Then we style our outer root element to flow downward like this:

#root {
  display: flex;
  flex-direction: column;
  align-items: stretch;
}

The middle or chester element we want to expand, and we also want it to lay out its children in a row, so we apply this style:

#chester {
  flex-grow: 1;

  display: flex;
  flex-direction: row;
  align-items: stretch;
}

We set the heart to take up any leftover space with this rule:

#heart {
  flex-grow: 1;
}

And we size the sidebars and keep them from shrinking with these rules:

#left-arm, #right-arm {
  flex-shrink: 0;
  flex-basis: 200px;
}

Wrapping

If the children of a flex container have all been shrunk to their utmost and there still is not enough room for them, they can be made to wrap along the cross axis using the flex-wrap property. Valid values include nowrap, wrap, and wrap-reverse. We’ll see these at work in Flexercise.

Activity

With exactly one neighbor, claim your task on Crowdsource. (Everyone will be assigned task 1.) Then recreate the following page:

Apply styling to the following starter code to match the page above:

<!DOCTYPE html>
<html>

<head>
  <title>Gallery</title>
  <style>

/* Add your CSS here. */

#root {
  width: 960px;
}

  </style>
</head>

<body>
  <div id="root">
    <div class="card"><img src="https://picsum.photos/200/300?random=1"></div>
    <div class="card"><img src="https://picsum.photos/200/300?random=2"></div>
    <div class="card"><img src="https://picsum.photos/200/300?random=3"></div>
    <div class="card"><img src="https://picsum.photos/200/300?random=4"></div>
    <div class="card"><img src="https://picsum.photos/200/300?random=5"></div>
    <div class="card"><img src="https://picsum.photos/200/300?random=6"></div>
    <div class="card"><img src="https://picsum.photos/200/300?random=7"></div>
    <div class="card"><img src="https://picsum.photos/200/300?random=8"></div>
    <div class="card"><img src="https://picsum.photos/200/300?random=9"></div>
    <div class="card"><img src="https://picsum.photos/200/300?random=10"></div>
    <div class="card"><img src="https://picsum.photos/200/300?random=11"></div>
    <div class="card"><img src="https://picsum.photos/200/300?random=12"></div>
    <div class="card"><img src="https://picsum.photos/200/300?random=13"></div>
    <div class="card"><img src="https://picsum.photos/200/300?random=14"></div>
    <div class="card"><img src="https://picsum.photos/200/300?random=15"></div>
    <div class="card"><img src="https://picsum.photos/200/300?random=16"></div>
    <div class="card"><img src="https://picsum.photos/200/300?random=17"></div>
  </div>
</body>

</html>

Notice how the content is centered. We have used Flexbox to achieve vertical and horizontal centering. To achieve horizontal centering only, we have another option:

Notice also the shadowing, which was achieved with the box-shadow property.

Submit your solution on Crowdsource.

TODO

Here’s your TODO list for next time:

See you next time.

Sincerely,

P.S. It’s time for a haiku!

Life is like Flexbox
Youth and work are flex: 0
The rest is old age

P.P.S. Here’s the code we wrote together in class…

codepen.html

<!DOCTYPE html>

<html lang="en">
<head>
  <title></title>
  <style>
html {
  height: 100%;
}

body {
  margin: 0;
  display: flex;
  flex-direction: column;
  height: 100%;
}

#editor-root {
  display: flex;
  flex-direction: row;
  flex-basis: 200px;
}

.editor {
  flex-grow: 1;
}

#preview {
  flex-grow: 1;
}

  </style>
</head>

<body>
  <div id="editor-root">
    <textarea id="html-editor" class="editor"></textarea>
    <textarea id="css-editor" class="editor"></textarea>
  </div>

  <iframe src="https://twodee.org" id="preview"></iframe>



  <script>

const htmlEditor = document.getElementById('html-editor');
const cssEditor = document.getElementById('css-editor');
const preview = document.getElementById('preview');

function updatePreview() {
  const html = `
<html>
  <head>
    <style>
      ${cssEditor.value}
    </style>
  </head>
  <body>
    ${htmlEditor.value}
  </body>
</html>
  `;
  console.log(html);
  preview.srcdoc = html;
}

htmlEditor.addEventListener('input', updatePreview);
cssEditor.addEventListener('input', updatePreview);

  </script>
</body>

</html>

holygrail.html

<!DOCTYPE html>

<html lang="en">
  <head>
    <title></title>
    <style>
html, body, #root {
  height: 100%;
}





body {
  margin: 0;
}

#root {
  display: flex;
  flex-direction: column;
}

#chester {
  flex-grow: 2;
  display: flex;
  flex-direction: row;
}

#left-arm {
  background-color: purple;
  flex-basis: 200px;
}

#right-arm {
  background-color: pink;
  flex-basis: 200px;
}

#torso {
  background-color: red;
  flex-grow: 1;
}

header {
  background-color: blue;
}

footer {
  background-color: green;
}

    </style>
  </head>

  <body>

    <div id="root">
      <header>header</header>
      <div id="chester">
        <div id="left-arm">left</div>
        <div id="torso">torso</div>
        <div id="right-arm">right</div>
      </div>
      <footer>footer</footer>
    </div>

  </body>
</html>