diff --git a/packages/gatsby-image/README.md b/packages/gatsby-image/README.md
index 5078188cb448b..d34a5d726ceb9 100644
--- a/packages/gatsby-image/README.md
+++ b/packages/gatsby-image/README.md
@@ -352,9 +352,10 @@ You will need to add it in your graphql query as is shown in the following snipp
| `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`. |
-| `critical` | `bool` | Opt-out of lazy-loading behavior. Defaults to `false`. |
| `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. |
## Image processing arguments
diff --git a/packages/gatsby-image/index.d.ts b/packages/gatsby-image/index.d.ts
index 910ba9ef8059c..bbc4f2fe8d9d2 100644
--- a/packages/gatsby-image/index.d.ts
+++ b/packages/gatsby-image/index.d.ts
@@ -42,6 +42,7 @@ interface GatsbyImageProps {
onError?: (event: any) => void
Tag?: string
itemProp?: string
+ loading?: `auto` | `lazy` | `eager`
}
export default class GatsbyImage extends React.Component<
diff --git a/packages/gatsby-image/src/__tests__/__snapshots__/index.js.snap b/packages/gatsby-image/src/__tests__/__snapshots__/index.js.snap
index 892ff4ffca341..53c1acf582abc 100644
--- a/packages/gatsby-image/src/__tests__/__snapshots__/index.js.snap
+++ b/packages/gatsby-image/src/__tests__/__snapshots__/index.js.snap
@@ -35,7 +35,7 @@ exports[` should have a transition-delay of 1sec 1`] = `
/>
@@ -76,7 +76,7 @@ exports[` should render fixed size images 1`] = `
/>
@@ -120,7 +120,7 @@ exports[` should render fluid images 1`] = `
/>
diff --git a/packages/gatsby-image/src/index.js b/packages/gatsby-image/src/index.js
index 84ae8f40aee85..feba030e367e0 100644
--- a/packages/gatsby-image/src/index.js
+++ b/packages/gatsby-image/src/index.js
@@ -100,11 +100,34 @@ const noscriptImg = props => {
? `crossorigin="${props.crossOrigin}" `
: ``
- return ``
+ // Since we're in the noscript block for this image (which is rendered during SSR or when js is disabled),
+ // we have no way to "detect" if native lazy loading is supported by the user's browser
+ // Since this attribute is a progressive enhancement, it won't break a browser with no support
+ // Therefore setting it by default is a good idea.
+
+ const loading = props.loading ? `loading="${props.loading}" ` : ``
+
+ return ``
}
const Img = React.forwardRef((props, ref) => {
- const { sizes, srcSet, src, style, onLoad, onError, ...otherProps } = props
+ const {
+ sizes,
+ srcSet,
+ src,
+ style,
+ onLoad,
+ onError,
+ nativeLazyLoadSupported,
+ loading,
+ ...otherProps
+ } = props
+
+ let loadingAttribute = {}
+
+ if (nativeLazyLoadSupported) {
+ loadingAttribute.loading = loading
+ }
return (
{
onLoad={onLoad}
onError={onError}
ref={ref}
+ {...loadingAttribute}
style={{
position: `absolute`,
top: 0,
@@ -145,6 +169,7 @@ class Image extends React.Component {
let imgCached = false
let IOSupported = false
let fadeIn = props.fadeIn
+ let nativeLazyLoadSupported = false
// If this image has already been loaded before then we can assume it's
// already in the browser cache so it's cheap to just show directly.
@@ -160,6 +185,17 @@ class Image extends React.Component {
IOSupported = true
}
+ // Chrome Canary 75 added native lazy loading support!
+ // https://addyosmani.com/blog/lazy-loading/
+ if (
+ typeof HTMLImageElement !== `undefined` &&
+ `loading` in HTMLImageElement.prototype
+ ) {
+ // Setting isVisible to true to short circuit our IO code and let the browser do its magic
+ isVisible = true
+ nativeLazyLoadSupported = true
+ }
+
// Never render image during SSR
if (typeof window === `undefined`) {
isVisible = false
@@ -181,6 +217,7 @@ class Image extends React.Component {
fadeIn,
hasNoScript,
seenBefore,
+ nativeLazyLoadSupported,
}
this.imageRef = React.createRef()
@@ -207,6 +244,10 @@ class Image extends React.Component {
}
handleRef(ref) {
+ if (this.state.nativeLazyLoadSupported) {
+ // Bail because the browser natively supports lazy loading
+ return
+ }
if (this.state.IOSupported && ref) {
this.cleanUpListeners = listenToIntersections(ref, () => {
const imageInCache = inImageCache(this.props)
@@ -259,8 +300,30 @@ class Image extends React.Component {
durationFadeIn,
Tag,
itemProp,
+ critical,
} = convertProps(this.props)
+ let { loading } = convertProps(this.props)
+
+ if (
+ typeof critical === `boolean` &&
+ process.env.NODE_ENV !== `production`
+ ) {
+ console.log(
+ `
+ The "critical" prop is now deprecated and will be removed in the next major version
+ of "gatsby-image"
+
+ Please use the native "loading" attribute instead of "critical"
+ `
+ )
+ // We want to continue supporting critical and in case it is passed in
+ // we map its value to loading
+ loading = critical ? `eager` : `lazy`
+ }
+
+ const { nativeLazyLoadSupported } = this.state
+
const shouldReveal = this.state.imgLoaded || this.state.fadeIn === false
const shouldFadeIn = this.state.fadeIn === true && !this.state.imgCached
@@ -363,6 +426,8 @@ class Image extends React.Component {
onLoad={this.handleImageLoaded}
onError={this.props.onError}
itemProp={itemProp}
+ nativeLazyLoadSupported={nativeLazyLoadSupported}
+ loading={loading}
/>
)}
@@ -371,7 +436,12 @@ class Image extends React.Component {
{this.state.hasNoScript && (
)}
@@ -450,6 +520,8 @@ class Image extends React.Component {
onLoad={this.handleImageLoaded}
onError={this.props.onError}
itemProp={itemProp}
+ nativeLazyLoadSupported={nativeLazyLoadSupported}
+ loading={loading}
/>
)}
@@ -461,6 +533,7 @@ class Image extends React.Component {
__html: noscriptImg({
alt,
title,
+ loading,
...image,
}),
}}
@@ -475,11 +548,13 @@ class Image extends React.Component {
}
Image.defaultProps = {
- critical: false,
fadeIn: true,
durationFadeIn: 500,
alt: ``,
Tag: `div`,
+ // We set it to `lazy` by default because it's best to default to a performant
+ // setting and let the user "opt out" to `eager`
+ loading: `lazy`,
}
const fixedObject = PropTypes.shape({
@@ -526,6 +601,7 @@ Image.propTypes = {
onStartLoad: PropTypes.func,
Tag: PropTypes.string,
itemProp: PropTypes.string,
+ loading: PropTypes.oneOf([`auto`, `lazy`, `eager`]),
}
export default Image