Skip to content

Commit 8646aa4

Browse files
polarathenewardpeet
authored andcommitted
feat(gatsby-image): don't fadein image when already loaded "browser-cache" (#12468)
Placeholder transition CSS continues to work as normal, only when the image exists in the browser cache are the transition styles removed. This is mainly to address/reduce the undesirable transition for transparent images as #12254 describes.
1 parent e587e57 commit 8646aa4

File tree

3 files changed

+43
-29
lines changed

3 files changed

+43
-29
lines changed

packages/gatsby-image/src/__tests__/__snapshots__/index.js.snap

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,20 @@
11
// Jest Snapshot v1, https://goo.gl/fbAQLP
22

3-
exports[`<Img /> should render fixed size images 1`] = `
3+
exports[`<Image /> should render fixed size images 1`] = `
44
<div>
55
<div
66
class="fixedImage gatsby-image-wrapper"
77
style="position: relative; overflow: hidden; display: inline; width: 100px; height: 100px;"
88
>
99
<div
10-
style="background-color: lightgray; width: 100px; opacity: 1; transition-delay: 0.25s; height: 100px;"
10+
style="background-color: lightgray; width: 100px; opacity: 1; height: 100px; transition-delay: 0.5s;"
1111
title="Title for the image"
1212
/>
1313
<img
1414
alt=""
1515
class="placeholder"
1616
src="string_of_base64"
17-
style="position: absolute; top: 0px; left: 0px; width: 100%; height: 100%; object-fit: cover; object-position: center; opacity: 1; transition: opacity 0.5s; transition-delay: 0.25s; color: red;"
17+
style="position: absolute; top: 0px; left: 0px; width: 100%; height: 100%; object-fit: cover; object-position: center; opacity: 1; transition-delay: 0.5s; color: red;"
1818
title="Title for the image"
1919
/>
2020
<picture>
@@ -35,13 +35,13 @@ exports[`<Img /> should render fixed size images 1`] = `
3535
/>
3636
</picture>
3737
<noscript>
38-
&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;transition:opacity 0.5s;transition-delay:0.5s;opacity:1;width:100%;height:100%;object-fit:cover;object-position:center"/&gt;&lt;/picture&gt;
38+
&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;
3939
</noscript>
4040
</div>
4141
</div>
4242
`;
4343

44-
exports[`<Img /> should render fluid images 1`] = `
44+
exports[`<Image /> should render fluid images 1`] = `
4545
<div>
4646
<div
4747
class="fixedImage gatsby-image-wrapper"
@@ -51,14 +51,14 @@ exports[`<Img /> should render fluid images 1`] = `
5151
style="width: 100%; padding-bottom: 66.66666666666667%;"
5252
/>
5353
<div
54-
style="background-color: lightgray; position: absolute; top: 0px; bottom: 0px; opacity: 1; transition-delay: 0.25s; right: 0px; left: 0px;"
54+
style="background-color: lightgray; position: absolute; top: 0px; bottom: 0px; opacity: 1; right: 0px; left: 0px; transition-delay: 0.5s;"
5555
title="Title for the image"
5656
/>
5757
<img
5858
alt=""
5959
class="placeholder"
6060
src="string_of_base64"
61-
style="position: absolute; top: 0px; left: 0px; width: 100%; height: 100%; object-fit: cover; object-position: center; opacity: 1; transition: opacity 0.5s; transition-delay: 0.25s; color: red;"
61+
style="position: absolute; top: 0px; left: 0px; width: 100%; height: 100%; object-fit: cover; object-position: center; opacity: 1; transition-delay: 0.5s; color: red;"
6262
title="Title for the image"
6363
/>
6464
<picture>
@@ -79,7 +79,7 @@ exports[`<Img /> should render fluid images 1`] = `
7979
/>
8080
</picture>
8181
<noscript>
82-
&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;transition:opacity 0.5s;transition-delay:0.5s;opacity:1;width:100%;height:100%;object-fit:cover;object-position:center"/&gt;&lt;/picture&gt;
82+
&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;
8383
</noscript>
8484
</div>
8585
</div>

packages/gatsby-image/src/__tests__/index.js

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import "@babel/polyfill"
22
import React from "react"
33
import { render, cleanup, fireEvent } from "react-testing-library"
4-
import Img from "../"
4+
import Image from "../"
55

66
afterAll(cleanup)
77

