Skip to content

Commit 125fd01

Browse files
kimbaudiwardpeet
authored andcommitted
feat(gatsby-remark-images): Add flag to suppress css background-image to prevent FOUB (#17154)
1 parent 491d662 commit 125fd01

File tree

9 files changed

+164
-76
lines changed

9 files changed

+164
-76
lines changed

packages/gatsby-plugin-sharp/src/__tests__/__snapshots__/index.js.snap

+9-61
Original file line numberDiff line numberDiff line change
@@ -70,67 +70,15 @@ Object {
7070
}
7171
`;
7272

73-
exports[`gatsby-plugin-sharp queueImageResizing should process immediately when asked 1`] = `
74-
[MockFunction] {
75-
"calls": Array [
76-
Array [
77-
Object {
78-
"args": Object {
79-
"base64": true,
80-
"duotone": false,
81-
"grayscale": false,
82-
"jpegProgressive": true,
83-
"maxWidth": 800,
84-
"pathPrefix": "",
85-
"pngCompressionLevel": 9,
86-
"pngCompressionSpeed": 4,
87-
"quality": 50,
88-
"sizeByPixelDensity": false,
89-
"toFormat": "png",
90-
"width": 3,
91-
},
92-
"inputPath": "<PROJECT_ROOT>/packages/gatsby-plugin-sharp/src/__tests__/images/144-density.png",
93-
"outputPath": "<PROJECT_ROOT>/public/static/1234/39ca0/test.png",
94-
},
95-
Object {
96-
"addThirdPartySchema": [Function],
97-
"createJob": [Function],
98-
"createNode": [Function],
99-
"createNodeField": [Function],
100-
"createPage": [Function],
101-
"createPageDependency": [Function],
102-
"createParentChildLink": [Function],
103-
"createRedirect": [Function],
104-
"deleteComponentsDependencies": [Function],
105-
"deleteNode": [Function],
106-
"deleteNodes": [Function],
107-
"deletePage": [Function],
108-
"endJob": [Function],
109-
"replaceComponentQuery": [Function],
110-
"replaceStaticQuery": [Function],
111-
"replaceWebpackConfig": [Function],
112-
"setBabelOptions": [Function],
113-
"setBabelPlugin": [Function],
114-
"setBabelPreset": [Function],
115-
"setJob": [Function],
116-
"setPluginStatus": [Function],
117-
"setWebpackConfig": [Function],
118-
"touchNode": [Function],
119-
},
120-
Object {
121-
"defaultQuality": 50,
122-
"lazyImageGeneration": true,
123-
"stripMetadata": true,
124-
"useMozJpeg": false,
125-
},
126-
],
127-
],
128-
"results": Array [
129-
Object {
130-
"type": "return",
131-
"value": Promise {},
132-
},
133-
],
73+
exports[`gatsby-plugin-sharp stats determines if the image is transparent, based on the presence and use of alpha channel 1`] = `
74+
Object {
75+
"isTransparent": false,
76+
}
77+
`;
78+
79+
exports[`gatsby-plugin-sharp stats determines if the image is transparent, based on the presence and use of alpha channel 2`] = `
80+
Object {
81+
"isTransparent": true,
13482
}
13583
`;
13684

Loading

packages/gatsby-plugin-sharp/src/__tests__/index.js

+16
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ const {
1717
fixed,
1818
queueImageResizing,
1919
getImageSize,
20+
stats,
2021
} = require(`../`)
2122
const { scheduleJob } = require(`../scheduler`)
2223
scheduleJob.mockResolvedValue(Promise.resolve())
@@ -391,6 +392,21 @@ describe(`gatsby-plugin-sharp`, () => {
391392
expect(result).toMatchSnapshot()
392393
})
393394
})
395+
396+
describe(`stats`, () => {
397+
it(`determines if the image is transparent, based on the presence and use of alpha channel`, async () => {
398+
const result = await stats({ file, args })
399+
expect(result).toMatchSnapshot()
400+
expect(result.isTransparent).toEqual(false)
401+
402+
const alphaResult = await stats({
403+
file: getFileObject(path.join(__dirname, `images/alphatest.png`)),
404+
args,
405+
})
406+
expect(alphaResult).toMatchSnapshot()
407+
expect(alphaResult.isTransparent).toEqual(true)
408+
})
409+
})
394410
})
395411

