Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(gatsby-image): Placeholder Improvements #10944

Merged
16 changes: 8 additions & 8 deletions packages/gatsby-image/src/__tests__/__snapshots__/index.js.snap
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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,
wardpeet marked this conversation as resolved.
Show resolved Hide resolved
right: 0,
left: 0,
}}
/>
)}

{/* Show the blurry base64 image. */}
{image.base64 && (
wardpeet marked this conversation as resolved.
Show resolved Hide resolved
<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. */}
wardpeet marked this conversation as resolved.
Show resolved Hide resolved
{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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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