We’ve got a pretty specific subject this week because I was working on a pretty specific problem at work. I was building a coupon display screen for mobile users and when I set the background image, it was zoomed in too far. For example, here’s a beautiful image that looks like it would be a suitable mobile background based on its dimensions:
But here’s the result from setting it as the background property of the body element:
Sometimes when I’m writing, I have spend a lot of time just figuring out how to recreate the issue on a local environment. No such luck this time, as this looks exactly like what happened to me at work. What’s wrong?
If you want to follow along at home, open up a project and create an HTML and CSS file. I added an extension to VS Code to help easily provide boilerplate HTML (this one is popular), but you can also just copy this down since it’s very straightforward. I added a link to my CSS styles in the head element and an h1 in the body:
Then I downloaded my image from Pexels and added it to my project directory. Finally, I set up my simple CSS:
I set the background-image property of body using url with an argument of the relative path to the image. The ./ before the filename mean that the file is located in the same directory as the CSS file.
I’ll be basing my work on this GitHub Gist. We’ll go step-by-step through the properties and try to understand what each one is doing (side-note: I know the Gist uses background rather than background-image to set the image — we’ll address that later). To start, I’ll add this line to the body element in my stylesheet:
Unsurprisingly, background-size determines…the size of our background image. Though we used a keyword here, we can also set this property to one or two numeric values to set the specific dimensions (using one value will set the width, leaving the height to be ‘auto’). Here are a few examples with different settings:
Note that the first example, which sets our background-image width to only 100px, results in a series of repeated images. The next example looks like it’s a bit more in line with our mobile screen, but since we’ve hard-coded the value, it won’t be responsive. If we view this content on an iPad, the image isn’t going to look nearly as good. The last example uses cover, but we can see in this case that the image top doesn’t line up with the top of our screen and it repeats at the bottom. Still, we’re going to stick with this option because it will resize our image to cover the screen regardless of its dimensions.
Other options for this property include auto and contain. Auto will simply use the default setting that we first saw when we added the image. Contain will make sure that the full image is shown, which seems like it might be ideal but actually makes the image much smaller than we’d like it to be. This is likely because it has to account for every contingency for screen size, even a very very small one.
We’ve fixed our sizing issue, but it now looks like our image isn’t tall enough. Since it can’t fill out the whole screen vertically, it’s cutting off and repeating itself. In the Gist, we see that we can add a property to our background attribute called no-repeat, that looks like this:
background: url(./pexels-sean-whang-804269.jpg) no-repeat;
This is a shorthand that CSS is smart enough to interpret as the setting of a the background-repeat property. For clarity, we’ll use the more verbose syntax by adding this to our body styles:
Our image is set to match its top-left corner to the top-left corner of our display screen. It then resizes itself so that its width is equal to the width of our display. But if the relative image height is less than the screen’s height, there will be blank space on our screen. The image repeats by default to avoid this blank space, but that’s not an ideal solution for us. CSS provides us with the opportunity to make our own decisions about how to deal with this.
What if, rather than matching the top-left corner of our image to the top-left corner of our display, we matched center-to-center? Symmetry is generally a positive aesthetic indicator, so I’d like to promote that by setting the center of my screen as the anchor. We can use the background-position property to indicate how we would like our image to be mapped to our screen:
The first thing to note is that right top looks exactly the same as our default (which would be left top). Normally this wouldn’t be the case, but since we already set background-size to cover, our image width is always going to cover the full display. We won’t have a different view whether we are matching up right-to-right or left-to-left.
But there’s something that’s even more confusing — center center and left bottom don’t seem to be matching our image correctly on the Y-axis (vertically). In the first case, it looks like the bottom of our image is in the center of the screen. And then when we use the bottom property, the bottom of our image is almost at the top of the page! Shouldn’t it be at the bottom?
It took me a while to realize that these images weren’t being mapped to my display, but to my HTML. Here’s what right bottom looks like after I add another element:
Ultimately, our Gist indicated that we should be using center center for a dynamic full-screen display, but this looks like we’re going in the wrong direction unless we know we’re always going to have HTML filling up the whole screen. Before we address that issue, a few more notes about the background-position property:
We can also set background-position using percentages or static values like pixels or emphemeral units (em). Writing background-position: 50% 50% would be the same thing as writing center center, but it gives us a bit more flexibility if desired. Alternatively, we could write additional params to offset our right, left, center, top, or bottom attributes. Here’s right bottom shifted 125px off the right side and -125px off the bottom:
Per MDN, The background-attachment CSS property sets whether a background image’s position is fixed within the viewport, or scrolls with its containing block. There are three setting options: scroll (set by default), local, and fixed. Their impact on scrolling UI isn’t relative to us today (and frankly I find it very confusing), so we’re going to focus on how they affect positioning. If you want to learn more about scrolling, check out that MDN link or this css-tricks post.
Our problem here is that our image is positioned relative to its containing block — the body of our HTML. A viewport, on the other hand, references the area that is currently being viewed. The default selection for this property is scroll, but what happens if we change it to fixed (as in fixed within the viewport)?
Wow! Only halfway through the blog post and we’ve already got the experience we’re looking for! These settings resize our image to fit the screen without repeating anything or focusing on the wrong portion. But if we look at our Gist, there are still five more properties we haven’t applied. What are those for?
-webkit-background-size, -moz-background-size, and -o-background-size
I spent a lot of time researching these before I realized that they were all variations on the background-size attribute we set earlier that provide compatibility for different browsers (Safari, Mozilla, and Opera, respectively). According to Stack Overflow, they are used to help implement new or experimental CSS features before they are fully recognized by the W3. My guess is that these additions may be unnecessary today, as background-size doesn’t indicate any lack of support on the W3 website and I didn’t see any difference when I ran my code in Safari with and without -webkit-background-size.
Height is a curious addition to our code. This property determines the size of our element, but if you try adding this at home, you’ll notice no difference in display.
What I initially failed to realize here is that we are no longer setting background-specific properties, so when we set height to 100%, we are actually referencing the body element itself, not the background image. So this line isn’t added to assure that the image fills the body, but that the body fills its parent element. Its parent is <html>, which is only as large as the content of my site (one h1 element):
If I force html to 100% and then set body height to 50%, we can finally see an impact. Still, our background takes up the whole screen because of background-size: cover. Setting height to 100% covers us in case our background is set on an element smaller than its parent and if its parent is not fully shown on the screen.
Another popular version of this setting is 100vh, which stands for view height. This once again tells the element to be sized equal to the height of the window. Height can also be assigned with static numeric values or keywords auto, max-content, min-content, or fit-content. Max-content will set height according to the largest option based on the parent elements, min-content will focus on the size based on the content of the element itself, and fit-content is a combination of the two. It takes an argument and then sets the height, dynamically, based on the smallest value between max-height, min-height, and the argument. There’s an excellent demonstration in this article by GeeksforGeeks.
Finally, overflow determines how our site will react if our element is too large for the screen. This is another property that won’t be relevant unless our background element is flowing over the edge of our screen. If you want to see what that looks like, we can set both html and body height to 100% and set our overflow to scroll:
When we go back to our browser, we should be able to experience a bit of scroll. If we inspect the page, we’ll notice that the body element is slightly offset from html, therefore creating a slight overflow. If we do not set html to 100% however, it will only take up a small portion of our viewport’s height and therefore have no overflow, which means no scroll. This is again confusing because it doesn’t have anything to do with the size of the image, but instead with the size of the elements.
Following the Gist, we set our overflow property to hidden because we don’t want a user to be scrolling around finding unseen parts of our body element. The other options for this property are auto and visible, which means that content inside of an element will continue to be displayed even if that element has run out of space. This is best illustrated by a text box that has too much writing — if we select overflow: visible, the text will bleed onto other parts of our page instead of being contained inside the box.
A finished product
Ironically, we already saw what this would look like about three sections ago, but here’s another look for posterity:
CSS can be difficult for the same reason it can be easy: abstraction. More than any other language or framework that I’ve used, it’s hard to understand exactly what’s going on and what takes priority. It can seem overwhelming when we see seemingly endless stylesheets, but, as usual, we can overcome this feeling by taking things one step at a time and making an earnest effort to understand each individual line of code.
- GitHub Gist for Full size background image using CSS cover in mobile devices
- background from MDN
- background-attachment from css-tricks
- webkit, moz, and o from Stack Overflow
- CSS | fit-content() Property from GeeksforGeeks