Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion packages/gatsby-image/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
1 change: 1 addition & 0 deletions packages/gatsby-image/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ exports[`<Image /> should have a transition-delay of 1sec 1`] = `
/>
</picture>
<noscript>
&lt;picture&gt;&lt;source type='image/webp' srcset="some srcSetWebp" /&gt;&lt;img 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"/&gt;&lt;/picture&gt;
&lt;picture&gt;&lt;source type='image/webp' srcset="some srcSetWebp" /&gt;&lt;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"/&gt;&lt;/picture&gt;
</noscript>
</div>
</div>
Expand Down Expand Up @@ -76,7 +76,7 @@ exports[`<Image /> should render fixed size images 1`] = `
/>
</picture>
<noscript>
&lt;picture&gt;&lt;source type='image/webp' srcset="some srcSetWebp" /&gt;&lt;img 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"/&gt;&lt;/picture&gt;
&lt;picture&gt;&lt;source type='image/webp' srcset="some srcSetWebp" /&gt;&lt;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"/&gt;&lt;/picture&gt;
</noscript>
</div>
</div>
Expand Down Expand Up @@ -120,7 +120,7 @@ exports[`<Image /> should render fluid images 1`] = `
/>
</picture>
<noscript>
&lt;picture&gt;&lt;source type='image/webp' srcset="some srcSetWebp" sizes="(max-width: 600px) 100vw, 600px" /&gt;&lt;img 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"/&gt;&lt;/picture&gt;
&lt;picture&gt;&lt;source type='image/webp' srcset="some srcSetWebp" sizes="(max-width: 600px) 100vw, 600px" /&gt;&lt;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"/&gt;&lt;/picture&gt;
</noscript>
</div>
</div>
Expand Down
84 changes: 80 additions & 4 deletions packages/gatsby-image/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -100,11 +100,34 @@ const noscriptImg = props => {
? `crossorigin="${props.crossOrigin}" `
: ``

return `<picture>${srcSetWebp}<img ${width}${height}${sizes}${srcSet}${src}${alt}${title}${crossOrigin}style="position:absolute;top:0;left:0;opacity:1;width:100%;height:100%;object-fit:cover;object-position:center"/></picture>`
// 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}" ` : ``
Comment thread
sidharthachatterjee marked this conversation as resolved.

return `<picture>${srcSetWebp}<img ${loading}${width}${height}${sizes}${srcSet}${src}${alt}${title}${crossOrigin}style="position:absolute;top:0;left:0;opacity:1;width:100%;height:100%;object-fit:cover;object-position:center"/></picture>`
}

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 (
<img
Expand All @@ -115,6 +138,7 @@ const Img = React.forwardRef((props, ref) => {
onLoad={onLoad}
onError={onError}
ref={ref}
{...loadingAttribute}
style={{
position: `absolute`,
top: 0,
Expand Down Expand Up @@ -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.
Expand All @@ -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` &&
Comment thread
sidharthachatterjee marked this conversation as resolved.
`loading` in HTMLImageElement.prototype
) {
// Setting isVisible to true to short circuit our IO code and let the browser do its magic
isVisible = true
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe to make it clear this is now representing two different states it could be changed to isVisibleOrNativeLazyLoadingSupported — verbose is effective :-D

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@KyleAMathews isn't the isVisible state here linked to the image components visibility state for it's picture element? I suggested a refactor here, but perhaps that's the wrong approach.

nativeLazyLoadSupported = true
}

// Never render image during SSR
if (typeof window === `undefined`) {
isVisible = false
Expand All @@ -181,6 +217,7 @@ class Image extends React.Component {
fadeIn,
hasNoScript,
seenBefore,
nativeLazyLoadSupported,
}

this.imageRef = React.createRef()
Expand All @@ -207,6 +244,10 @@ class Image extends React.Component {
}

handleRef(ref) {
if (this.state.nativeLazyLoadSupported) {
// Bail because the browser natively supports lazy loading
return
}
Comment thread
sidharthachatterjee marked this conversation as resolved.
if (this.state.IOSupported && ref) {
this.cleanUpListeners = listenToIntersections(ref, () => {
const imageInCache = inImageCache(this.props)
Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -363,6 +426,8 @@ class Image extends React.Component {
onLoad={this.handleImageLoaded}
onError={this.props.onError}
itemProp={itemProp}
nativeLazyLoadSupported={nativeLazyLoadSupported}
loading={loading}
/>
</picture>
)}
Expand All @@ -371,7 +436,12 @@ class Image extends React.Component {
{this.state.hasNoScript && (
<noscript
dangerouslySetInnerHTML={{
__html: noscriptImg({ alt, title, ...image }),
__html: noscriptImg({
alt,
title,
loading,
...image,
}),
}}
/>
)}
Expand Down Expand Up @@ -450,6 +520,8 @@ class Image extends React.Component {
onLoad={this.handleImageLoaded}
onError={this.props.onError}
itemProp={itemProp}
nativeLazyLoadSupported={nativeLazyLoadSupported}
loading={loading}
/>
</picture>
)}
Expand All @@ -461,6 +533,7 @@ class Image extends React.Component {
__html: noscriptImg({
alt,
title,
loading,
...image,
}),
}}
Expand All @@ -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`,
Comment thread
sidharthachatterjee marked this conversation as resolved.
}

const fixedObject = PropTypes.shape({
Expand Down Expand Up @@ -526,6 +601,7 @@ Image.propTypes = {
onStartLoad: PropTypes.func,
Tag: PropTypes.string,
itemProp: PropTypes.string,
loading: PropTypes.oneOf([`auto`, `lazy`, `eager`]),
}

export default Image