Final Destination III

There are really only three things that need to happen to our HTML and CSS at this point. As it stands, the website we built in those last videos (see here and then here to catch up) is already minimally responsive – that is, if you visit it from your phone, it will shrink to fit your screen, and things won’t roll off the right side of the screen, or disappear behind an impossible-to-reach scroll bar.

But in doing so, the build we ended up with is overzealous, and squishes all those handsome images way down, rendering the graphics that are too tiny and hard to appreciate. This is, after all, an image gallery: If we want people to understand why these album covers (or photographs, or whatever) are worth displaying, then we need to ensure that they are as big and beautiful as they can be.

At the same time, though, there is another problem: The page is, well, bland. Vanilla. Beige. Predictable, symmetrical. There is no tension in the design, no rhythm. The page is basically just a digital version of a photo contact sheet: A plain, utilitarian catalog of images arranged in a grid. Meh.

What good is interactivity if you can’t make something that people want to interact with in the first place?

So how to do fix these things? In truth, we’re closed to done. We just need to add a couple of really important lines of code to what we’ve built so far. Here are the steps we’ll follow:

  1. First, we need to make it so we can change the size of some – but not all – of our images. If you imagine laying things out on a piece of graph paper, our images will always be either 1 unit square, or double that size (2 units square), or triple that size (3 units square).
  2. But making some of those images bigger is going to change how the images fit together on the page. When all of your boxes are the same size, they stack very neatly. When the boxes are three different sizes, well, it becomes a puzzle to solve. Our page is already good at shrinking and enlarging all of those cells as the page width grows, but now the challenge is to fit different-sized images together like a puzzle. As the window shrinks and grows, the puzzle will keep changing. So our code will need to be able to both resize the images and reorder them.

It turns out that we’ve been doing that for years, kinda, by using something called a “media query” in our HTML. An @media_query is just a list of different screen widths (up to 300px; up to 480px; up to 800px; 801px or more), different browser types, and so on: When your visitor opens your HTML, the browser checks the size of her screen, and loads the appropriate .css file. It is common for a single web page to have four or five different .css files associated with it; a mobile browser causes one file to load, while a 4k monitor causes a different .css file to load.

That is OK, but it was hellaciously inefficient: You end up building lots of different .css files that are all dedicated to the same web page.

We are avoiding @mediaqueries altogether. They seem very 20th Century to me, and it is time to leave them behind. When you have a moment, you can read about @media queries here and here. Two years ago, @media queries were a staple of web development. Because of what we’re about to do, however, we can kick them to the curb. Or at least escort them to the exit.

Step One: Grid Spans (CSS)


Let’s get started. If you’re working along with me, pull up the code that we’d built in those last two videos (referenced above) – the ones where we put 9 record album covers into a 3x3 grid, and ensured that it automatically resized the images when we enlarged or shrank the screen.

We’d observed that the grid was nice, and it seemed to work, but it was sooooo boring. Oh look, obsessive-compulsive symmetry! In every direction! It is roughly the equivalent of wearing khakis, a short-sleeved dress shirt, and a tie. The world needs less of that, not more.

On the Grid

Just like old-school paste-up artists, we’ve built things on a giant grid. Remember how, when we added the bjork-volta.jpg image to our neat little grid, it had initially misbehaved, and went way past its column’s declared width? (See the end of this video for a reminder.)

We fixed that problem, you may recall, by adding specifying a 100% width for our <img> selector.

