Gatsby: Does [gatsby-image] work with background images?

Created on 16 Oct 2017  ·  22Comments  ·  Source: gatsbyjs/gatsby

Loving Gatsby and the [gatsby-image] component!

Is there a way to use [gatsby-image] for background images as well? I frequently create page sections with a background image (background-size: cover) and would love to take advantage of the auto-resizing and optimization features of [gatsby-image] for this use case.

(If [gatsby-image] can't be used for background images, can it somehow be used to simulate background-size: cover?)

Most helpful comment

Alright, I've done some experimenting and have found a working solution for using gatsby-image for background images. I wanted to share this with anyone else getting stuck on this issue, as gatsby-image's features are too good to only use for inline images!

Here's what worked for me:

1. Add CSS Styling

Before we add styles, it's good to know that the gatsby-image component outputs the following HTML:

<div class="gatsby-image-outer-wrapper">
  <div class="gatsby-image-wrapper">
    <div></div>
    <!-- Placeholder (hidden after main image loads) -->
    <img style="...object-fit: cover; object-position: center center;">
    <!-- Main image -->
    <img style="...object-fit: cover; object-position: center center;">
  </div>
</div>

Styles set directly on gatsby-image will be applied to <div class="gatsby-image-wrapper>. Styles we want to apply to the image itself need to use a child selector to target the <img> tags.

To make gatsby-image act like a CSS background image, add the following styles (I've used gatsby-plugin-styled-components here):

import React from 'react'
import Image from 'gatsby-image'
import styled from 'styled-components'

const BgImage = styled(Image)`
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  z-index: -1;
  height: 100vh; // or whatever

  // Adjust image positioning (if image covers area with defined height) and add font-family for polyfill
  & > img {
    object-fit: cover !important; // or whatever
    object-position: 0% 0% !important; // or whatever
    font-family: 'object-fit: cover !important; object-position: 0% 0% !important;' // needed for IE9+ polyfill
  }
`

export default BgImage

Here is a version of the same BgImage component that uses props to set the dynamic values:

import React from 'react'
import Image from 'gatsby-image'
import styled from 'styled-components'

const BgImage = styled(Image)`
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  z-index: -1;
  height: ${props => props.height || 'auto'};

  // Adjust image positioning (if image covers area with defined height) and add font-family for polyfill
  & > img {
    object-fit: ${props => props.fit || 'cover'} !important;
    object-position: ${props => props.position || '50% 50%'} !important;
    font-family: 'object-fit: ${props => props.fit || 'cover'} !important; object-position: ${props => props.position || '50% 50%'} !important;'
  }
`

export default BgImage

The use of !important here is not ideal, but necessary to override the inline object-fit/object-position styles.

Note that the object-fit and object-position properties will not need to be adjusted unless the gatsby-image covers an area with a set height. For these use cases, object-fit: cover; usually still works for me and I just need to tweak the object-position value (gatsby-image sets it to center center by default).

2. Add IE9+ Polyfill for object-fit/object-position

The styles above will work in all browsers except IE (see caniuse.com: object-fit/object-position). Unfortunately, in IE, object-fit/object-position do not work, and any background-image with a set height will stretch to fill its container, distorting the image.

Fortunately, there is a polyfill that fixes this in IE9+, which you can find here. (Thanks to @fk for pointing this out.)

Here's how to implement the polyfill:

  1. Make sure you've included the extra font-family declaration in your CSS (see above)
  2. Install the polyfill in your project: npm install —save object-fit-images
  3. Create a gatsby-browser.js file in the root of your project
  4. Add the following to gatsby-browser.js:
import objectFitImages from 'object-fit-images'

exports.onInitialClientRender = () => {
  objectFitImages()
}

NOTE: I activated the polyfill in onInitialClientRender because the polyfill's instructions are to place the activation call "before </body>, or _on DOM ready_". Elsewhere, I've seen @KyleAMathews recommend that polyfills be implemented in onClientEntry instead, so I'm not sure if that would be better (any comment, Kyle?).

Hope that helps!

All 22 comments

Great to hear it's working well for ya!

On background images, check out the gatsby-image demo site https://using-gatsby-image.gatsbyjs.org/

Is that what you want?

Thanks for the quick reply!

Just to make sure I understand what's happening on the gatsby-image demo site...

  1. Instead of using background-image, gatsby-image always uses <img /> and background images are simulated using position: absolute;?
  2. Instead of using background-size/background-position to position images, gatsby-image always uses object-fit/object-position?

I'm perfectly happy to use gatsby-image this way for all my images as long as they display properly in all modern browsers... Does Gatsby include measures to support the object-fit/object-position properties in IE 11 and Edge (see browser support issues here: caniuse.com)?

If so, would you recommend we just avoid CSS background images altogether (to take advantage of Gatsby's automated image processing)? Using gatsby-image, is there a recommended approach for adjust the object-fit/object-position properties?

If not, is there a recommended, cross-browser way to use background images with background-size:cover in Gatsby?

@ooloth the component is very new so my answer to your question is… please play around with using gatsby-image as you'd like and report back what you find :-)

css background images are problematic because they don't allow you to (easily) add multiple thumbnail sizes for different sized devices. But you could work around this with media queries.

Haha. Understood. And will do. Thanks for your help!

If anyone else wants to share their best practices for the following, I'd be very interested:

  1. How to support object-fit/object-position in IE 11 and Edge (does Gatsby already do this?)
  2. How to tweak the object-fit/object-position values?

Regarding 1., take a look at https://github.com/bfred-it/object-fit-images

@fk Thanks for the polyfill! As of Oct 16, Edge supports object-fit/object-position, but IE11 still needs some help.

Can you please advise on one thing? I'm clear on how to install and import the polyfill...
npm install --save object-fit-images
import objectFitImages from 'object-fit-images'

..but I'm not sure where to place the activation call:
objectFitImages()

Can you please recommend where to place this line? I'm a bit new to both React and Gatsby.

(Btw, I've figured out how to adjust the object-fit/object-position values and will share that code as soon as I've sorted out adding the polyfill and its CSS adjustments.)

Alright, I've done some experimenting and have found a working solution for using gatsby-image for background images. I wanted to share this with anyone else getting stuck on this issue, as gatsby-image's features are too good to only use for inline images!

Here's what worked for me:

1. Add CSS Styling

Before we add styles, it's good to know that the gatsby-image component outputs the following HTML:

<div class="gatsby-image-outer-wrapper">
  <div class="gatsby-image-wrapper">
    <div></div>
    <!-- Placeholder (hidden after main image loads) -->
    <img style="...object-fit: cover; object-position: center center;">
    <!-- Main image -->
    <img style="...object-fit: cover; object-position: center center;">
  </div>
</div>

Styles set directly on gatsby-image will be applied to <div class="gatsby-image-wrapper>. Styles we want to apply to the image itself need to use a child selector to target the <img> tags.

To make gatsby-image act like a CSS background image, add the following styles (I've used gatsby-plugin-styled-components here):

import React from 'react'
import Image from 'gatsby-image'
import styled from 'styled-components'

const BgImage = styled(Image)`
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  z-index: -1;
  height: 100vh; // or whatever

  // Adjust image positioning (if image covers area with defined height) and add font-family for polyfill
  & > img {
    object-fit: cover !important; // or whatever
    object-position: 0% 0% !important; // or whatever
    font-family: 'object-fit: cover !important; object-position: 0% 0% !important;' // needed for IE9+ polyfill
  }
`

export default BgImage

Here is a version of the same BgImage component that uses props to set the dynamic values:

import React from 'react'
import Image from 'gatsby-image'
import styled from 'styled-components'

const BgImage = styled(Image)`
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  z-index: -1;
  height: ${props => props.height || 'auto'};

  // Adjust image positioning (if image covers area with defined height) and add font-family for polyfill
  & > img {
    object-fit: ${props => props.fit || 'cover'} !important;
    object-position: ${props => props.position || '50% 50%'} !important;
    font-family: 'object-fit: ${props => props.fit || 'cover'} !important; object-position: ${props => props.position || '50% 50%'} !important;'
  }
`

export default BgImage

The use of !important here is not ideal, but necessary to override the inline object-fit/object-position styles.

Note that the object-fit and object-position properties will not need to be adjusted unless the gatsby-image covers an area with a set height. For these use cases, object-fit: cover; usually still works for me and I just need to tweak the object-position value (gatsby-image sets it to center center by default).

2. Add IE9+ Polyfill for object-fit/object-position

The styles above will work in all browsers except IE (see caniuse.com: object-fit/object-position). Unfortunately, in IE, object-fit/object-position do not work, and any background-image with a set height will stretch to fill its container, distorting the image.

Fortunately, there is a polyfill that fixes this in IE9+, which you can find here. (Thanks to @fk for pointing this out.)

Here's how to implement the polyfill:

  1. Make sure you've included the extra font-family declaration in your CSS (see above)
  2. Install the polyfill in your project: npm install —save object-fit-images
  3. Create a gatsby-browser.js file in the root of your project
  4. Add the following to gatsby-browser.js:
import objectFitImages from 'object-fit-images'

exports.onInitialClientRender = () => {
  objectFitImages()
}

NOTE: I activated the polyfill in onInitialClientRender because the polyfill's instructions are to place the activation call "before </body>, or _on DOM ready_". Elsewhere, I've seen @KyleAMathews recommend that polyfills be implemented in onClientEntry instead, so I'm not sure if that would be better (any comment, Kyle?).

Hope that helps!

Argh damn, sorry for not coming back earlier @ooloth! Glad you figured it out!
Thank you for coming back and writing up your findings! 👍 ❤️

My pleasure!

@stolinski runs into the same issue in one of his tutorials. He goes about it a different way:
https://www.youtube.com/watch?v=zhM6C0P7VO0

<Img
  sizes={dataSizes}
  style={{
    position: "absolute",
    left: 0,
    top: 0,
    width: "100%",
    height: "100%"
  }}
/>

Much simpler! Thanks @grod220.

@grod220 that was easy... Thank you 😄

@enten That would work, but you'd miss out on the performance advantages of using gatsby-image to deliver an appropriately-resized version of the image at each viewport size via the srcset and sizes attributes.

For me, the ability to use gatsby-image to easily deliver the optimal copy of each image is Gatsby's best feature. Those benefits aren't available when you use CSS background images.

@ooloth Thanks for covering this. Gatsby newbie here. In your instructions with the styling, where does the actual image mentioned?

@fucata55 My pleasure!

The actual image is something you first query via graphql and then pass as a prop to your gatsby-image component's fluid or fixed prop (or if using v1, its sizes or resolutions prop).

The gatsby-image component will then generate all the copies of the image you'll need as well and add the complicated sizes attribute to the <img /> in the resulting HTML for you.

For a walkthrough on how to use gatsby-image (and send an image to it), see the gatsby-image docs here for v2 or here for v1 (they're great).

@ooloth your work has been very helpful. Thank you.

Is it possible to use "background-attachment: fixed; " ? If so, where exactly. I can get it working but not with safari. When I get it working the behavior is that the image looks good until I add more components in the page. The more components added the bigger the picture gets. So, then I polyfill for the safari issue but then the "background-attachment: fixed; " is no longer an option, or so it seems. Any help greatly appreciated.

@zachgaskin I wish I could help!

This sounds like it's probably a CSS issue (i.e. not related to Gatsby or gatsby-image) and I'm afraid I don't have much experience using background-attachment: fixed and haven't encountered the issue you're describing. If you're able to post a link to a minimal reproduction of this issue, I'd be happy to take a look, though.

According to caniuse.com, iOS Safari doesn't support background-attachment: fixed, but macOS Safari should work (should...).

It can be a real pain to sort out why certain browsers treat the same CSS differently, so you have my sympathy. Good luck!

@zachgaskin @ooloth I've come across this too. It's not ideal but the solution I have come up with is adding position: 'fixed' to the image. Although, this means it is in the background of the whole page. You need to make sure you have a background specified everywhere else. The z-index: -1 will make sure it is hidden behind your other backgrounds.

<Img
  sizes={dataSizes}
  style={{
    position: "fixed",
    left: 0,
    top: 0,
    width: "100%",
    height: "100%",
    z-index: -1
  }}
/>

Gatsby Background Image is a step forward...

You might find useful to import gatsby-image directly with a IE polyfill and apply objectFit and objectPosition to it as props, instead of using !important in your styles

import Img from 'gatsby-image/withIEPolyfill'

<Img
      fixed={heroImage.childImageSharp.fixed}
      style={{ width: '100%', height: '100%' }}
      objectFit="cover"
      objectPosition="bottom left"
   />

Thanks, @AronBe

I'm not working with Gatsby at the moment, so I don't know the latest; however, Gatsby Background Image (mentioned above) really did the trick for me on my last project.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

benstr picture benstr  ·  3Comments

brandonmp picture brandonmp  ·  3Comments

andykais picture andykais  ·  3Comments

signalwerk picture signalwerk  ·  3Comments

jimfilippou picture jimfilippou  ·  3Comments