From 44491ef341126e8f1c8e65c025f42770ec919dbc Mon Sep 17 00:00:00 2001 From: Brennan Kinney Date: Wed, 13 Mar 2019 03:04:34 +1300 Subject: [PATCH] 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 `` 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 * Change `density_72PPI` to `defaultImagePPI` 2 Co-Authored-By: polarathene --- .../src/__tests__/__snapshots__/index.js.snap | 16 +++---- packages/gatsby-image/src/index.js | 47 ++++++++++--------- packages/gatsby-plugin-sharp/src/index.js | 26 ++++++++-- .../src/extend-node-type.js | 14 ++++++ 4 files changed, 69 insertions(+), 34 deletions(-) diff --git a/packages/gatsby-image/src/__tests__/__snapshots__/index.js.snap b/packages/gatsby-image/src/__tests__/__snapshots__/index.js.snap index c8f07e691e644..c13a35df2cbfc 100644 --- a/packages/gatsby-image/src/__tests__/__snapshots__/index.js.snap +++ b/packages/gatsby-image/src/__tests__/__snapshots__/index.js.snap @@ -6,6 +6,10 @@ exports[` should render fixed size images 1`] = ` class="fixedImage gatsby-image-wrapper" style="position: relative; overflow: hidden; display: inline; width: 100px; height: 100px;" > +
should render fixed size images 1`] = ` style="position: absolute; top: 0px; left: 0px; width: 100%; height: 100%; opacity: 1; color: red;" title="Title for the image" /> -
should render fluid images 1`] = `
+
should render fluid images 1`] = ` style="position: absolute; top: 0px; left: 0px; width: 100%; height: 100%; opacity: 1; color: red;" title="Title for the image" /> -
- {/* Show the blurry base64 image. */} - {image.base64 && ( - - )} - - {/* Show the traced SVG image. */} - {image.tracedSVG && ( - - )} - {/* Show a solid background color. */} {bgColor && ( )} + {/* Show the blurry base64 image. */} + {image.base64 && ( + + )} + + {/* Show the traced SVG image. */} + {image.tracedSVG && ( + + )} + {/* Once the image is visible (or the browser doesn't support IntersectionObserver), start downloading the image */} {this.state.isVisible && ( @@ -365,16 +366,6 @@ class Image extends React.Component { ref={this.handleRef} key={`fixed-${JSON.stringify(image.srcSet)}`} > - {/* Show the blurry base64 image. */} - {image.base64 && ( - - )} - - {/* Show the traced SVG image. */} - {image.tracedSVG && ( - - )} - {/* Show a solid background color. */} {bgColor && ( )} + {/* Show the blurry base64 image. */} + {image.base64 && ( + + )} + + {/* Show the traced SVG image. */} + {image.tracedSVG && ( + + )} + {/* Once the image is visible, start downloading the image */} {this.state.isVisible && ( diff --git a/packages/gatsby-plugin-sharp/src/index.js b/packages/gatsby-plugin-sharp/src/index.js index fc31465c4c8e6..3805260eb7df3 100644 --- a/packages/gatsby-plugin-sharp/src/index.js +++ b/packages/gatsby-plugin-sharp/src/index.js @@ -41,6 +41,7 @@ exports.queue = queue /// Plugin options are loaded onPreInit in gatsby-node const pluginDefaults = { + forceBase64Format: false, useMozJpeg: process.env.GATSBY_JPEG_ENCODER === `MOZJPEG`, stripMetadata: true, lazyImageGeneration: true, @@ -58,6 +59,7 @@ const generalArgs = { duotone: false, pathPrefix: ``, toFormat: ``, + toFormatBase64: ``, sizeByPixelDensity: false, } @@ -93,6 +95,7 @@ const healOptions = ( options.pngCompressionLevel = parseInt(options.pngCompressionLevel, 10) options.pngCompressionSpeed = parseInt(options.pngCompressionSpeed, 10) options.toFormat = options.toFormat.toLowerCase() + options.toFormatBase64 = options.toFormatBase64.toLowerCase() // when toFormat is not set we set it based on fileExtension if (options.toFormat === ``) { @@ -235,9 +238,11 @@ function queueImageResizing({ file, args = {}, reporter }) { } } +// A value in pixels(Int) +const defaultBase64Width = () => pluginOptions.base64Width || 20 async function generateBase64({ file, args, reporter }) { const options = healOptions(pluginOptions, args, file.extension, { - width: 20, + width: defaultBase64Width(), }) let pipeline try { @@ -247,6 +252,12 @@ async function generateBase64({ file, args, reporter }) { return null } + const forceBase64Format = + args.toFormatBase64 || pluginOptions.forceBase64Format + if (forceBase64Format) { + args.toFormat = forceBase64Format + } + pipeline .resize(options.width, options.height, { position: options.cropFocus, @@ -261,6 +272,10 @@ async function generateBase64({ file, args, reporter }) { progressive: options.jpegProgressive, force: args.toFormat === `jpg`, }) + .webp({ + quality: options.quality, + force: args.toFormat === `webp`, + }) // grayscale if (options.grayscale) { @@ -342,9 +357,10 @@ async function fluid({ file, args = {}, reporter, cache }) { } const { width, height, density, format } = metadata + const defaultImagePPI = 72 // Standard digital image pixel density const pixelRatio = options.sizeByPixelDensity && typeof density === `number` && density > 0 - ? density / 72 + ? density / defaultImagePPI : 1 // 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 }) { let base64Image if (options.base64) { - const base64Width = 20 + const base64Width = options.base64Width || defaultBase64Width() const base64Height = Math.max(1, Math.round((base64Width * height) / width)) const base64Args = { duotone: options.duotone, grayscale: options.grayscale, rotate: options.rotate, toFormat: options.toFormat, + toFormatBase64: options.toFormatBase64, width: base64Width, height: base64Height, } @@ -566,10 +583,13 @@ async function fixed({ file, args = {}, reporter, cache }) { let base64Image if (options.base64) { const base64Args = { + // height is adjusted accordingly with respect to the aspect ratio + width: options.base64Width, duotone: options.duotone, grayscale: options.grayscale, rotate: options.rotate, toFormat: options.toFormat, + toFormatBase64: options.toFormatBase64, } // Get base64 version diff --git a/packages/gatsby-transformer-sharp/src/extend-node-type.js b/packages/gatsby-transformer-sharp/src/extend-node-type.js index 935bb52e31b46..519c23a00d1c6 100644 --- a/packages/gatsby-transformer-sharp/src/extend-node-type.js +++ b/packages/gatsby-transformer-sharp/src/extend-node-type.js @@ -115,6 +115,9 @@ const fixedNodeType = ({ height: { type: GraphQLInt, }, + base64Width: { + type: GraphQLInt, + }, jpegProgressive: { type: GraphQLBoolean, defaultValue: true, @@ -142,6 +145,10 @@ const fixedNodeType = ({ type: ImageFormatType, defaultValue: ``, }, + toFormatBase64: { + type: ImageFormatType, + defaultValue: ``, + }, cropFocus: { type: ImageCropFocusType, defaultValue: sharp.strategy.attention, @@ -240,6 +247,9 @@ const fluidNodeType = ({ maxHeight: { type: GraphQLInt, }, + base64Width: { + type: GraphQLInt, + }, grayscale: { type: GraphQLBoolean, defaultValue: false, @@ -267,6 +277,10 @@ const fluidNodeType = ({ type: ImageFormatType, defaultValue: ``, }, + toFormatBase64: { + type: ImageFormatType, + defaultValue: ``, + }, cropFocus: { type: ImageCropFocusType, defaultValue: sharp.strategy.attention,