Building Better Backgrounds

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

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
Image for post
Image for post
An example section with a CSS background image
<div class="c-section">
<div class="c-section__content">
<h1>Section Heading</h1>
<p>Lorem...</p>
<button>Call to Action</button>
</div>
</div>
<style>
/* Apply a background image to the section,
position it so we can see the couple,
and set the background to cover the available space */
.c-section {
max-width: 1400px;
margin: 0 auto;
padding: 48px;
background-image: url('..image.jpg');
background-size: cover;
background-position: right top;
}
/* Pad the content, make it 50% wide, and
add the transparent background color */
.c-section__content {
padding: 24px;
width: 50%;
background-color: rgba(255, 255, 255, .4);
}
</style>
Image for post
Image for post
…yuck.
Image for post
Image for post
this poor man looks so lonely…
Image for post
Image for post
i can read it now!
<div class="c-section">
<img class="c-section__image" src="../image.jpg">
<div class="c-section__content">
<h2>Section Title</h2>
<p>Lorem ipsum dolor...</p>
<button>Call to Action</button>
</div>
</div>
.c-section__image {
width: 100%;
height: auto;
}
.c-section__content {
padding: 24px;
}
  1. We need to make a background-image behave like an <img> tag on small displays.

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

Image for post
Image for post
our final result
<div class="c-section">
<img class="c-section__image" src="../image.jpg">
<div class="c-section__content">
<h2>Section Title</h2>
<p>Lorem ipsum dolor...</p>
<button>Call to Action</button>
</div>
</div>
.c-section__image {
width: 100%;
height: auto;
}
.c-section__content {
padding: 24px;
}
  1. Make the .c-section__content 50% wide again, and re-add the transparent background.
  2. Add overflow: hidden to the .c-section so that the image doesn’t overlap outside of the section.
  3. Give the .c-section__content a positioning context and a z-index so it shows up above the image.