@@ -25,7 +25,7 @@ const fluidShapeMock = {
2525

2626
const setup = (fluid = false, onLoad = () => {}, onError = () => {}) => {
2727
const { container } = render(
28-
<Img
28+
<Image
2929
backgroundColor
3030
className={`fixedImage`}
3131
style={{ display: `inline` }}
@@ -45,7 +45,7 @@ const setup = (fluid = false, onLoad = () => {}, onError = () => {}) => {
4545
return container
4646
}
4747

48-
describe(`<Img />`, () => {
48+
describe(`<Image />`, () => {
4949
it(`should render fixed size images`, () => {
5050
const component = setup()
5151
expect(component).toMatchSnapshot()
@@ -73,7 +73,7 @@ describe(`<Img />`, () => {
7373
)
7474
// No Intersection Observer in JSDOM, so placeholder img will be visible (opacity 1) by default
7575
expect(placeholderImageTag.getAttribute(`style`)).toEqual(
76-
`position: absolute; top: 0px; left: 0px; width: 100%; height: 100%; object-fit: cover; object-position: center; opacity: 1; transition: opacity 0.5s; transition-delay: 0.25s; color: red;`
76+
`position: absolute; top: 0px; left: 0px; width: 100%; height: 100%; object-fit: cover; object-position: center; opacity: 1; transition-delay: 0.5s; color: red;`
7777
)
7878
expect(placeholderImageTag.getAttribute(`class`)).toEqual(`placeholder`)
7979
})

packages/gatsby-image/src/index.js

Lines changed: 31 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -96,12 +96,11 @@ const noscriptImg = props => {
9696
const alt = props.alt ? `alt="${props.alt}" ` : `alt="" ` // required attribute
9797
const width = props.width ? `width="${props.width}" ` : ``
9898
const height = props.height ? `height="${props.height}" ` : ``
99-
const opacity = props.opacity ? props.opacity : `1`
100-
const transitionDelay = props.transitionDelay ? props.transitionDelay : `0.5s`
10199
const crossOrigin = props.crossOrigin
102100
? `crossorigin="${props.crossOrigin}" `
103101
: ``
104-
return `<picture>${srcSetWebp}<img ${width}${height}${sizes}${srcSet}${src}${alt}${title}${crossOrigin}style="position:absolute;top:0;left:0;transition:opacity 0.5s;transition-delay:${transitionDelay};opacity:${opacity};width:100%;height:100%;object-fit:cover;object-position:center"/></picture>`
102+
103+
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>`
105104
}
106105

107106
const Img = React.forwardRef((props, ref) => {
@@ -143,6 +142,7 @@ class Image extends React.Component {
143142
// default settings for browser without Intersection Observer available
144143
let isVisible = true
145144
let imgLoaded = false
145+
let imgCached = false
146146
let IOSupported = false
147147
let fadeIn = props.fadeIn
148148

@@ -176,6 +176,7 @@ class Image extends React.Component {
176176
this.state = {
177177
isVisible,
178178
imgLoaded,
179+
imgCached,
179180
IOSupported,
180181
fadeIn,
181182
hasNoScript,
@@ -216,7 +217,16 @@ class Image extends React.Component {
216217
this.props.onStartLoad({ wasCached: imageInCache })
217218
}
218219

219-
this.setState({ isVisible: true, imgLoaded: imageInCache })
220+
// imgCached and imgLoaded must update after isVisible,
221+
// Once isVisible is true, imageRef becomes accessible, which imgCached needs access to.
222+
// imgLoaded and imgCached are in a 2nd setState call to be changed together,
223+
// avoiding initiating unnecessary animation frames from style changes.
224+
this.setState({ isVisible: true }, () =>
225+
this.setState({
226+
imgLoaded: imageInCache,
227+
imgCached: this.imageRef.current.currentSrc.length > 0,
228+
})
229+
)
220230
})
221231
}
222232
}
@@ -250,24 +260,30 @@ class Image extends React.Component {
250260
itemProp,
251261
} = convertProps(this.props)
252262

263+
const shouldReveal = this.state.imgLoaded || this.state.fadeIn === false
264+
const shouldFadeIn = this.state.fadeIn === true && !this.state.imgCached
265+
const durationFadeIn = `0.5s`
266+
267+
const imageStyle = {
268+
opacity: shouldReveal ? 1 : 0,
269+
transition: shouldFadeIn ? `opacity ${durationFadeIn}` : `none`,
270+
...imgStyle,
271+
}
272+
253273
const bgColor =
254274
typeof backgroundColor === `boolean` ? `lightgray` : backgroundColor
255275

256-
const initialDelay = `0.25s`
276+
const delayHideStyle = {
277+
transitionDelay: durationFadeIn,
278+
}
279+
257280
const imagePlaceholderStyle = {
258281
opacity: this.state.imgLoaded ? 0 : 1,
259-
transition: `opacity 0.5s`,
260-
transitionDelay: this.state.imgLoaded ? `0.5s` : initialDelay,
282+
...(shouldFadeIn && delayHideStyle),
261283
...imgStyle,
262284
...placeholderStyle,
263285
}
264286

265-
const imageStyle = {
266-
opacity: this.state.imgLoaded || this.state.fadeIn === false ? 1 : 0,
267-
transition: this.state.fadeIn === true ? `opacity 0.5s` : `none`,
268-
...imgStyle,
269-
}
270-
271287
const placeholderImageProps = {
272288
title,
273289
alt: !this.state.isVisible ? alt : ``,
@@ -307,9 +323,9 @@ class Image extends React.Component {
307323
top: 0,
308324
bottom: 0,
309325
opacity: !this.state.imgLoaded ? 1 : 0,
310-
transitionDelay: initialDelay,
311326
right: 0,
312327
left: 0,
328+
...(shouldFadeIn && delayHideStyle),
313329
}}
314330
/>
315331
)}
@@ -393,8 +409,8 @@ class Image extends React.Component {
393409
backgroundColor: bgColor,
394410
width: image.width,
395411
opacity: !this.state.imgLoaded ? 1 : 0,
396-
transitionDelay: initialDelay,
397412
height: image.height,
413+
...(shouldFadeIn && delayHideStyle),
398414
}}
399415
/>
400416
)}
@@ -445,8 +461,6 @@ class Image extends React.Component {
445461
__html: noscriptImg({
446462
alt,
447463
title,
448-
width: image.width,
449-
height: image.height,
450464
...image,
451465
}),
452466
}}

0 commit comments

Comments
 (0)