Some years ago I wrote an article1 describing how to render a background image with the following properties: responsive; fills the browser viewport; maintains a correct aspect ratio; never tiles/repeats and has neither vertical or horizontal borders. My complicated “solution” involved multiple stacked <div>
tags, a ream of CSS and failed disastrously without JavaScript. Fortunately, in 2015 it’s considerably easier.
For the design of my new home-page2 I wanted a single <div>
to have a background image with all of the properties listed above and without a JavaScript dependency. Additionally, I wanted to centre-align a tag or two, both vertically and horizontally,3 atop the image.
Let’s start with some basic HTML:
<div class="hero">
<h1>headline</h1>
<p>a regular paragraph of text</p>
</div>
Notice that there is nothing superfluous in the HTML. I have a container <div>
along with single <h1>
and <p>
tags. The magic is in the CSS.
I use the Sass CSS pre-processor along with Zell Liew’s “Mappy Breakpoints” mixin, so the CSS looks like this:
/*
Variables
$breakpoints: (
full: 1080px,
);
$tile-blue: rgb(61, 104, 144);
$region-header-height: 4rem;
$white: #fff;
$content-font-size: 14pt;
*/
.hero {
display: none;
}
@include mappy-bp(full height 400px) {
.hero {
background:
/* colour overlay */
linear-gradient(rgba(0, 64, 128, 0.7), rgba(0, 64, 128, 0.7)),
/* pixel grid diffuser */
url(/assets/images/pattern_pixels.png),
/* background image */
url(/assets/images/workspace_1024.jpg) bottom center / cover no-repeat $tile-blue;
/* element dimensions */
width: 100vw;
height: calc(100vh - #{$region-header-height});
position: relative;
/* use the CSS Flexible Box Layout Module */
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
h1 {
/* compensate for the navigation bar */
margin-top: -$region-header-height;
border: 1px solid $white;
border-radius: 10px;
padding: 1rem;
white-space: nowrap;
}
p {
max-width: 58ex;
font-size: $content-font-size;
text-align: center;
}
}
}
Let’s break this down:
- Mobile Friendly
- There is little value in displaying the full-screen image on a small screen and even less in having mobile devices download it. Thus the default state of
<div class="hero">
isdisplay: none
. This prevents all modern browsers from downloading or displaying the image.4 - Prevent Content Compression/
Overlap - If the viewport is less than 400 pixels in height, the content doesn’t have enough white-space around it and things start to get messy.
@include mappy-bp(full height 400px)
- The breakpoint above sets a minimum viewport height of
400px
, along with a minimum viewport width of1080px
. - Stacked Background Layers
- One of my concerns in using a full-screen image is that the file-size of the image has to be large in order to maintain quality when viewed at high resolution or on a larger display.
- An early version of the new home page had a background image that was some 900KB in size with dimensions of 5,760 × 3,840 pixels (aka: totally unacceptable). I created additional versions with different dimensions and saved with a selection of JPEG quality values. The variant I settled on is 1,024 × 683 pixels, 10% quality and weighs in at a svelte 49KB. Which is great from a bandwidth and performance perspective, but not so good for image quality.
- Fortunately, there’s an old trick for disguising a poor quality image on web: overlay a pixel-pattern image. In this case a transparent PNG consisting of a 2 × 2 pixel grid with a single, black pixel in the top left corner (and weighing in at 4KB). It acts as a diffuser of the background image, effectively camouflaging its artefacts and noise.
- The topmost layer is a simple blue tint. This mutes the principle image, colour-codes it to rest of the design and provides a contrast sufficient for the textual content that sits atop it.
- Fill the Viewport
- I’ve set the background image size to
cover
, which “Scales the image as large as possible and maintains image aspect ratio (image doesn’t get squished). The image ‘covers’ the entire width or height of the container. When the image and container have different dimensions, the image is clipped either left/right or top/bottom.”5 - I’ve set the width of
<div class="hero">
to100vw
. Thevw
unit translates as viewport-width and the value is a percentage. Thus the style instructs the browser to size<div class="hero">
to 100% of the width of the browser window. If the user resizes the browser window the 100% width of<div class="hero">
persists since it remains relative to the viewport width. - Handling the height is slightly different because, in this case, I need to accommodate the navigation menu. This has a fixed height, determined by the Sass variable
$region-header-height
, of4rem
. The relevant directive is this:height: calc(100vh - #{$region-header-height});
- I love the above line. One of the wonders of CSS
calc
is that it can accept values with different units and still produce a meaningful result. The height calculation above subtracts the interpolated Sass variable$region-header-height
(4rem
) from the browser viewport height. That’s so cool. - Flexbox for Content Layout
- I wanted to have the content of
<div class="hero">
dead-centre in the viewport, both vertically and horizontally. In my opinion,flexbox
is the only model worth bothering with for this. Withflexbox
, thejustify-content
andalign-items
properties are all I need to achieve dead-centre of<div class="hero">
. - Finally, having achieved vertical centre in
<div class="hero">
, I had to compensate for the navigation header in order to achieve perfect viewport centre: I pull the<h1>
back withmargin-top: -$region-header-height;
and BOOM!
-
On Urban Mainframe, my previous weblog, sadly now forever lost. ↩︎
-
See: Hold the Front Page! ↩︎
-
Aligning content dead centre in the viewport used to a hard enough problem in its own right. ↩︎
-
See: Tim Kadlec’s “Media Query & Asset Downloading Results.” ↩︎