<div class="c-section">
<img class="c-section__image" src="../image.jpg">
<div class="c-section__content">
<h2>Section Title</h2>
<p>Lorem ipsum dolor...</p>
<button>Call to Action</button>
</div>
</div>
.c-section {
/* This makes sure the image doesn't stick outside of the
section */
position: relative;
overflow: hidden;
}
.c-section__image {
/* The image will fill the width of the section and preserve
it's aspect ratio */

width: 100%;
height: auto;
position: absolute;
left: 0;
top: 0;

}
.c-section__content {
padding: 24px;
width: 50%;
/* This makes sure the content is above the image */
position: relative;
z-index: 2;

}
Image for post
Image for post
looks good!
.c-section {
position: relative;
overflow: hidden;
}
.c-section__image {
width: 100%;
height: auto;
}
@media (min-width: 600px) {
.c-section__image {
position: absolute;
left: 0;
top: 0;
}
}
.c-section__content {
width: 100%;
padding: 24px;
position: relative;
z-index: 2;
}
@media (min-width: 600px) {
.c-section__content {
margin: 24px;
background-color: rgba(255, 255, 255, .5);
width: 50%;
}
}
Image for post
Image for post
still looks great…right?
Image for post
Image for post
gross.
.c-section__image {
width: 100%;
height: 100%;
object-fit: cover;
object-position: top center;

}
Image for post
Image for post
perfect!
  • Improved SEO and accessibility: Background images can have alt text
  • No JavaScript required
  • When object-fit 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.

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.

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.
.c-section {
padding: 24px;
background-image: url(https://www.dropbox.com/s/xm1fohp06rfh1zd/2337770b75e90edc80cbe42d34c3c2bd-xxlarge.jpg?dl=1);
background-size: 100% auto;
background-repeat: no-repeat;

}
@media (min-width: 600px) {
.c-section {
background-size: cover;
background-position: top right;
}
}
.c-section__content {
width: 100%;
padding: 24px;
position: relative;
z-index: 2;
}
@media (min-width: 600px) {
.c-section__content {
background-color: rgba(255, 255, 255, .5);
width: 50%;
}
}
Image for post
Image for post
so close!
  • Our content is padded correctly
// Store all of our .c-sections in a variable called 'sections'
const sections = document.getElementsByClassName('c-section');
// Store all of our .c-sections in a variable called 'sections'
const sections = Array.from(document.getElementsByClassName('c-section'));
// Store all of our .c-sections in a variable called 'sections'
const sections = Array.from(document.getElementsByClassName('c-section'));
sections.forEach(section => {
// do stuff in here
});
// Store all of our .c-sections in a variable called 'sections'
const sections = Array.from(document.getElementsByClassName('c-section'));
sections.forEach(section => {
// Assign the background-image value to the variable 'image'
const imageUrl = window
.getComputedStyle(section)
.
getPropertyValue('background-image')
});
// Store all of our .c-sections in a variable called 'sections'
const sections = Array.from(document.getElementsByClassName('c-section'));
sections.forEach(section => {
// Assign the background-image value to the variable 'image'
const imageUrl = window
.getComputedStyle(section)
.getPropertyValue('background-image')
// Remove 'url("' and ')' from the image url string
.replace('url("', '')
.replace('")', '')

});
  • background-image: url('http://image.com)'
  • background-image: url("http://image.com")
  1. Remove every single or double quote in the url
// Store all of our .c-sections in a variable called 'sections'
const sections = Array.from(document.getElementsByClassName('c-section'));
sections.forEach(section => {
// Assign the background-image value to the variable 'image'
const imageUrl = window
.getComputedStyle(section)
.getPropertyValue('background-image')
// Get the piece of the string starting after the 4th
character and ending with the 2nd to last character.
// Then, replace all occurrences of ' or " with a blank
string.
.slice(4, -1).replace(/["|']/g, "")

});
// Store all of our .c-sections in a variable called 'sections'
const sections = Array.from(document.getElementsByClassName('c-section'));
sections.forEach(section => {
// Assign the background-image value to the variable 'image'
const image = new Image();
const imageUrl = window
.getComputedStyle(section)
.getPropertyValue(‘background-image’)
// Get the piece of the string starting after the 4th
character and ending with the 2nd to last character.
// Then, replace all occurrences of ' or " with a blank
string.
.slice(4, -1).replace(/["|']/g, "")

// Make the imageUrl the .src of the image
image.src = imageUrl;

});
// Store all of our .c-sections in a variable called 'sections'
const sections = Array.from(document.getElementsByClassName('c-section'));
sections.forEach(section => {
// Assign the background-image value to the variable 'image'
const image = new Image();
const imageUrl = window
.getComputedStyle(section)
.getPropertyValue(‘background-image’)
// Get the piece of the string starting after the 4th
character and ending with the 2nd to last character.
// Then, replace all occurrences of ' or " with a blank
string.
.slice(4, -1).replace(/["|']/g, "")

// Make the imageUrl the .src of the image
image.src = imageUrl;

// Set the padding-top, woohoo! Finally.
section.style.paddingTop = image.height + 'px';

});
// Store all of our .c-sections in a variable called 'sections'
const sections = Array.from(document.getElementsByClassName('c-section'));
window.addEventListener('load', () => {
sections.forEach(section => {
// Assign the background-image value to the variable 'image'
const image = new Image();
const imageUrl = window
.getComputedStyle(section)
.getPropertyValue(‘background-image’)
// Get the piece of the string starting after the 4th
character and ending with the 2nd to last character.
// Then, replace all occurrences of ' or " with a blank
string.
.slice(4, -1).replace(/["|']/g, "")

// Make the imageUrl the .src of the image
image.src = imageUrl;

// Set the padding-top, woohoo! Finally.
section.style.paddingTop = image.height + 'px';
});
});
const sections = Array.from(document.getElementsByClassName('c-section'));
const sectionBgStack = () =>
sections.forEach(section => {
const image = new Image();
image.src = window
.getComputedStyle(section)
.getPropertyValue(‘background-image’)
.slice(4, -1).replace(/["|']/g, "")

section.style.paddingTop = image.height + 'px';
});
}
// Run 'sectionBgStack' on window load and resize
window.addEventListener('load', sectionBgStack);
window.addEventListener('resize', sectionBgStack);
Image for post
Image for post
oh no!
Image for post
Image for post
there it is…1067!
const sections = Array.from(document.getElementsByClassName('c-section'));
const sectionBgStack = () => {
sections.forEach(section => {
const image = new Image();
image.src = window
.getComputedStyle(section)
.getPropertyValue('background-image')
.slice(4, -1).replace(/["|']/g, "")

const scale = section.offsetWidth / image.width;
section.style.paddingTop = (image.height * scale) + 'px';
})
};
// Run 'sectionBgStack' on window load and resize
window.addEventListener('load', sectionBgStack);
window.addEventListener('resize', sectionBgStack);
const throttle = (func, limit) => {
let wait = false;
return () => {
if (wait) return;
func();
wait = true;
setTimeout(() => wait = false, limit);
}
}
const sections = Array.from(document.getElementsByClassName('c-section'));
const sectionBgStack = () => {
sections.forEach(section => {
const image = new Image();
image.src = window
.getComputedStyle(section)
.getPropertyValue('background-image')
.slice(4, -1).replace(/["|']/g, "")

const scale = section.offsetWidth / image.width;
section.style.paddingTop = (image.height * scale) + 'px';
})
};
// Run 'sectionBgStack' on window load and resize
window.addEventListener('load', sectionBgStack);
window.addEventListener('resize', throttle(sectionBgStack, 400));
  • Your images aren’t part of your content; they are merely decoration.
  • There’s probably performance considerations from new Image()-ing so many times, if your site has a large number of sections.

person in providence

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