396412
function getFileObject(absolutePath, name = `test`) {

packages/gatsby-plugin-sharp/src/index.js

+19
Original file line numberDiff line numberDiff line change
@@ -261,6 +261,24 @@ async function getTracedSVG({ file, options, cache, reporter }) {
261261
return undefined
262262
}
263263

264+
async function stats({ file, reporter }) {
265+
let imgStats
266+
try {
267+
imgStats = await sharp(file.absolutePath).stats()
268+
} catch (err) {
269+
reportError(
270+
`Failed to get stats for image ${file.absolutePath}`,
271+
err,
272+
reporter
273+
)
274+
return null
275+
}
276+
277+
return {
278+
isTransparent: !imgStats.isOpaque,
279+
}
280+
}
281+
264282
async function fluid({ file, args = {}, reporter, cache }) {
265283
const options = healOptions(getPluginOptions(), args, file.extension)
266284

@@ -572,3 +590,4 @@ exports.resolutions = fixed
572590
exports.fluid = fluid
573591
exports.fixed = fixed
574592
exports.getImageSize = getImageSize
593+
exports.stats = stats

packages/gatsby-remark-images/README.md

+14-13
Large diffs are not rendered by default.

packages/gatsby-remark-images/src/__tests__/__snapshots__/index.js.snap

+58
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,63 @@
11
// Jest Snapshot v1, https://goo.gl/fbAQLP
22

3+
exports[`disableBgImageOnAlpha disables background image on transparent images when disableBgImageOnAlpha === true 1`] = `
4+
"<span
5+
class=\\"gatsby-resp-image-wrapper\\"
6+
style=\\"position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 300px;\\"
7+
>
8+
<a
9+
class=\\"gatsby-resp-image-link\\"
10+
href=\\"not-a-real-dir/images/my-image.jpeg\\"
11+
style=\\"display: block\\"
12+
target=\\"_blank\\"
13+
rel=\\"noopener\\"
14+
>
15+
<span
16+
class=\\"gatsby-resp-image-background-image\\"
17+
style=\\"padding-bottom: 133.33333333333331%; position: relative; bottom: 0; left: 0; display: block;\\"
18+
></span>
19+
<img
20+
class=\\"gatsby-resp-image-image\\"
21+
alt=\\"some alt\\"
22+
title=\\"some title\\"
23+
src=\\"not-a-real-dir/images/my-image.jpeg\\"
24+
srcset=\\"not-a-real-dir/images/my-image.jpeg, not-a-real-dir/images/my-image.jpeg\\"
25+
sizes=\\"(max-width: 650px) 100vw, 650px\\"
26+
loading=\\"lazy\\"
27+
/>
28+
</a>
29+
</span>"
30+
`;
31+
32+
exports[`disableBgImageOnAlpha does not disable background image on transparent images when disableBgImageOnAlpha === false 1`] = `
33+
"<span
34+
class=\\"gatsby-resp-image-wrapper\\"
35+
style=\\"position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 300px;\\"
36+
>
37+
<a
38+
class=\\"gatsby-resp-image-link\\"
39+
href=\\"not-a-real-dir/images/my-image.jpeg\\"
40+
style=\\"display: block\\"
41+
target=\\"_blank\\"
42+
rel=\\"noopener\\"
43+
>
44+
<span
45+
class=\\"gatsby-resp-image-background-image\\"
46+
style=\\"padding-bottom: 133.33333333333331%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw'); background-size: cover; display: block;\\"
47+
></span>
48+
<img
49+
class=\\"gatsby-resp-image-image\\"
50+
alt=\\"some alt\\"
51+
title=\\"some title\\"
52+
src=\\"not-a-real-dir/images/my-image.jpeg\\"
53+
srcset=\\"not-a-real-dir/images/my-image.jpeg, not-a-real-dir/images/my-image.jpeg\\"
54+
sizes=\\"(max-width: 650px) 100vw, 650px\\"
55+
loading=\\"lazy\\"
56+
/>
57+
</a>
58+
</span>"
59+
`;
60+
361
exports[`it handles goofy nesting properly 1`] = `
462
"<span
563
class=\\"gatsby-resp-image-wrapper\\"

packages/gatsby-remark-images/src/__tests__/index.js

+35
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,11 @@ jest.mock(`gatsby-plugin-sharp`, () => {
1616
})
1717
},
1818
traceSVG: mockTraceSVG,
19+
stats() {
20+
return Promise.resolve({
21+
isTransparent: true,
22+
})
23+
},
1924
}
2025
})
2126

@@ -587,3 +592,33 @@ describe(`markdownCaptions`, () => {
587592
expect($(`figcaption`).length).toBe(0)
588593
})
589594
})
595+
596+
describe(`disableBgImageOnAlpha`, () => {
597+
it(`does not disable background image on transparent images when disableBgImageOnAlpha === false`, async () => {
598+
const imagePath = `images/my-image.jpeg`
599+
const content = `![some alt](./${imagePath} "some title")`
600+
601+
const nodes = await plugin(createPluginOptions(content, imagePath), {
602+
disableBgImageOnAlpha: false,
603+
})
604+
expect(nodes.length).toBe(1)
605+
606+
const node = nodes.pop()
607+
expect(node.type).toBe(`html`)
608+
expect(node.value).toMatchSnapshot()
609+
})
610+
611+
it(`disables background image on transparent images when disableBgImageOnAlpha === true`, async () => {
612+
const imagePath = `images/my-image.jpeg`
613+
const content = `![some alt](./${imagePath} "some title")`
614+
615+
const nodes = await plugin(createPluginOptions(content, imagePath), {
616+
disableBgImageOnAlpha: true,
617+
})
618+
expect(nodes.length).toBe(1)
619+
620+
const node = nodes.pop()
621+
expect(node.type).toBe(`html`)
622+
expect(node.value).toMatchSnapshot()
623+
})
624+
})

packages/gatsby-remark-images/src/constants.js

+1
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ exports.DEFAULT_OPTIONS = {
88
withWebp: false,
99
tracedSVG: false,
1010
loading: `lazy`,
11+
disableBgImageOnAlpha: false,
1112
}
1213

1314
exports.imageClass = `gatsby-resp-image-image`

packages/gatsby-remark-images/src/index.js

+12-2
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ const path = require(`path`)
1010
const queryString = require(`query-string`)
1111
const isRelativeUrl = require(`is-relative-url`)
1212
const _ = require(`lodash`)
13-
const { fluid, traceSVG } = require(`gatsby-plugin-sharp`)
13+
const { fluid, stats, traceSVG } = require(`gatsby-plugin-sharp`)
1414
const Promise = require(`bluebird`)
1515
const cheerio = require(`cheerio`)
1616
const slash = require(`slash`)
@@ -282,10 +282,20 @@ module.exports = (
282282
const imageCaption =
283283
options.showCaptions && getImageCaption(node, overWrites)
284284

285+
let removeBgImage = false
286+
if (options.disableBgImageOnAlpha) {
287+
const imageStats = await stats({ file: imageNode, reporter })
288+
if (imageStats && imageStats.isTransparent) removeBgImage = true
289+
}
290+
291+
const bgImage = removeBgImage
292+
? ``
293+
: ` background-image: url('${placeholderImageData}'); background-size: cover;`
294+
285295
let rawHTML = `
286296
<span
287297
class="${imageBackgroundClass}"
288-
style="padding-bottom: ${ratio}; position: relative; bottom: 0; left: 0; background-image: url('${placeholderImageData}'); background-size: cover; display: block;"
298+
style="padding-bottom: ${ratio}; position: relative; bottom: 0; left: 0;${bgImage} display: block;"
289299
></span>
290300
${imageTag}
291301
`.trim()

0 commit comments

Comments
 (0)