Skip to content

Commit

Permalink
feat(gatsby-image): Placeholder Improvements (#10944)
Browse files Browse the repository at this point in the history
* 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>
  • Loading branch information
polarathene authored and wardpeet committed Mar 12, 2019
1 parent 3bc5f4d commit 44491ef
Show file tree
Hide file tree
Showing 4 changed files with 69 additions and 34 deletions.
16 changes: 8 additions & 8 deletions packages/gatsby-image/src/__tests__/__snapshots__/index.js.snap
Expand Up @@ -6,17 +6,17 @@ exports[`<Img /> should render fixed size images 1`] = `
class="fixedImage gatsby-image-wrapper"
style="position: relative; overflow: hidden; display: inline; width: 100px; height: 100px;"
>
<div
style="width: 100px; opacity: 1; height: 100px;"
title="Title for the image"
/>
<img
alt=""
class="placeholder"
src="string_of_base64"
style="position: absolute; top: 0px; left: 0px; width: 100%; height: 100%; opacity: 1; color: red;"
title="Title for the image"
/>
<div
style="width: 100px; opacity: 1; height: 100px;"
title="Title for the image"
/>
<picture>
<source
srcset="some srcSetWebp"
Expand Down Expand Up @@ -49,17 +49,17 @@ exports[`<Img /> should render fluid images 1`] = `
<div
style="width: 100%; padding-bottom: 66.66666666666667%;"
/>
<div
style="position: absolute; top: 0px; bottom: 0px; opacity: 1; right: 0px; left: 0px;"
title="Title for the image"
/>
<img
alt=""
class="placeholder"
src="string_of_base64"
style="position: absolute; top: 0px; left: 0px; width: 100%; height: 100%; opacity: 1; color: red;"
title="Title for the image"
/>
<div
style="position: absolute; top: 0px; bottom: 0px; opacity: 1; right: 0px; left: 0px;"
title="Title for the image"
/>
<picture>
<source
sizes="(max-width: 600px) 100vw, 600px"
Expand Down
47 changes: 24 additions & 23 deletions packages/gatsby-image/src/index.js
Expand Up @@ -235,10 +235,11 @@ class Image extends React.Component {
const bgColor =
typeof backgroundColor === `boolean` ? `lightgray` : backgroundColor

const initialDelay = `0.25s`
const imagePlaceholderStyle = {
opacity: this.state.imgLoaded ? 0 : 1,
transition: `opacity 0.5s`,
transitionDelay: this.state.imgLoaded ? `0.5s` : `0.25s`,
transitionDelay: this.state.imgLoaded ? `0.5s` : initialDelay,
...imgStyle,
...placeholderStyle,
}
Expand Down Expand Up @@ -278,16 +279,6 @@ class Image extends React.Component {
}}
/>

{/* Show the blurry base64 image. */}
{image.base64 && (
<Img src={image.base64} {...placeholderImageProps} />
)}

{/* Show the traced SVG image. */}
{image.tracedSVG && (
<Img src={image.tracedSVG} {...placeholderImageProps} />
)}

{/* Show a solid background color. */}
{bgColor && (
<Tag
Expand All @@ -298,13 +289,23 @@ class Image extends React.Component {
top: 0,
bottom: 0,
opacity: !this.state.imgLoaded ? 1 : 0,
transitionDelay: `0.35s`,
transitionDelay: initialDelay,
right: 0,
left: 0,
}}
/>
)}

{/* Show the blurry base64 image. */}
{image.base64 && (
<Img src={image.base64} {...placeholderImageProps} />
)}

{/* Show the traced SVG image. */}
{image.tracedSVG && (
<Img src={image.tracedSVG} {...placeholderImageProps} />
)}

{/* Once the image is visible (or the browser doesn't support IntersectionObserver), start downloading the image */}
{this.state.isVisible && (
<picture>
Expand Down Expand Up @@ -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 && (
<Img src={image.base64} {...placeholderImageProps} />
)}

{/* Show the traced SVG image. */}
{image.tracedSVG && (
<Img src={image.tracedSVG} {...placeholderImageProps} />
)}

{/* Show a solid background color. */}
{bgColor && (
<Tag
Expand All @@ -383,12 +374,22 @@ class Image extends React.Component {
backgroundColor: bgColor,
width: image.width,
opacity: !this.state.imgLoaded ? 1 : 0,
transitionDelay: `0.25s`,
transitionDelay: initialDelay,
height: image.height,
}}
/>
)}

{/* Show the blurry base64 image. */}
{image.base64 && (
<Img src={image.base64} {...placeholderImageProps} />
)}

{/* Show the traced SVG image. */}
{image.tracedSVG && (
<Img src={image.tracedSVG} {...placeholderImageProps} />
)}

{/* Once the image is visible, start downloading the image */}
{this.state.isVisible && (
<picture>
Expand Down
26 changes: 23 additions & 3 deletions packages/gatsby-plugin-sharp/src/index.js
Expand Up @@ -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,
Expand All @@ -58,6 +59,7 @@ const generalArgs = {
duotone: false,
pathPrefix: ``,
toFormat: ``,
toFormatBase64: ``,
sizeByPixelDensity: false,
}

Expand Down Expand Up @@ -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 === ``) {
Expand Down Expand Up @@ -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 {
Expand All @@ -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,
Expand All @@ -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) {
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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,
}
Expand Down Expand Up @@ -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
Expand Down
14 changes: 14 additions & 0 deletions packages/gatsby-transformer-sharp/src/extend-node-type.js
Expand Up @@ -115,6 +115,9 @@ const fixedNodeType = ({
height: {
type: GraphQLInt,
},
base64Width: {
type: GraphQLInt,
},
jpegProgressive: {
type: GraphQLBoolean,
defaultValue: true,
Expand Down Expand Up @@ -142,6 +145,10 @@ const fixedNodeType = ({
type: ImageFormatType,
defaultValue: ``,
},
toFormatBase64: {
type: ImageFormatType,
defaultValue: ``,
},
cropFocus: {
type: ImageCropFocusType,
defaultValue: sharp.strategy.attention,
Expand Down Expand Up @@ -240,6 +247,9 @@ const fluidNodeType = ({
maxHeight: {
type: GraphQLInt,
},
base64Width: {
type: GraphQLInt,
},
grayscale: {
type: GraphQLBoolean,
defaultValue: false,
Expand Down Expand Up @@ -267,6 +277,10 @@ const fluidNodeType = ({
type: ImageFormatType,
defaultValue: ``,
},
toFormatBase64: {
type: ImageFormatType,
defaultValue: ``,
},
cropFocus: {
type: ImageCropFocusType,
defaultValue: sharp.strategy.attention,
Expand Down

0 comments on commit 44491ef

Please sign in to comment.