Building Better Backgrounds

Approaches to stacking css background images on mobile for improved readability, using object-fit and ES2015+.

Zack Krida
Jul 17, 2017 · 16 min read

Imagine you work at a small web firm and your designer just sent you mockups for a new marketing site (Static mockups, designer? Get with the times!). She asks you to design a homepage with the following content sections:

Image for post
Image for post

Simple, right? Just a css background image; some padding and margin around; and a background color on the content. You jump into your code editor, grab a photo from your wedding (because stock photo licenses are confusing), and come up with something up like this:

Image for post
Image for post
An example section with a CSS background image

Looks great, right? Just what the designer ordered. After shipping the site with your new sections, you realize something: the designer never mocked up mobile. D’oh!

The sections you launched need to be made responsive. The question arises — how should they look at the smallest breakpoint? You’re a self reliant developer, and you feel embarrassed to ask you designer for yet another mockup. You quickly try out a couple of styles, using the same code, but making the content full-width and playing with color:

Image for post
Image for post

These aren’t terrible, but they certainly aren’t good! We completely obscure our subject’s faces; the original composition of the image is lost; and the text legibility is significantly reduced. Additionally, things start looking even worse once we add a section with longer content. We even lose one of our subjects entirely!

Image for post
Image for post
this poor man looks so lonely…

What we really need here is to completely rethink the mobile design. Maybe the mobile version doesn’t need to match the functionality of the desktop version, but should instead present the content in an ideal way. You decide to loop the designer back in after all, and she provides you with a much more sensible small-device mockup:

Image for post
Image for post
i can read it now!

This looks much better. Our content is legible and our image retains not only it’s original composition, but we can clearly see the subjects’ faces. It looks great! On large displays we’ll continue to show the photo as a background image, but on smaller ones we’ll stack the content below the image. The only question is: how on earth do we code this?

Our goal here seems relatively straightforward, but we have to remember we’re talking about CSS here. Remember vertical-centering pre-flexbox?

Anyway, if you look at the newest mobile mockup and think about it in it’s simplest form, it’s really just an html image tag followed by some padded content. We could express it like this very simply:

With our previous implementation on large displays using css background images and our new mobile approach using image tags, the solution to our problem can be explained in two ways:

  1. We need to make an behave like a background-image on large displays. Or:
  2. We need to make a background-image behave like an <img> tag on small displays.

So, let’s get started! Each approach has it’s pros and cons, which I’ll do my best to outline.

Making an tag behave like a background-image

Image for post
Image for post
our final result

To make this happen we’re going to have do a few things. We’ll start with an image tag above the content just like in our most-recent code sample:

Now, we’ve already achieved the desired effect on small displays. To make large displays work we’re going to:

  1. Absolutely position the to its parent . We’ll have to add to the to give the image a positioning context.
  2. Make the 50% wide again, and re-add the transparent background.
  3. Add to the so that the image doesn’t overlap outside of the section.
  4. Give the a positioning context and a so it shows up above the image.

Our updated code will look something like this:

and our finished result like this:

Image for post
Image for post
looks good!

Great! that’s exactly what we wanted. Finally, we can add some media queries to seamlessly switch between the two styles. Our final css code looks like this:

Perfect! Once again we get the desired effect:

Image for post
Image for post
still looks great…right?

One problem with this approach occurs when our content is taller than the background image. Currently, it will look like this:

Image for post
Image for post

The image stays 100% wide, To fix this, we can use the and properties. We’ll update the css to look like this:

Does this syntax look familiar? It should! The properties work just like the and properties, but for HTML images and videos.

With out new code, the long-content version looks as-expected:

Image for post
Image for post

Finally, we are done with the image as background-image approach. Some of the benefits of this approach are:

  • Can be used with HTML responsive images (the and properties) to decrease page size.
  • Improved SEO and accessibility: Background images can have alt text
  • No JavaScript required

There are also some cons to consider:

  • is supported in all modern browsers, but not in Internet Explorer, Edge, or older versions of Android & Safari:
  • When is unsupported, images appear as stretched and distorted
  • Your background images are now part of your content, and not just a decorative flair. This has implications for SEO, search indexing, and screen readers.

In conclusion, the image as background-image approach works well if you don’t need to support windows-specific browsers.

Note: There is a polyfill for object-fit that will give you full support in IE9+. It just requires a bit of trickery and configuration.

Making a background-image behave like an <img> tag

For this approach, there’s no css property to make a css background-image display inline like an image. We’re going to have to use JavaScript to position our image where we want it! If you haven’t written much JavaScript — you should start! It’s a fun and powerful way to add functionality to your site. We’re going to write our JavaScript using ES2015+ syntax, the latest standard of the language. To learn more about ES2015-ES2017 and more check out this presentation.

Let’s begin by comparing our original mobile design to the one from our previous example:

Image for post
Image for post
In the first example, our image is cropped and obscured. In the final implementation it stays true to the original.

In our first attempt, we crop the image so it covers all the available height. It seems like a good starting point for our approach to preserve the aspect ratio of the background image. Once we get the image the correct size, then we can work on positioning it. Let’s give that a try.

We’re going to start this approach with similar code to our original example, but this time, we’re going to remove on mobile. Instead, we’re going to make the background image 100% wide and give it an automatic height to preserve it’s original aspect ratio, just like in our image-tag-to-background-image example. The code ends up looking like this:

