diff --git a/examples/using-gatsby-image/src/components/floating-image.js b/examples/using-gatsby-image/src/components/floating-image.js
index 56ef35ca8b781..f033653c0ffcd 100644
--- a/examples/using-gatsby-image/src/components/floating-image.js
+++ b/examples/using-gatsby-image/src/components/floating-image.js
@@ -12,18 +12,6 @@ const Image = styled(Img)`
margin-left: ${rhythm(options.blockMarginBottom * 2)};
margin-right: -${gutter.default};
- ${mq.phablet} {
- display: none;
- }
-`
-
-const ImageDesktop = styled(Image)`
- display: none;
-
- ${mq.phablet} {
- display: block;
- }
-
${mq.tablet} {
margin-right: -${gutter.tablet};
}
@@ -53,15 +41,14 @@ const FloatingImage = ({
https://www.gatsbyjs.org/packages/gatsby-image/#gatsby-image-props
*/}
-
diff --git a/packages/gatsby-image/README.md b/packages/gatsby-image/README.md
index 409fad98a03eb..cc305c25ce34c 100644
--- a/packages/gatsby-image/README.md
+++ b/packages/gatsby-image/README.md
@@ -331,31 +331,85 @@ You will need to add it in your graphql query as is shown in the following snipp
}
```
+## Art-directing multiple images
+
+`gatsby-image` supports showing different images at different breakpoints, which is known as [art direction](https://developer.mozilla.org/en-US/docs/Learn/HTML/Multimedia_and_embedding/Responsive_images#Art_direction). To do this, you can define your own array of `fixed` or `fluid` images, along with a `media` key per image, and pass it to `gatsby-image`'s `fixed` or `fluid` props. The `media` key that is set on an image can be any valid CSS media query.
+
+```jsx
+import React from "react"
+import { graphql } from "gatsby"
+import Img from "gatsby-image"
+
+export default ({ data }) => {
+ // Set up the array of image data and `media` keys.
+ // You can have as many entries as you'd like.
+ const sources = [
+ data.mobileImage.childImageSharp.fluid,
+ {
+ ...data.desktopImage.childImageSharp.fluid,
+ media: `(min-width: 768px)`,
+ },
+ ]
+
+ return (
+
+
Hello art-directed gatsby-image
+
+
+ )
+}
+
+export const query = graphql`
+ query {
+ mobileImage(relativePath: { eq: "blog/avatars/kyle-mathews.jpeg" }) {
+ childImageSharp {
+ fluid(maxWidth: 1000, quality: 100) {
+ ...GatsbyImageSharpFluid
+ }
+ }
+ }
+ desktopImage(
+ relativePath: { eq: "blog/avatars/kyle-mathews-desktop.jpeg" }
+ ) {
+ childImageSharp {
+ fluid(maxWidth: 2000, quality: 100) {
+ ...GatsbyImageSharpFluid
+ }
+ }
+ }
+ }
+`
+```
+
+While you could achieve a similar effect with plain CSS media queries, `gatsby-image` accomplishes this using the `` tag, which ensures that browsers only download the image they need for a given breakpoint.
+
## `gatsby-image` props
-| Name | Type | Description |
-| ---------------------- | ------------------- | --------------------------------------------------------------------------------------------------------------------------- |
-| `fixed` | `object` | Data returned from the `fixed` query |
-| `fluid` | `object` | Data returned from the `fluid` query |
-| `fadeIn` | `bool` | Defaults to fading in the image on load |
-| `durationFadeIn` | `number` | fading duration is set up to 500ms by default |
-| `title` | `string` | Passed to the `img` element |
-| `alt` | `string` | Passed to the `img` element. Defaults to an empty string `alt=""` |
-| `crossOrigin` | `string` | Passed to the `img` element |
-| `className` | `string` / `object` | Passed to the wrapper element. Object is needed to support Glamor's css prop |
-| `style` | `object` | Spread into the default styles of the wrapper element |
-| `imgStyle` | `object` | Spread into the default styles of the actual `img` element |
-| `placeholderStyle` | `object` | Spread into the default styles of the placeholder `img` element |
-| `placeholderClassName` | `string` | A class that is passed to the placeholder `img` element |
-| `backgroundColor` | `string` / `bool` | Set a colored background placeholder. If true, uses "lightgray" for the color. You can also pass in any valid color string. |
-| `onLoad` | `func` | A callback that is called when the full-size image has loaded. |
-| `onStartLoad` | `func` | A callback that is called when the full-size image starts loading, it gets the parameter { wasCached: boolean } provided. |
-| `onError` | `func` | A callback that is called when the image fails to load. |
-| `Tag` | `string` | Which HTML tag to use for wrapping elements. Defaults to `div`. |
-| `objectFit` | `string` | Passed to the `object-fit-images` polyfill when importing from `gatsby-image/withIEPolyfill`. Defaults to `cover`. |
-| `objectPosition` | `string` | Passed to the `object-fit-images` polyfill when importing from `gatsby-image/withIEPolyfill`. Defaults to `50% 50%`. |
-| `loading` | `string` | Set the browser's native lazy loading attribute. One of `lazy`, `eager` or `auto`. Defaults to `lazy`. |
-| `critical` | `bool` | Opt-out of lazy-loading behavior. Defaults to `false`. Deprecated, use `loading` instead. |
+| Name | Type | Description |
+| ---------------------- | ------------------- | --------------------------------------------------------------------------------------------------------------------------------------------- |
+| `fixed` | `object` / `array` | Data returned from the `fixed` query. When prop is an array it has to be combined with `media` keys, allows for art directing `fixed` images. |
+| `fluid` | `object` / `array` | Data returned from the `fluid` query. When prop is an array it has to be combined with `media` keys, allows for art directing `fluid` images. |
+| `fadeIn` | `bool` | Defaults to fading in the image on load |
+| `durationFadeIn` | `number` | fading duration is set up to 500ms by default |
+| `title` | `string` | Passed to the `img` element |
+| `alt` | `string` | Passed to the `img` element. Defaults to an empty string `alt=""` |
+| `crossOrigin` | `string` | Passed to the `img` element |
+| `className` | `string` / `object` | Passed to the wrapper element. Object is needed to support Glamor's css prop |
+| `style` | `object` | Spread into the default styles of the wrapper element |
+| `imgStyle` | `object` | Spread into the default styles of the actual `img` element |
+| `placeholderStyle` | `object` | Spread into the default styles of the placeholder `img` element |
+| `placeholderClassName` | `string` | A class that is passed to the placeholder `img` element |
+| `backgroundColor` | `string` / `bool` | Set a colored background placeholder. If true, uses "lightgray" for the color. You can also pass in any valid color string. |
+| `onLoad` | `func` | A callback that is called when the full-size image has loaded. |
+| `onStartLoad` | `func` | A callback that is called when the full-size image starts loading, it gets the parameter { wasCached: boolean } provided. |
+| `onError` | `func` | A callback that is called when the image fails to load. |
+| `Tag` | `string` | Which HTML tag to use for wrapping elements. Defaults to `div`. |
+| `objectFit` | `string` | Passed to the `object-fit-images` polyfill when importing from `gatsby-image/withIEPolyfill`. Defaults to `cover`. |
+| `objectPosition` | `string` | Passed to the `object-fit-images` polyfill when importing from `gatsby-image/withIEPolyfill`. Defaults to `50% 50%`. |
+| `loading` | `string` | Set the browser's native lazy loading attribute. One of `lazy`, `eager` or `auto`. Defaults to `lazy`. |
+| `critical` | `bool` | Opt-out of lazy-loading behavior. Defaults to `false`. Deprecated, use `loading` instead. |
+| `fixedImages` | `array` | An array of objects returned from `fixed` queries. When combined with `media` keys, allows for art directing `fixed` images. |
+| `fluidImages` | `array` | An array of objects returned from `fluid` queries. When combined with `media` keys, allows for art directing `fluid` images. |
## Image processing arguments
diff --git a/packages/gatsby-image/index.d.ts b/packages/gatsby-image/index.d.ts
index 11d6b01ccf9e5..976e4a2b08f21 100644
--- a/packages/gatsby-image/index.d.ts
+++ b/packages/gatsby-image/index.d.ts
@@ -9,6 +9,7 @@ export interface FixedObject {
tracedSVG?: string
srcWebp?: string
srcSetWebp?: string
+ media?: string
}
export interface FluidObject {
@@ -20,6 +21,7 @@ export interface FluidObject {
tracedSVG?: string
srcWebp?: string
srcSetWebp?: string
+ media?: string
}
interface GatsbyImageProps {
@@ -27,6 +29,8 @@ interface GatsbyImageProps {
sizes?: FluidObject
fixed?: FixedObject
fluid?: FluidObject
+ fixedImages?: FixedObject[]
+ fluidImages?: FluidObject[]
fadeIn?: boolean
title?: string
alt?: string
diff --git a/packages/gatsby-image/src/__tests__/__snapshots__/index.js.snap b/packages/gatsby-image/src/__tests__/__snapshots__/index.js.snap
index dbd0f6218016a..bb624596d6cfd 100644
--- a/packages/gatsby-image/src/__tests__/__snapshots__/index.js.snap
+++ b/packages/gatsby-image/src/__tests__/__snapshots__/index.js.snap
@@ -22,6 +22,9 @@ exports[` should have a transition-delay of 1sec 1`] = `
srcset="some srcSetWebp"
type="image/webp"
/>
+
should have a transition-delay of 1sec 1`] = `
/>
- <picture><source type='image/webp' srcset="some srcSetWebp" /><img loading="lazy" width="100" height="100" srcset="some srcSet" src="test_image.jpg" alt="Alt text for the image" title="Title for the image" style="position:absolute;top:0;left:0;opacity:1;width:100%;height:100%;object-fit:cover;object-position:center"/></picture>
+ <picture><source type='image/webp' srcset="some srcSetWebp" /><source srcset="some srcSet" /><img loading="lazy" width="100" height="100" srcset="some srcSet" src="test_image.jpg" alt="Alt text for the image" title="Title for the image" style="position:absolute;top:0;left:0;opacity:1;width:100%;height:100%;object-fit:cover;object-position:center"/></picture>
@@ -64,6 +67,9 @@ exports[` should render fixed size images 1`] = `
srcset="some srcSetWebp"
type="image/webp"
/>
+
should render fixed size images 1`] = `
/>
- <picture><source type='image/webp' srcset="some srcSetWebp" /><img loading="lazy" width="100" height="100" srcset="some srcSet" src="test_image.jpg" alt="Alt text for the image" title="Title for the image" style="position:absolute;top:0;left:0;opacity:1;width:100%;height:100%;object-fit:cover;object-position:center"/></picture>
+ <picture><source type='image/webp' srcset="some srcSetWebp" /><source srcset="some srcSet" /><img loading="lazy" width="100" height="100" srcset="some srcSet" src="test_image.jpg" alt="Alt text for the image" title="Title for the image" style="position:absolute;top:0;left:0;opacity:1;width:100%;height:100%;object-fit:cover;object-position:center"/></picture>
@@ -110,6 +116,10 @@ exports[` should render fluid images 1`] = `
srcset="some srcSetWebp"
type="image/webp"
/>
+
should render fluid images 1`] = `
/>
- <picture><source type='image/webp' srcset="some srcSetWebp" sizes="(max-width: 600px) 100vw, 600px" /><img loading="lazy" sizes="(max-width: 600px) 100vw, 600px" srcset="some srcSet" src="test_image.jpg" alt="Alt text for the image" title="Title for the image" style="position:absolute;top:0;left:0;opacity:1;width:100%;height:100%;object-fit:cover;object-position:center"/></picture>
+ <picture><source type='image/webp' srcset="some srcSetWebp" sizes="(max-width: 600px) 100vw, 600px" /><source srcset="some srcSet" sizes="(max-width: 600px) 100vw, 600px" /><img loading="lazy" sizes="(max-width: 600px) 100vw, 600px" srcset="some srcSet" src="test_image.jpg" alt="Alt text for the image" title="Title for the image" style="position:absolute;top:0;left:0;opacity:1;width:100%;height:100%;object-fit:cover;object-position:center"/></picture>
+
+
+
+`;
+
+exports[` should render multiple fixed image variants 1`] = `
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ <picture><source type='image/webp' media="only screen and (min-width: 768px)" srcset="some other srcSetWebp" /><source media="only screen and (min-width: 768px)" srcset="some other srcSet" /><source type='image/webp' srcset="some srcSetWebp" /><source srcset="some srcSet" /><img loading="lazy" width="100" height="100" srcset="some other srcSet" src="test_image_2.jpg" alt="Alt text for the image" title="Title for the image" style="position:absolute;top:0;left:0;opacity:1;width:100%;height:100%;object-fit:cover;object-position:center"/></picture>
+
+
+
+`;
+
+exports[` should render multiple fluid image variants 1`] = `
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ <picture><source type='image/webp' media="only screen and (min-width: 768px)" srcset="some other srcSetWebp" sizes="(max-width: 600px) 100vw, 600px" /><source media="only screen and (min-width: 768px)" srcset="some other srcSet" sizes="(max-width: 600px) 100vw, 600px" /><source type='image/webp' srcset="some srcSetWebp" sizes="(max-width: 600px) 100vw, 600px" /><source srcset="some srcSet" sizes="(max-width: 600px) 100vw, 600px" /><img loading="lazy" sizes="(max-width: 600px) 100vw, 600px" srcset="some other srcSet" src="test_image_2.jpg" alt="Alt text for the image" title="Title for the image" style="position:absolute;top:0;left:0;opacity:1;width:100%;height:100%;object-fit:cover;object-position:center"/></picture>
diff --git a/packages/gatsby-image/src/__tests__/index.js b/packages/gatsby-image/src/__tests__/index.js
index 102963e2c686b..7d1d70b90cba4 100644
--- a/packages/gatsby-image/src/__tests__/index.js
+++ b/packages/gatsby-image/src/__tests__/index.js
@@ -23,6 +23,46 @@ const fluidShapeMock = {
base64: `string_of_base64`,
}
+const fixedImagesShapeMock = [
+ {
+ width: 100,
+ height: 100,
+ src: `test_image.jpg`,
+ srcSet: `some srcSet`,
+ srcSetWebp: `some srcSetWebp`,
+ base64: `string_of_base64`,
+ },
+ {
+ width: 100,
+ height: 100,
+ src: `test_image_2.jpg`,
+ srcSet: `some other srcSet`,
+ srcSetWebp: `some other srcSetWebp`,
+ base64: `other_string_of_base64`,
+ media: `only screen and (min-width: 768px)`,
+ },
+]
+
+const fluidImagesShapeMock = [
+ {
+ aspectRatio: 1.5,
+ src: `test_image.jpg`,
+ srcSet: `some srcSet`,
+ srcSetWebp: `some srcSetWebp`,
+ sizes: `(max-width: 600px) 100vw, 600px`,
+ base64: `string_of_base64`,
+ },
+ {
+ aspectRatio: 2,
+ src: `test_image_2.jpg`,
+ srcSet: `some other srcSet`,
+ srcSetWebp: `some other srcSetWebp`,
+ sizes: `(max-width: 600px) 100vw, 600px`,
+ base64: `string_of_base64`,
+ media: `only screen and (min-width: 768px)`,
+ },
+]
+
const setup = (
fluid = false,
props = {},
@@ -51,6 +91,32 @@ const setup = (
return container
}
+const setupImages = (
+ fluidImages = false,
+ onLoad = () => {},
+ onError = () => {}
+) => {
+ const { container } = render(
+
+ )
+
+ return container
+}
+
describe(` `, () => {
it(`should render fixed size images`, () => {
const component = setup()
@@ -62,6 +128,16 @@ describe(` `, () => {
expect(component).toMatchSnapshot()
})
+ it(`should render multiple fixed image variants`, () => {
+ const component = setupImages()
+ expect(component).toMatchSnapshot()
+ })
+
+ it(`should render multiple fluid image variants`, () => {
+ const component = setupImages(true)
+ expect(component).toMatchSnapshot()
+ })
+
it(`should have correct src, title, alt, and crossOrigin attributes`, () => {
const imageTag = setup().querySelector(`picture img`)
expect(imageTag.getAttribute(`src`)).toEqual(`test_image.jpg`)
@@ -91,9 +167,39 @@ describe(` `, () => {
})
it(`should have the the "critical" prop set "loading='eager'"`, () => {
+ jest.spyOn(global.console, `log`)
+
const props = { critical: true }
const imageTag = setup(false, props).querySelector(`picture img`)
expect(imageTag.getAttribute(`loading`)).toEqual(`eager`)
+ expect(console.log).toBeCalled()
+ })
+
+ it(`should warn if image variants provided are missing media keys.`, () => {
+ jest.spyOn(global.console, `warn`)
+
+ render(
+
+ )
+ expect(console.warn).toBeCalled()
})
it(`should call onLoad and onError image events`, () => {
diff --git a/packages/gatsby-image/src/index.js b/packages/gatsby-image/src/index.js
index 80c9d440fa894..d9f2bf0f88747 100644
--- a/packages/gatsby-image/src/index.js
+++ b/packages/gatsby-image/src/index.js
@@ -37,29 +37,43 @@ const convertProps = props => {
convertedProps.loading = `eager`
}
+ // convert fluid & fixed to arrays so we only have to work with arrays
+ if (convertedProps.fluid) {
+ convertedProps.fluid = groupByMedia([].concat(convertedProps.fluid))
+ }
+ if (convertedProps.fixed) {
+ convertedProps.fixed = groupByMedia([].concat(convertedProps.fixed))
+ }
+
return convertedProps
}
+/**
+ * Find the source of an image to use as a key in the image cache.
+ * Use `the first image in either `fixed` or `fluid`
+ * @param {{fluid: {src: string}[], fixed: {src: string}[]}} args
+ * @return {string}
+ */
+const getImageSrcKey = ({ fluid, fixed }) => {
+ const data = (fluid && fluid[0]) || (fixed && fixed[0])
+
+ return data.src
+}
+
// Cache if we've seen an image before so we don't bother with
// lazy-loading & fading in on subsequent mounts.
const imageCache = Object.create({})
const inImageCache = props => {
const convertedProps = convertProps(props)
// Find src
- const src = convertedProps.fluid
- ? convertedProps.fluid.src
- : convertedProps.fixed.src
-
+ const src = getImageSrcKey(convertedProps)
return imageCache[src] || false
}
const activateCacheForImage = props => {
const convertedProps = convertProps(props)
// Find src
- const src = convertedProps.fluid
- ? convertedProps.fluid.src
- : convertedProps.fixed.src
-
+ const src = getImageSrcKey(convertedProps)
imageCache[src] = true
}
@@ -101,6 +115,73 @@ function getIO() {
return io
}
+function generateImageSources(imageVariants) {
+ return imageVariants.map(({ src, srcSet, srcSetWebp, media, sizes }) => (
+
+ {srcSetWebp && (
+
+ )}
+
+
+ ))
+}
+
+// Return an array ordered by elements having a media prop, does not use
+// native sort, as a stable sort is not guaranteed by all browsers/versions
+function groupByMedia(imageVariants) {
+ const withMedia = []
+ const without = []
+ imageVariants.forEach(variant =>
+ (variant.media ? withMedia : without).push(variant)
+ )
+
+ if (without.length > 1 && process.env.NODE_ENV !== `production`) {
+ console.warn(
+ `We've found ${
+ without.length
+ } sources without a media property. They might be ignored by the browser, see: https://www.gatsbyjs.org/packages/gatsby-image/#art-directing-multiple-images`
+ )
+ }
+
+ return [...withMedia, ...without]
+}
+
+function generateTracedSVGSources(imageVariants) {
+ return imageVariants.map(({ src, media, tracedSVG }) => (
+
+ ))
+}
+
+function generateBase64Sources(imageVariants) {
+ return imageVariants.map(({ src, media, base64 }) => (
+
+ ))
+}
+
+function generateNoscriptSource({ srcSet, srcSetWebp, media, sizes }, isWebp) {
+ const src = isWebp ? srcSetWebp : srcSet
+ const mediaAttr = media ? `media="${media}" ` : ``
+ const typeAttr = isWebp ? `type='image/webp' ` : ``
+ const sizesAttr = sizes ? `sizes="${sizes}" ` : ``
+
+ return ` `
+}
+
+function generateNoscriptSources(imageVariants) {
+ return imageVariants
+ .map(
+ variant =>
+ (variant.srcSetWebp ? generateNoscriptSource(variant, true) : ``) +
+ generateNoscriptSource(variant)
+ )
+ .join(``)
+}
+
const listenToIntersections = (el, cb) => {
const observer = getIO()
@@ -120,9 +201,6 @@ const noscriptImg = props => {
// HTML validation issues caused by empty values like width="" and height=""
const src = props.src ? `src="${props.src}" ` : `src="" ` // required attribute
const sizes = props.sizes ? `sizes="${props.sizes}" ` : ``
- const srcSetWebp = props.srcSetWebp
- ? ` `
- : ``
const srcSet = props.srcSet ? `srcset="${props.srcSet}" ` : ``
const title = props.title ? `title="${props.title}" ` : ``
const alt = props.alt ? `alt="${props.alt}" ` : `alt="" ` // required attribute
@@ -133,7 +211,25 @@ const noscriptImg = props => {
: ``
const loading = props.loading ? `loading="${props.loading}" ` : ``
- return `${srcSetWebp} `
+ let sources = generateNoscriptSources(props.imageVariants)
+
+ return `${sources} `
+}
+
+// Earlier versions of gatsby-image during the 2.x cycle did not wrap
+// the `Img` component in a `picture` element. This maintains compatibility
+// until a breaking change can be introduced in the next major release
+const Placeholder = ({ src, imageVariants, generateSources, spreadProps }) => {
+ const baseImage =
+
+ return imageVariants.length > 1 ? (
+
+ {generateSources(imageVariants)}
+ {baseImage}
+
+ ) : (
+ baseImage
+ )
}
const Img = React.forwardRef((props, ref) => {
@@ -314,7 +410,8 @@ class Image extends React.Component {
}
if (fluid) {
- const image = fluid
+ const imageVariants = fluid
+ const image = imageVariants[0]
return (
+
)}
{/* Show the traced SVG image. */}
{image.tracedSVG && (
-
+
)}
{/* Once the image is visible (or the browser doesn't support IntersectionObserver), start downloading the image */}
{this.state.isVisible && (
- {image.srcSetWebp && (
-
- )}
-
+ {generateImageSources(imageVariants)}
@@ -408,7 +509,9 @@ class Image extends React.Component {
}
if (fixed) {
- const image = fixed
+ const imageVariants = fixed
+ const image = imageVariants[0]
+
const divStyle = {
position: `relative`,
overflow: `hidden`,
@@ -445,25 +548,28 @@ class Image extends React.Component {
{/* Show the blurry base64 image. */}
{image.base64 && (
-
+
)}
{/* Show the traced SVG image. */}
{image.tracedSVG && (
-
+
)}
{/* Once the image is visible, start downloading the image */}
{this.state.isVisible && (
- {image.srcSetWebp && (
-
- )}
-
+ {generateImageSources(imageVariants)}
@@ -523,6 +630,7 @@ const fixedObject = PropTypes.shape({
tracedSVG: PropTypes.string,
srcWebp: PropTypes.string,
srcSetWebp: PropTypes.string,
+ media: PropTypes.string,
})
const fluidObject = PropTypes.shape({
@@ -534,13 +642,14 @@ const fluidObject = PropTypes.shape({
tracedSVG: PropTypes.string,
srcWebp: PropTypes.string,
srcSetWebp: PropTypes.string,
+ media: PropTypes.string,
})
Image.propTypes = {
resolutions: fixedObject,
sizes: fluidObject,
- fixed: fixedObject,
- fluid: fluidObject,
+ fixed: PropTypes.oneOfType([fixedObject, PropTypes.arrayOf(fixedObject)]),
+ fluid: PropTypes.oneOfType([fluidObject, PropTypes.arrayOf(fluidObject)]),
fadeIn: PropTypes.bool,
durationFadeIn: PropTypes.number,
title: PropTypes.string,
diff --git a/packages/gatsby-plugin-sharp/src/__tests__/__snapshots__/index.js.snap b/packages/gatsby-plugin-sharp/src/__tests__/__snapshots__/index.js.snap
index 1a30c242e9255..40b27b2a80c7a 100644
--- a/packages/gatsby-plugin-sharp/src/__tests__/__snapshots__/index.js.snap
+++ b/packages/gatsby-plugin-sharp/src/__tests__/__snapshots__/index.js.snap
@@ -110,7 +110,7 @@ Object {
"originalName": undefined,
"src": "/static/1234/c0399/test.png",
"srcSet": "/static/1234/c0399/test.png 1x",
- "tracedSVG": "data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='100' height='100'%3e%3cpath d='M41 24c-18 7-24 29-11 43 15 17 44 8 46-15 1-19-17-34-35-28' fill='red' fill-rule='evenodd'/%3e%3c/svg%3e",
+ "tracedSVG": "data:image/svg+xml,%3csvg%20xmlns='http://www.w3.org/2000/svg'%20width='100'%20height='100'%3e%3cpath%20d='M41%2024c-18%207-24%2029-11%2043%2015%2017%2044%208%2046-15%201-19-17-34-35-28'%20fill='red'%20fill-rule='evenodd'/%3e%3c/svg%3e",
"width": 100,
}
`;
@@ -130,6 +130,6 @@ Object {
/static/1234/bc08f/test.png 50w,
/static/1234/c0399/test.png 100w",
"srcSetType": "image/png",
- "tracedSVG": "data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='100' height='100'%3e%3cpath d='M41 24c-18 7-24 29-11 43 15 17 44 8 46-15 1-19-17-34-35-28' fill='red' fill-rule='evenodd'/%3e%3c/svg%3e",
+ "tracedSVG": "data:image/svg+xml,%3csvg%20xmlns='http://www.w3.org/2000/svg'%20width='100'%20height='100'%3e%3cpath%20d='M41%2024c-18%207-24%2029-11%2043%2015%2017%2044%208%2046-15%201-19-17-34-35-28'%20fill='red'%20fill-rule='evenodd'/%3e%3c/svg%3e",
}
`;
diff --git a/packages/gatsby-plugin-sharp/src/trace-svg.js b/packages/gatsby-plugin-sharp/src/trace-svg.js
index 663465747bfc6..541a1fdaa1666 100644
--- a/packages/gatsby-plugin-sharp/src/trace-svg.js
+++ b/packages/gatsby-plugin-sharp/src/trace-svg.js
@@ -118,9 +118,13 @@ exports.notMemoizedtraceSVG = async ({ file, args, fileArgs, reporter }) => {
const optionsSVG = _.defaults(args, defaultArgs)
+ // `srcset` attribute rejects URIs with literal spaces
+ const encodeSpaces = str => str.replace(/ /gi, `%20`)
+
return trace(tmpFilePath, optionsSVG)
.then(optimize)
.then(svgToMiniDataURI)
+ .then(encodeSpaces)
} catch (e) {
throw e
}
diff --git a/www/static/Gatsby-Logo.svg b/www/static/Gatsby-Logo.svg
index 93194642f49dc..37aa217394330 100644
--- a/www/static/Gatsby-Logo.svg
+++ b/www/static/Gatsby-Logo.svg
@@ -1,5 +1,7 @@
- Gatsby
+
+ Gatsby
+
diff --git a/www/static/Gatsby-Monogram.svg b/www/static/Gatsby-Monogram.svg
index d6975a5e98c09..5578b06cb2490 100644
--- a/www/static/Gatsby-Monogram.svg
+++ b/www/static/Gatsby-Monogram.svg
@@ -1,5 +1,7 @@
- Gatsby
+
+ Gatsby
+