Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Opt-out of lazy-load: Add 'critical' flag to Img component #7083

Merged
merged 5 commits into from
Sep 11, 2018
Merged

Opt-out of lazy-load: Add 'critical' flag to Img component #7083

merged 5 commits into from
Sep 11, 2018

Conversation

tbrannam
Copy link
Contributor

@tbrannam tbrannam commented Aug 6, 2018

Address #4297

  • Add critical attribute to opt-out of lazy-loading behavior
  • Picture tag to allow browser to select best matching file format (webp detection)
  • if fadeIn is true (default) - then the loaded image still will not appear until after the component is mounted
	    <Img fixed={images.file1.childImageSharp.fixed} />
	    <Img fixed={images.file1.childImageSharp.fixed} />
	    <Img fixed={images.file2.childImageSharp.fixed} critical fadeIn={false}/>
	    <Img fixed={images.file2.childImageSharp.fixed} critical/>

@@ -69,24 +69,6 @@ const listenToIntersections = (el, cb) => {
listeners.push([el, cb])
}

let isWebpSupportedCache = null
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Using picture tag makes this obsolete

@@ -141,7 +123,7 @@ class Image extends React.Component {

// 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.
const seenBefore = inImageCache(props)
const seenBefore = !props.critical && inImageCache(props)
Copy link
Contributor Author

@tbrannam tbrannam Aug 6, 2018

Choose a reason for hiding this comment

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

This may be a bit controversial - if a critical image is used in multiple places on a page, but has different fadeIn values - one of the critical image may not appear when the component is mounted.

@@ -168,10 +154,16 @@ class Image extends React.Component {
this.handleRef = this.handleRef.bind(this)
}

componentDidMount() {
if (this.props.critical) {
this.setState({imgLoaded: true});
Copy link
Contributor Author

Choose a reason for hiding this comment

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

onLoad can not be relied on to change the state for images that where loaded from the DOM

Copy link
Contributor

Choose a reason for hiding this comment

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

What did you change here? If you push individual commits, that's helpful to people reviewing your code.

BTW, did you see #4297 (comment)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Here I fixed up the lint error about spaces around braces.

@KyleAMathews
Copy link
Contributor

KyleAMathews commented Aug 6, 2018

Deploy preview for using-postcss-sass failed.

Built with commit fd48bb3

https://app.netlify.com/sites/using-postcss-sass/deploys/5b68dfb4e39e7c630cd35077

handleRef(ref) {
if (this.state.IOSupported && ref) {
listenToIntersections(ref, () => {
this.setState({ isVisible: true, imgLoaded: false })
this.setState({ isVisible: true })
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Not sure why this was set to false here. Reseting the state to false causes issues with critical images, and I could not find where non-critical images required this state change.

Copy link
Contributor

Choose a reason for hiding this comment

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

hmmm yeah, don't remember either 😅

alt={alt}
title={title}
src={image.src}
srcSet={image.srcSet}
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Is there really a need to include srcSet, and sizes in the fallback? It appears that browsers that support these on img also implement picture - so it is added weight, without a purpose.

Copy link
Contributor

Choose a reason for hiding this comment

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

👍 sounds right to me.

@gatsbybot
Copy link
Collaborator

gatsbybot commented Aug 6, 2018

Deploy preview for using-drupal ready!

Built with commit fd48bb3

https://deploy-preview-7083--using-drupal.netlify.com

@gatsbybot
Copy link
Collaborator

gatsbybot commented Aug 6, 2018

Deploy preview for gatsbygram ready!

Built with commit fd48bb3

https://deploy-preview-7083--gatsbygram.netlify.com

@KyleAMathews
Copy link
Contributor

KyleAMathews commented Aug 6, 2018

Deploy preview for gatsbyjs failed.

Built with commit fd48bb3

https://app.netlify.com/sites/gatsbyjs/deploys/5b68dfb3e39e7c630cd3506b

}),
}}
/>
{!this.props.critical && (
Copy link
Contributor Author

Choose a reason for hiding this comment

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

May need to walk this back - if fadeIn is true - and there is no scripting enabled - the loaded image will not appear

Copy link
Contributor

Choose a reason for hiding this comment

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

Is this still true, or was this fixed? I can't see which commits this came from 🙃

Copy link
Contributor Author

Choose a reason for hiding this comment

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

this has been revised like so:

{this.state.hasNoScript && (

srcSet={image.srcSet}
sizes={image.sizes}
style={imageStyle}
onLoad={() => {
Copy link
Contributor

Choose a reason for hiding this comment

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

should onLoad event handler be used in <source> as well? or maybe it would work using it in <picture>?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Img is a required tag in picture. It would seem to me that it would be redundant?

@tbrannam
Copy link
Contributor Author

I have adjusted my local branch to support fadeIn for when just is disabled, but is has the side effect of having two instances of the picture loaded (the first has opacity:0 and the second is presented via the noscript block. I don't see a way around this. I am adjusting the noscript image to also use picture so that the same asset will be used and the asset will only be downloaded once.

All that should be left is verifying that onload on Gatsby-Image is called if the image is downloaded before the script bundle is loaded.

@KyleAMathews
Copy link
Contributor

Deploy preview for using-contentful failed.

Built with commit 2c873b3

https://app.netlify.com/sites/using-contentful/deploys/5b7c68c5e39e7c6abe678f98

Todd Brannam added 3 commits August 21, 2018 15:41
Implementation notes:
   * use Picture tag to allow browser to select best matching file format (webp detection)
   * if fadeIn is true (default) - then the loaded image still will not appear until after the component is mounted
   * the inImageCache should ignore 'critical' assets or images that have different fadeIn behaviors will not trigger an appropriate change in state (won't show loaded image) see example:
```
	    <Img fixed={images.file1.childImageSharp.fixed} />
	    <Img fixed={images.file1.childImageSharp.fixed} />
	    <Img fixed={images.file2.childImageSharp.fixed} critical fadeIn={false}/>
	    <Img fixed={images.file2.childImageSharp.fixed} critical/>
```
Mimic behavior of lazy-images bypassing fadeIn when `seenBefore==true`
Trigger load-complete script on componentDidMount if the image is loaded prior to the JS load complete
Modify imagePlaceholder transitionDelay to reduce flicker present on critical images at tail end of fade (observed on Chrome 67.0)
`noscript` `img` transitioned to use `picture` tag so that only one resource is downloaded in the event that JS is disabled when `critical==true + fadeIn==false`
Added webp src to mock data
Updated Snapshot with <picture>
@tbrannam
Copy link
Contributor Author

tbrannam commented Aug 22, 2018

@KyleAMathews not sure about the ci failures. Looks like most prs are failing? Otherwise I think this PR is feature complete.

this.state = {
isVisible,
imgLoaded,
IOSupported,
fadeIn,
hasNoScript,
seenBefore,
Copy link
Contributor Author

Choose a reason for hiding this comment

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

stored to evaluate on mount to emulate the lazy behavior

}

this.imageRef = React.createRef()
Copy link
Contributor Author

Choose a reason for hiding this comment

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

need a reference to the HTMLImageElement to evaluate the completed state - to detect if DOM finished loading image before the JS started

@@ -194,13 +214,14 @@ class Image extends React.Component {

const imagePlaceholderStyle = {
opacity: this.state.imgLoaded ? 0 : 1,
transitionDelay: `0.25s`,
transitionDelay: this.state.imgLoaded ? `0.5s` : `0.25s`,
Copy link
Contributor Author

Choose a reason for hiding this comment

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

when transitioning a critical image to opaque:1 - Chrome 67/68 flashes/flickers - not certain if there's a reason to synchronize the placeholder while fading in the real image.

...imgStyle,
...placeholderStyle,
}

const imageStyle = {
opacity: this.state.imgLoaded || this.props.fadeIn === false ? 1 : 0,
opacity: this.state.imgLoaded || this.state.fadeIn === false ? 1 : 0,
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fade in now copied from props at construction time. Can be mutated if a critical image has been seen before.

@tbrannam
Copy link
Contributor Author

resolves #4297

@DSchau
Copy link
Contributor

DSchau commented Sep 7, 2018

@tbrannam I plan to check this out this afternoon, so stay tuned :)

First thing I noticed is we're using the picture element now--which is cool--but that's not supported in IE 11 (per usual...). I think this will gracefully fallback to the image element (i.e. the picture tag will just be ignored) but it could be worth mentioning this in the gatsby-image documentation and then linking to appropriate polyfills. What do you think?

@tbrannam
Copy link
Contributor Author

tbrannam commented Sep 7, 2018 via email

@DSchau DSchau self-assigned this Sep 7, 2018
Copy link
Contributor

@DSchau DSchau left a comment

Choose a reason for hiding this comment

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

I think this looks great. We can/should document the usage of picture in gatsby-image/README.md, so that people are at least aware of the change.

I validated the following:

  1. No regressions to existing functionality with examples/image-processing project
  2. critical attribute set to true and fadeIn of false seem to work like I'd expect

@pieh want to chime in here, or are we good to go on merging this?

Update transition style for no fade from `<empty>` to `none`
Added text mentioning the use of `<picture>`
@@ -277,3 +277,6 @@ prop. e.g. `<Img fluid={fluid} />`
- Images marked as `critical` will start loading immediately as the DOM is
parsed, but unless `fadeIn` is set to `false`, the transition from placeholder
to final image will not occur until after the component is mounted.
- Gatsby-Image now is backed by newer `<picture>` tag. This newer standard allows for
Copy link
Contributor

Choose a reason for hiding this comment

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

👍 Thanks for this!

@DSchau DSchau merged commit 5ffb130 into gatsbyjs:master Sep 11, 2018
@gatsbot
Copy link

gatsbot bot commented Sep 11, 2018

Holy buckets, @tbrannam — we just merged your PR to Gatsby! 💪💜

Gatsby is built by awesome people like you. Let us say “thanks” in two ways:

  1. We’d like to send you some Gatsby swag. As a token of our appreciation, you can go to the Gatsby Swag Store and log in with your GitHub account to get a coupon code good for one free piece of swag. (Currently we’ve got a couple t-shirts available, plus some socks that are really razzing our berries right now.)
  2. We just invited you to join the Gatsby organization on GitHub. This will add you to our team of maintainers. You’ll receive an email shortly asking you to confirm. By joining the team, you’ll be able to label issues, review pull requests, and merge approved pull requests.

If there’s anything we can do to help, please don’t hesitate to reach out to us: tweet at @gatsbyjs and we’ll come a-runnin’.

Thanks again!

DSchau pushed a commit that referenced this pull request Sep 20, 2018
oorestisime pushed a commit to oorestisime/gatsby that referenced this pull request Sep 28, 2018
@tbrannam tbrannam deleted the topics/tcb-image-critial branch November 4, 2018 15:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants