Skip to content

Commit 44491ef

Browse files
polarathenewardpeet
authored andcommittedMar 12, 2019
feat(gatsby-image): Placeholder Improvements (#10944)
* feat(gatsby-plugin-sharp): Configurable base64 width Allows for using larger(or smaller) than the default `20px` width, trading size for quality via more/less pixels. Adds the `base64Width` arg to the fixed & fluid nodes. * chore(gatsby-plugin-sharp): Remove magic numbers Provides added clarity for where these numbers are used. Especially for the value of `72` where it's not clear if the value was for image or display density. `defaultBase64Width` improves clarity and keeps it DRY across both usages. * fix(gatsby-image): Support placeholder image with backgroundColor Place the `backgroundColor` div before/behind the placeholder img. * chore(gatsby-image): Share common transitionDelay variable They seem all intended to use the same duration of `0.25s`, `fluid` backgroundColor div actually used `0.35s` for some reason which seemed a possible typo, this corrects that and avoids it in future. * feat(gatsby-plugin-sharp): Allow base64 format to differ, allow WEBP For example, WEBP base64 placeholders with a `backgroundColor` as fallback while using JPG/PNG with WEBP for the `<picture>` element. Adds the `forceBase64Format` config option. Adds support for WEBP base64 images. * chore(gatsby-image): Update test snapshot * feat(gatsby-plugin-sharp): Support base64Width as a config option Allows setting a project wide default, rather than having to request it per image. Updates `defaultBase64Width` to be a function as `pluginOptions` has not initialized when assigned. * feat(gatsby-plugin-sharp): Support forceBase64Format as query arg * Change `density_72PPI` to `defaultImagePPI` Co-Authored-By: polarathene <polarathene@users.noreply.github.com> * Change `density_72PPI` to `defaultImagePPI` 2 Co-Authored-By: polarathene <polarathene@users.noreply.github.com>
1 parent 3bc5f4d commit 44491ef

File tree

4 files changed

+69
-34
lines changed

4 files changed

+69
-34
lines changed
 

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

+8-8
Original file line numberDiff line numberDiff line change
@@ -6,17 +6,17 @@ exports[`<Img /> should render fixed size images 1`] = `
66
class="fixedImage gatsby-image-wrapper"
77
style="position: relative; overflow: hidden; display: inline; width: 100px; height: 100px;"
88
>
9+
<div
10+
style="width: 100px; opacity: 1; height: 100px;"
11+
title="Title for the image"
12+
/>
913
<img
1014
alt=""
1115
class="placeholder"
1216
src="string_of_base64"
1317
style="position: absolute; top: 0px; left: 0px; width: 100%; height: 100%; opacity: 1; color: red;"
1418
title="Title for the image"
1519
/>
16-
<div
17-
style="width: 100px; opacity: 1; height: 100px;"
18-
title="Title for the image"
19-
/>
2020
<picture>
2121
<source
2222
srcset="some srcSetWebp"
@@ -49,17 +49,17 @@ exports[`<Img /> should render fluid images 1`] = `
4949
<div
5050
style="width: 100%; padding-bottom: 66.66666666666667%;"
5151
/>
52+
<div
53+
style="position: absolute; top: 0px; bottom: 0px; opacity: 1; right: 0px; left: 0px;"
54+
title="Title for the image"
55+
/>
5256
<img
5357
alt=""
5458
class="placeholder"
5559
src="string_of_base64"
5660
style="position: absolute; top: 0px; left: 0px; width: 100%; height: 100%; opacity: 1; color: red;"
5761
title="Title for the image"
5862
/>
59-
<div
60-
style="position: absolute; top: 0px; bottom: 0px; opacity: 1; right: 0px; left: 0px;"
61-
title="Title for the image"
62-
/>
6363
<picture>
6464
<source
6565
sizes="(max-width: 600px) 100vw, 600px"

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

+24-23
Original file line numberDiff line numberDiff line change
@@ -235,10 +235,11 @@ class Image extends React.Component {
235235
const bgColor =
236236
typeof backgroundColor === `boolean` ? `lightgray` : backgroundColor
237237

238+
const initialDelay = `0.25s`
238239
const imagePlaceholderStyle = {
239240
opacity: this.state.imgLoaded ? 0 : 1,
240241
transition: `opacity 0.5s`,
241-
transitionDelay: this.state.imgLoaded ? `0.5s` : `0.25s`,
242+
transitionDelay: this.state.imgLoaded ? `0.5s` : initialDelay,
242243
...imgStyle,
243244
...placeholderStyle,
244245
}
@@ -278,16 +279,6 @@ class Image extends React.Component {
278279
}}
279280
/>
280281

281-
{/* Show the blurry base64 image. */}
282-
{image.base64 && (
283-
<Img src={image.base64} {...placeholderImageProps} />
284-
)}
285-
286-
{/* Show the traced SVG image. */}
287-
{image.tracedSVG && (
288-
<Img src={image.tracedSVG} {...placeholderImageProps} />
289-
)}
290-
291282
{/* Show a solid background color. */}
292283
{bgColor && (
293284
<Tag
@@ -298,13 +289,23 @@ class Image extends React.Component {
298289
top: 0,
299290
bottom: 0,
300291
opacity: !this.state.imgLoaded ? 1 : 0,
301-
transitionDelay: `0.35s`,
292+
transitionDelay: initialDelay,
302293
right: 0,
303294
left: 0,
304295
}}
305296
/>
306297
)}
307298

299+
{/* Show the blurry base64 image. */}
300+
{image.base64 && (
301+
<Img src={image.base64} {...placeholderImageProps} />
302+
)}
303+
304+
{/* Show the traced SVG image. */}
305+
{image.tracedSVG && (
306+
<Img src={image.tracedSVG} {...placeholderImageProps} />
307+
)}
308+
308309
{/* Once the image is visible (or the browser doesn't support IntersectionObserver), start downloading the image */}
309310
{this.state.isVisible && (
310311
<picture>
@@ -365,16 +366,6 @@ class Image extends React.Component {
365366
ref={this.handleRef}
366367
key={`fixed-${JSON.stringify(image.srcSet)}`}
367368
>
368-
{/* Show the blurry base64 image. */}
369-
{image.base64 && (
370-
<Img src={image.base64} {...placeholderImageProps} />
371-
)}
372-
373-
{/* Show the traced SVG image. */}
374-
{image.tracedSVG && (
375-
<Img src={image.tracedSVG} {...placeholderImageProps} />
376-
)}
377-
378369
{/* Show a solid background color. */}
379370
{bgColor && (
380371
<Tag
@@ -383,12 +374,22 @@ class Image extends React.Component {
383374
backgroundColor: bgColor,
384375
width: image.width,
385376
opacity: !this.state.imgLoaded ? 1 : 0,
386-
transitionDelay: `0.25s`,
377+
transitionDelay: initialDelay,
387378
height: image.height,
388379
}}
389380
/>
390381
)}
391382

383+
{/* Show the blurry base64 image. */}
384+
{image.base64 && (
385+
<Img src={image.base64} {...placeholderImageProps} />
386+
)}
387+
388+
{/* Show the traced SVG image. */}
389+
{image.tracedSVG && (
390+
<Img src={image.tracedSVG} {...placeholderImageProps} />
391+
)}
392+
392393
{/* Once the image is visible, start downloading the image */}
393394
{this.state.isVisible && (
394395
<picture>

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

+23-3
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ exports.queue = queue
4141

4242
/// Plugin options are loaded onPreInit in gatsby-node
4343
const pluginDefaults = {
44+
forceBase64Format: false,
4445
useMozJpeg: process.env.GATSBY_JPEG_ENCODER === `MOZJPEG`,
4546
stripMetadata: true,
4647
lazyImageGeneration: true,
@@ -58,6 +59,7 @@ const generalArgs = {
5859
duotone: false,
5960
pathPrefix: ``,
6061
toFormat: ``,
62+
toFormatBase64: ``,
6163
sizeByPixelDensity: false,
6264
}
6365

@@ -93,6 +95,7 @@ const healOptions = (
9395
options.pngCompressionLevel = parseInt(options.pngCompressionLevel, 10)
9496
options.pngCompressionSpeed = parseInt(options.pngCompressionSpeed, 10)
9597
options.toFormat = options.toFormat.toLowerCase()
98+
options.toFormatBase64 = options.toFormatBase64.toLowerCase()
9699

97100
// when toFormat is not set we set it based on fileExtension
98101
if (options.toFormat === ``) {
@@ -235,9 +238,11 @@ function queueImageResizing({ file, args = {}, reporter }) {
235238
}
236239
}
237240

241+
// A value in pixels(Int)
242+
const defaultBase64Width = () => pluginOptions.base64Width || 20
238243
async function generateBase64({ file, args, reporter }) {
239244
const options = healOptions(pluginOptions, args, file.extension, {
240-
width: 20,
245+
width: defaultBase64Width(),
241246
})
242247
let pipeline
243248
try {
@@ -247,6 +252,12 @@ async function generateBase64({ file, args, reporter }) {
247252
return null
248253
}
249254

255+
const forceBase64Format =
256+
args.toFormatBase64 || pluginOptions.forceBase64Format
257+
if (forceBase64Format) {
258+
args.toFormat = forceBase64Format
259+
}
260+
250261
pipeline
251262
.resize(options.width, options.height, {
252263
position: options.cropFocus,
@@ -261,6 +272,10 @@ async function generateBase64({ file, args, reporter }) {
261272
progressive: options.jpegProgressive,
262273
force: args.toFormat === `jpg`,
263274
})
275+
.webp({
276+
quality: options.quality,
277+
force: args.toFormat === `webp`,
278+
})
264279

265280
// grayscale
266281
if (options.grayscale) {
@@ -342,9 +357,10 @@ async function fluid({ file, args = {}, reporter, cache }) {
342357
}
343358

344359
const { width, height, density, format } = metadata
360+
const defaultImagePPI = 72 // Standard digital image pixel density
345361
const pixelRatio =
346362
options.sizeByPixelDensity && typeof density === `number` && density > 0
347-
? density / 72
363+
? density / defaultImagePPI
348364
: 1
349365

350366
// if no maxWidth is passed, we need to resize the image based on the passed maxHeight
@@ -446,13 +462,14 @@ async function fluid({ file, args = {}, reporter, cache }) {
446462

447463
let base64Image
448464
if (options.base64) {
449-
const base64Width = 20
465+
const base64Width = options.base64Width || defaultBase64Width()
450466
const base64Height = Math.max(1, Math.round((base64Width * height) / width))
451467
const base64Args = {
452468
duotone: options.duotone,
453469
grayscale: options.grayscale,
454470
rotate: options.rotate,
455471
toFormat: options.toFormat,
472+
toFormatBase64: options.toFormatBase64,
456473
width: base64Width,
457474
height: base64Height,
458475
}
@@ -566,10 +583,13 @@ async function fixed({ file, args = {}, reporter, cache }) {
566583
let base64Image
567584
if (options.base64) {
568585
const base64Args = {
586+
// height is adjusted accordingly with respect to the aspect ratio
587+
width: options.base64Width,
569588
duotone: options.duotone,
570589
grayscale: options.grayscale,
571590
rotate: options.rotate,
572591
toFormat: options.toFormat,
592+
toFormatBase64: options.toFormatBase64,
573593
}
574594

575595
// Get base64 version

‎packages/gatsby-transformer-sharp/src/extend-node-type.js

+14
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,9 @@ const fixedNodeType = ({
115115
height: {
116116
type: GraphQLInt,
117117
},
118+
base64Width: {
119+
type: GraphQLInt,
120+
},
118121
jpegProgressive: {
119122
type: GraphQLBoolean,
120123
defaultValue: true,
@@ -142,6 +145,10 @@ const fixedNodeType = ({
142145
type: ImageFormatType,
143146
defaultValue: ``,
144147
},
148+
toFormatBase64: {
149+
type: ImageFormatType,
150+
defaultValue: ``,
151+
},
145152
cropFocus: {
146153
type: ImageCropFocusType,
147154
defaultValue: sharp.strategy.attention,
@@ -240,6 +247,9 @@ const fluidNodeType = ({
240247
maxHeight: {
241248
type: GraphQLInt,
242249
},
250+
base64Width: {
251+
type: GraphQLInt,
252+
},
243253
grayscale: {
244254
type: GraphQLBoolean,
245255
defaultValue: false,
@@ -267,6 +277,10 @@ const fluidNodeType = ({
267277
type: ImageFormatType,
268278
defaultValue: ``,
269279
},
280+
toFormatBase64: {
281+
type: ImageFormatType,
282+
defaultValue: ``,
283+
},
270284
cropFocus: {
271285
type: ImageCropFocusType,
272286
defaultValue: sharp.strategy.attention,

0 commit comments

Comments
 (0)
Please sign in to comment.