and our example looks like this on mobile:

Image for post
Image for post
so close!

Already we’ve got a few things right:

  • Our image is the right proportions
  • Our content is padded correctly

We really just need to find a way to add more space above the content. Specifically, the space needs to be the height of the image. We could manually add a to the content, but this approach fails quickly. As soon as we resize the browser, our padding-top would no longer match the new dimensions of the image. What we really need is a way to dynamically set the padding-top of the content based on the current height of the image. Whenever your site needs dynamic values, it’s usually JavaScript to the rescue!

For simplicity’s sake, pretend our JavaScript is in either an included file or an inline tag positioned before the closing tag on our page. We’re going to start by creating variables for our sections. Since our page might have multiple , we’re going to write code that will correctly resize all of the background images. We’ll start by selecting all of our sections:

Now we have all of our sections as a list in one variable. Great! However, you may assume that this list is a JavaScript Array, but actually returns what’s known as an HTML Collection. We need to turn this collection into an array, which ES2015’s method makes simple! You can pass an HTML Collection, Node List, or other array-like structure and turn it into an array. Our list of sections now looks like this:

As we said previously, we want to run our code for each section, so we’re going to loop through our array using the method, like

Great! Now we’re ready to write our logic to grab the background image from each section, detect its height, and apply that height as to the section. To do this, JavaScript needs to know the dimensions of our image. We’ll need to get the image url from the css property on our . We can get the values of all css properties on an element by using the method in JavaScript. To get the value of a single css property, we can follow our call of with a call to. The whole thing ends up looking like this:

If we run this JavaScript on our section, the value of the variable will be . This is close to what we want, but look at the output. The permalink to the image is wrapped in the text required by CSS. We could use JavaScript’s method to replace the and pieces. This would look like this:

This works but there’s another problem. In css, you can write with double quotes, like above, but also with single quotes, or even no quotes! All of the following are valid syntax:

To support these three cases, we’ll follow some simple steps:

  1. Get the string between the first 4 characters:
    and the last character .
  2. Remove every single or double quote in the url

Will handle this with JavaScript’s and methods. Our final JavaScript for this will look as follows:

Awesome. now the value of is our url. Phew!

At this point, we have the url of our background image for each section. Now, we need to get the dimensions of each image! In JavaScript we will need an for each url, which we can then get the width and height off of. To create a new without attaching it to the DOM (i.e, adding it to our HTML page), we can use the constructor. For each we’ll create an and attach our as the source. All together it looks like this:

Finally, we can access the height and width of the image with and , respectively. We can now set our section’s padding-top to the height of the image!

I hope I didn’t lead you on, because when you run this code, it doesn’t work! That’s because when this JavaScript executes, the browser may not have finished loading the images yet. We need to wrap our code in a event that fires once we know the images are loaded. The object’s event is a perfect candidate. MDN describes it as follows:

The event fires at the end of the document loading process. At this point, all of the objects in the document are in the DOM, and all the images, scripts, links and sub-frames have finished loading.

This sounds like exactly what we need, so we’ll now wrap our code in an event listener:

This is ugly, so now might be a good time to refactor. Look at how many layers of nesting we have. We can store our call inside a function in case we need to run it multiple times. At this point, our code is explicit enough that we can remove our comments as well. We can also just set without first storing it as — since we’re only using it once it really doesn’t need its own variable.

Finally, we also want our code to run any time we resize the browser! There’s another window event that we can write the same way as . The cleaned up version looks like this:

This looks a lot cleaner, and if you run it, it works! Wait a second, what’s all that white space?!

Image for post
Image for post
oh no!

If you your section, you’ll see our code is adding a whopping of to our section! Wow! Where’s that 1067 number coming from?

Well, if you download the image we’re using, you can see that it just so happens to be 1067px tall.

Image for post
Image for post
there it is…1067!

What’s happening is that our call to is retrieving the height of the actual image file, not the height the image is displaying at. There is no such property on the object, so we’ll have to calculate it ourselves.

Basically, we need the height of the image based on how wide the image is displaying. So in this case, if the image was showing half as wide, (800px), we’d want the height to be halved as well (533.5px). To do this we’ll base the height value off of the width value. But how? Like , returns the value of the image file, not the size it is displaying at. So, what can we get calculate the width of the image off of? Is there another element with the same width as the image?

The section!

That’s right, our image is the same width as our ! And since we’re already looping through our sections, we have the section width available to us. If we divide the by the width, we can get a scale ratio. If we multiply the by our scale ratio, we’ll have the correct size! Our revised code:

Finally, that ‘resize’ event is a problem. It fires off as many calls as possible on window resize and is terrible for performance! We’ll want to our resize call so it can only happen once per 300ms. This will boost performance significantly. I recently wrote what is the simplest, smallest throttle function i’ve ever seen in the wild:

So, let’s add that to our project and our call every 300ms:

And with that, we’re actually done. Whew! Here’s the pros of this approach:

  • It got you to write some cool JavaScript, right? 2017 baby.
  • Your images aren’t part of your content; they are merely decoration.

And the cons:

  • Your JavaScript will need to be run through a build process (think Babel, Buble, or other tools) for compatibility with older browsers.
  • There’s probably performance considerations from -ing so many times, if your site has a large number of sections.

Well, I tried. Hopefully you learned something at least!If you’re curious, you can view this JavaScript technique in production here:

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch

Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore

Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store