img {
    display: block;
    margin: 0 auto;
    width: 100%;

A few things about those lines. First, display: block; is surprising, right, because aren’t images already blocks? Like paragraphs, and h1? Yeah, kinda. But also, not so much. For complicated historical reasons, your browser thinks of images in contradictory terms: As both inline elements and block-level elements. Frustrating. But in the end, we don’t need to worry about why it is necessary, we just need to remember that display: block gives us better results.

Now that we are getting more predictable behavior from our images, we can turn to the property that does the real work here. It is the width: 100% that actually tamed bjork-volta.jpg. Previously, when we put the image in a grid cell, it busted out, and stretched way past the edge of the window. It acted like the grid didn’t even exist.

But that is because by default, an image only knows how wide it is, not how wide you want it to be. By default, every image’s width is set to its actual width in pixels. Which kinda makes sense. So if your image is 635 pixels wide, you can imagine this “default” CSS:

img {
	width: 635px;

That’s what made Bjork’s album cover way too big. The browser engine just ignored the grid cell we put the image inside, and instead drew the image full size.

To fix this problem, we need to override the default CSS (above). We can do so by taking advantage of the idea of INHERITANCE again. We don’t want the image to be whatever-it-wants-to-be! We want the image to do whatever its PARENT tells it to do! By default, as we saw above, CSS ignores that family relationship. But since the PARENT of the image is the <div class="gridcell">, and we want the image to conform to the width of that PARENT, all we need to do is specify this:

img {
	width: 100%;

When the browser sees that 100%, it knows that we mean “the image should be exactly as wide as its PARENT is.”

I know that this image told you it was 635 pixels wide, but while it is living under my roof, it is only ever as wide as I am.

Word. So, for example, if the <img> appears inside a <div> that is 120px wide, then our <img> is going to get set at 120px wide. If the parent container of this <img> were an <article> element, or a <section> element, or a <figure> element, we will still get the same outcome: Setting width: 100% makes the image exactly the same width as the <article>, the <section>, or the <figure>. By setting width of a CHILD to a relative value (“33%”, “15%”, or even “110%"), that element INHERITS the actual size of the PARENT (209px, 95px, or even 698px).

So that’s what fixed our overly-large bjork-volta.jpg image. It made each of our little images fit comfortably inside their columns (their PARENT grid cells).

Which was great. But since all the grid cells are the same width, that means that all the children of those grid cells will also be the same width. So what if we want to make some of the images wider than others? Not in an out-of-control-and-off-the-page-like-Bjork sense, of course. Something more dignified.

Let’s turn back to the place where we defined the width of those columns (those grid cells) in the first place: Our .gridcontainer selector in the CSS file:

.gridcontainer {
    grid-template-columns:  repeat(3, 1fr);

This divided the grid horizontally into 3 equal columns (each was set to be 1 “fraction” of that total width; 3 columns means each is roughly 33.333% of the width of the .gridcontainer itself.

(See how CSS is obsessed with family relationships? .gridcontainer is the grandparent, .gridcell is the parent, and img is the child.)

Now I just said that the equivalent of grid-template-columns: repeat(3, 1fr) was roughly grid-template-columns: 33% 33% 33%. But that is really what academics call “a lie.” Honestly, it SHOULD BE THE SAME THING. It really should. But it isn’t. Because… well, who cares why? We know to expect this kind of nonsense from CSS by now. If you really want to know, take a deep breath and then see this article. But don’t say I didn’t warn you.

So let’s just leave it at this: grid-template-columns: 1fr 1fr 1fr is the only way to specify “create three columns of equal (but variable) width”.

More Than This

So we’ve got everyone behaving and sticking to their lanes!

Now what?

Well, remember that we decided we wanted some of our images to get bigger. So now we need to define a new class that specifies how many of those lanes (columns or rows) we want our <img> to occupy! In other words, to keep things really orderly, but still interesting, we need to figure out how to say that an image should be 1 column wide, 2 columns wide, or 3 columns wide.

It is easily done. Here’s our new class: Let’s call it .large

.large {
    grid-column: span 2;
    grid-row: span 2;

The word “span” is part of the magic, here. In that first property, we’re setting the grid-column to span 2. In other words, we’re saying “when we have a grid cell that is “class=large”, then that particular cell will “span” 2 columns and 2 rows. It temporarily melts two columns and two rows together, making a bigger cell out of them. Our “large”-class image will always start in one column and finish in the next. So if I place the image in column two, it will stretch across both columns two and three! If I put the image in column four, it will end up occupying both columns four and five.

So you’re wondering, is “span 2” all that we can do? Can we say “start in column x and end in column y,” or “start 3 columns from the far right column,” or “start in the second column and stretch across all but the final column”? Yes, yes we can. This property is powerful, and has lots of shorthand versions, so I don’t want to discuss it in depth – it just gets confusing. But here is one resource that can introduce other features of grid spans. Take a look later, though: Stick with this for now.

Now that we’ve spanned two cells horizontally and two cells vertically, let’s make another new class, this one for even bigger images:

.huge {
    grid-column: span 3;
    grid-row: span 3;

Now we know that we can make any of the <div>...</div> elements (the CHILDREN) inside of our <div class="gridcontainer"> PARENT occupy more than just one cell! They can also occupy 4 cells (2 columns and 2 rows) or 9 cells (3 columns and 3 rows).

And that gives us the ability to change things up, add tension and drama. It helps us get away from the “automated” look of a photographer’s contact sheet, and allows us to breathe some life into things.

Nota bene: You don’t have to limit yourself to just <img> inside these new classes. You can put text there. Or (imagine!) maybe just leave it blank! Maybe just leave some of the precious Real Estate on your visitor’s screen unoccupied! It seems to me that there must be a name for that almost-forgotten practice….

Just to hammer home my @#!%$& point: Neither of those pages actually make use of whitespace themselves, because ad revenue or whatever. Sigh. Whenever you get a chance, spend some time with the works of Edward Tufte, one of our most gifted information architects. His words ring true here (and you should write this down, and quote him frequently):

“Clutter is a failure of design, not an attribute of information.” — Tufte

Step Two: Grid Spans (HTML)

So we’ve got those two new classes defined in our .css file. How to implement them? Well, we just assign them to a few of the CHILDREN of our .gridcontainer PARENT <div>. That’s it. (This is one of those moments where you can fool around with it in your browser window until you like the way it looks!)

I just need to add this new class, so that it sits beside the old .gridcell class – that way, the browser uses them both! Just separate the classes with a single space! Easy!

Here, I’ve added the .big class to my second image <div> here. NOTE I’ve removed the real image information, because it is irrelevant here. You’ll need to include it, as before.

<div class="gridcontainer">
    <div class="gridcell">
        <img src="..." alt="..." />
    <div class="gridcell big">
        <img src="..." alt="..." />

Remember: A <div class="gridcell big"> takes up more room than the plain old <div class="gridcell"> image. And a <div class="gridcell huge"> takes up even more room! Use them sparingly!

Now we just need to deal with one more thing: Now that our images can be different sizes, we need to make this grid smarter.

Step Three: Automating the Grid

This part really deserves a dissertation or so worth of explanation. But let’s cut to the chase. Here is an explanation of each of the four lines in my new <div class="gridcontainer">. You can add a lot more, or take a different approach, etc., but here is (at a minimum) what we need to make this happen.

.gridcontainer {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
    grid-auto-rows: 1fr;
    grid-gap: 15px;
    grid-auto-flow: dense;

The second line continuously resizes the columns in a row, shrinking them down to as little as 160px, in order to ensure that – no matter the width of our browser window – all of the columns are UNIFORMLY WIDE.

So equal column widths is no big deal. What IS a big deal is the other part of this little algorithm: Because I say that the total number of columns in my row should be “auto-fit” (instead of, say, 2 or 5), the equation becomes much more dynamic. Now I’m saying:

  1. I need each column to be 160px wide or more; and
  2. I need all of the columns to be the same width; and
  3. I don’t care how many columns there are.

This set of rules is supplemented by what we said about <img> (make it 100% of the width of its PARENT). Which normally would just be a single column… except for those new classes, like .big. When our code bumps into an image that takes up two columns, it can’t break it up! It has to keep those two columns (or three columns!) together! So when it figures out a suitable column width, but then notices one of our <div class="gridcell big"> images would go over the right edge of our boundary, it “auto-fits”: It pushes the big 2-cell image down onto the next row.

But what about the space that is left over? That’s where this property comes in: grid-auto-flow: dense. It looks for those empty cells (columns or rows) and fills them with the <div class="gridcell"> images that it hasn’t used yet. In other words, as it works to build the page, it will grab stuff it hasn’t yet placed – from further back in the queue – and bring them to the front of the line. It is a fantastic little feature.

You can do make it even more complex. Jen Simmons famously recreated a dynamic Mondrian painting using this same technique. One of her examples is here (it isn’t a real painting – resize your browser window and be amazed). Her video on the experiment is worth watching, when you have the time.


So that’s it. That’s everything you need. Probably unnecessarily long-winded on my part, but that’s (in part) because of the challenge of a 2.9 week term. Do I think you’ve understood everything I tried to explain here? Nope. It took me months to really start to understand how all of this stuff worked together. Again, it is a practice, like playing the piano or speaking German. You can “know” how it works, but you have to actually do it, again and again and again, before you really "know" how it works.

Here’s one last codepen to experiment with. Note that it doesn’t use images, it just uses blank spaces, but the principle is the same. Fool around with some of the code to get a feel for how it works, then adjust the code in your Project accordingly.

Obviously, I don’t want to just give you the Project code you’ll need. So this example changes things up a bit by using 2 different spans: One is horizontal, the other is vertical. If that starts to get confusing, remember CSS is super flexible: Just delete the properties under one of them (e.g., .highcard) so that it is no longer in the mix. It will still be in the HTML, but it won’t have any effect. You’ll understand it all much better if you reconfigure it, mix it up, change it, even randomly, and think about the outcomes you’re seeing.

Keep it simple. Make one thing work before moving on to the next. Iterate.