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:

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>
…yuck.
this poor man looks so lonely…
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

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;

}
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%;
}
}
still looks great…right?
gross.
.c-section__image {
width: 100%;
height: 100%;
object-fit: cover;
object-position: top center;

}
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.

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%;
}
}
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);
oh no!
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