Skip to content

Instantly share code, notes, and snippets.

@daydream05
Created May 30, 2020 03:13
Show Gist options
  • Save daydream05/b5befd50f9c9001fb094f331f98a3ec5 to your computer and use it in GitHub Desktop.
Save daydream05/b5befd50f9c9001fb094f331f98a3ec5 to your computer and use it in GitHub Desktop.
import _ from 'lodash'
import qs from 'qs'
// @see https://www.contentful.com/developers/docs/references/images-api/#/reference/resizing-&-cropping/specify-width-&-height
const CONTENTFUL_IMAGE_MAX_SIZE = 4000
const isImage = image =>
_.includes(
[`image/jpeg`, `image/jpg`, `image/png`, `image/webp`, `image/gif`],
_.get(image, `file.contentType`)
)
const getBasicImageProps = (image, args) => {
let aspectRatio
if (args.width && args.height) {
aspectRatio = args.width / args.height
} else {
aspectRatio =
image.file.details.image.width / image.file.details.image.height
}
return {
baseUrl: image.file.url,
contentType: image.file.contentType,
aspectRatio,
width: image.file.details.image.width,
height: image.file.details.image.height,
}
}
const createUrl = (imgUrl, options = {}) => {
// Convert to Contentful names and filter out undefined/null values.
const args = _.pickBy(
{
w: options.width,
h: options.height,
fl: options.jpegProgressive ? `progressive` : null,
q: options.quality,
fm: options.toFormat || ``,
fit: options.resizingBehavior || ``,
f: options.cropFocus || ``,
bg: options.background || ``,
},
_.identity
)
return `${imgUrl}?${qs.stringify(args)}`
}
export const getFluidGatsbyImage = (image, options) => {
if (!isImage(image)) return null
console.log(image)
const { baseUrl, width, aspectRatio } = getBasicImageProps(image, options)
let desiredAspectRatio = aspectRatio
// If no dimension is given, set a default maxWidth
if (options.maxWidth === undefined && options.maxHeight === undefined) {
options.maxWidth = 800
}
// If only a maxHeight is given, calculate the maxWidth based on the height and the aspect ratio
if (options.maxHeight !== undefined && options.maxWidth === undefined) {
options.maxWidth = Math.round(options.maxHeight * desiredAspectRatio)
}
// If we're cropping, calculate the specified aspect ratio.
if (options.maxHeight !== undefined && options.maxWidth !== undefined) {
desiredAspectRatio = options.maxWidth / options.maxHeight
}
// If the users didn't set a default sizes, we'll make one.
if (!options.sizes) {
options.sizes = `(max-width: ${options.maxWidth}px) 100vw, ${options.maxWidth}px`
}
// Create sizes (in width) for the image. If the max width of the container
// for the rendered markdown file is 800px, the sizes would then be: 200,
// 400, 800, 1200, 1600, 2400.
//
// This is enough sizes to provide close to the optimal image size for every
// device size / screen resolution
let fluidSizes = []
fluidSizes.push(options.maxWidth / 4)
fluidSizes.push(options.maxWidth / 2)
fluidSizes.push(options.maxWidth)
fluidSizes.push(options.maxWidth * 1.5)
fluidSizes.push(options.maxWidth * 2)
fluidSizes.push(options.maxWidth * 3)
fluidSizes = fluidSizes.map(Math.round)
// Filter out sizes larger than the image's maxWidth and the contentful image's max size.
const filteredSizes = fluidSizes.filter(size => {
const calculatedHeight = Math.round(size / desiredAspectRatio)
return (
size <= CONTENTFUL_IMAGE_MAX_SIZE &&
calculatedHeight <= CONTENTFUL_IMAGE_MAX_SIZE &&
size <= width
)
})
// Add the original image (if it isn't already in there) to ensure the largest image possible
// is available for small images.
if (
!filteredSizes.includes(parseInt(width)) &&
parseInt(width) < CONTENTFUL_IMAGE_MAX_SIZE &&
Math.round(width / desiredAspectRatio) < CONTENTFUL_IMAGE_MAX_SIZE
) {
filteredSizes.push(width)
}
// Sort sizes for prettiness.
const sortedSizes = _.sortBy(filteredSizes)
// Create the srcSet.
const srcSet = sortedSizes
.map(width => {
const h = Math.round(width / desiredAspectRatio)
return `${createUrl(image.file.url, {
...options,
width,
height: h,
})} ${Math.round(width)}w`
})
.join(`,\n`)
return {
aspectRatio: desiredAspectRatio,
baseUrl,
src: createUrl(baseUrl, {
...options,
width: options.maxWidth,
height: options.maxHeight,
}),
srcSet,
sizes: options.sizes,
}
}
@daydream05
Copy link
Author

@Seth-Carter glad to hear that! There's an upcoming version of the contentful source that will allow us to query for gatsby image and also fixes a lot of rich text problems.

@hannessolo
Copy link

Wow, thanks so much. After trying loads of different solutions, this finally worked for me.

@ctekk7
Copy link

ctekk7 commented Oct 7, 2020

Thank you!

@daydream05
Copy link
Author

To the folks who are currently using this or about to use it, there's an upcoming update on the gatsby-source-contentful . See here: gatsbyjs/gatsby#25249

You can do an npm install gatsby-source-contentful@next if you wanna give it a try.

The new update would allow us to reference images straight to rich text so you won't hopefully have to use this hack. Note though that it has breaking changes.

I've been using it for my new projects and have little issues so far. And the best part is querying reference fields inside rich text. Those were harder to "hack" because we didn't have utility like it similar to gatsby-images.

@ctekk7
Copy link

ctekk7 commented Oct 8, 2020

Thanks for the headsup, I already tried it before using your solution. Sadly when using gatsby-source-contentful@next I got a couple of errors which I wasn't really in the mood for debugging for a not yet released version.

P.s. do you have an ETA when the update will be shipped?

@bsgreenb
Copy link

@daydream05 thanks for keeping this updated. I'm going to try gatsby-source-contentful@next, wading through the errors seems worth it.

@samh21
Copy link

samh21 commented Nov 10, 2020

@daydream05 any idea why I'm getting a 'Check render method of ...' error? All the options appear to be returning the expected values.

Screenshot 2020-11-10 at 12 45 41

@daydream05
Copy link
Author

@samh21 can you share the code for the News component? This error happens if your component is not returning anything.

@daydream05
Copy link
Author

The new version of contentful source is live at gatsby-source-contentful@4.0.0!

@extendit-experts
Copy link

extendit-experts commented Nov 14, 2020

Thanks for the headsup, do you have a link to the relevant changes to this topic?

@extendit-experts
Copy link

Thank you!

@yogeshdecodes
Copy link

Thanks for great solution. Finally solved fluid image issue after trying every solution out there.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment