Skip to content

Commit b3c000b

Browse files
timhagnpvdz
authored andcommitted
fix(gatsby-image): add matchMedia to fix wrong aspect ratio and dimensions in art-directed image arrays (#19887)
* fix query for wrong aspect ratio * exchange fixed[0] to getCurrentSrcData({ fixed }) to get correct dimensions * Update packages/gatsby-image/src/index.js change wording according to review Co-Authored-By: Peter van der Zee <209817+pvdz@users.noreply.github.com> * change functions according to reviews by @pvdz * adapt jsdocs to change requests
1 parent 5f3ec67 commit b3c000b

2 files changed

Lines changed: 116 additions & 4 deletions

File tree

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

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,22 @@ const setupImages = (
119119
}
120120

121121
describe(`<Image />`, () => {
122+
const OLD_MATCH_MEDIA = window.matchMedia
123+
beforeEach(() => {
124+
window.matchMedia = jest.fn(media =>
125+
media === `only screen and (min-width: 1024px)`
126+
? {
127+
matches: true,
128+
}
129+
: {
130+
matches: false,
131+
}
132+
)
133+
})
134+
afterEach(() => {
135+
window.matchMedia = OLD_MATCH_MEDIA
136+
})
137+
122138
it(`should render fixed size images`, () => {
123139
const component = setup()
124140
expect(component).toMatchSnapshot()
@@ -204,6 +220,66 @@ describe(`<Image />`, () => {
204220
expect(console.warn).toBeCalled()
205221
})
206222

223+
it(`should select the correct mocked image of fluid variants provided.`, () => {
224+
const tripleFluidImageShapeMock = fluidImagesShapeMock.concat({
225+
aspectRatio: 5,
226+
src: `test_image_4.jpg`,
227+
srcSet: `third other srcSet`,
228+
srcSetWebp: `third other srcSetWebp`,
229+
sizes: `(max-width: 1920px) 100vw, 1920px`,
230+
base64: `string_of_base64`,
231+
media: `only screen and (min-width: 1024px)`,
232+
})
233+
const { container } = render(
234+
<Image
235+
backgroundColor
236+
className={`fluidArtDirectedImage`}
237+
style={{ display: `inline` }}
238+
title={`Title for the image`}
239+
alt={`Alt text for the image`}
240+
crossOrigin={`anonymous`}
241+
fluid={tripleFluidImageShapeMock}
242+
itemProp={`item-prop-for-the-image`}
243+
placeholderStyle={{ color: `red` }}
244+
placeholderClassName={`placeholder`}
245+
/>
246+
)
247+
const aspectPreserver = container.querySelector(`div div div`)
248+
expect(aspectPreserver.getAttribute(`style`)).toEqual(
249+
expect.stringMatching(/padding-bottom: 20%/)
250+
)
251+
})
252+
253+
it(`should select the correct mocked image of fixed variants provided.`, () => {
254+
const tripleFixedImageShapeMock = fixedImagesShapeMock.concat({
255+
width: 1024,
256+
height: 768,
257+
src: `test_image_4.jpg`,
258+
srcSet: `third other srcSet`,
259+
srcSetWebp: `third other srcSetWebp`,
260+
base64: `string_of_base64`,
261+
media: `only screen and (min-width: 1024px)`,
262+
})
263+
const { container } = render(
264+
<Image
265+
backgroundColor
266+
className={`fixedArtDirectedImage`}
267+
style={{ display: `inline` }}
268+
title={`Title for the image`}
269+
alt={`Alt text for the image`}
270+
crossOrigin={`anonymous`}
271+
fixed={tripleFixedImageShapeMock}
272+
itemProp={`item-prop-for-the-image`}
273+
placeholderStyle={{ color: `red` }}
274+
placeholderClassName={`placeholder`}
275+
/>
276+
)
277+
const aspectPreserver = container.querySelector(`div div`)
278+
expect(aspectPreserver.getAttribute(`style`)).toEqual(
279+
expect.stringMatching(/width: 1024px; height: 768px;/)
280+
)
281+
})
282+
207283
it(`should call onLoad and onError image events`, () => {
208284
const onLoadMock = jest.fn()
209285
const onErrorMock = jest.fn()

packages/gatsby-image/src/index.js

Lines changed: 40 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -48,18 +48,54 @@ const convertProps = props => {
4848
return convertedProps
4949
}
5050

51+
/**
52+
* Checks if fluid or fixed are art-direction arrays.
53+
*
54+
* @param currentData {{media?: string}[]} The props to check for images.
55+
* @return {boolean}
56+
*/
57+
const hasArtDirectionSupport = currentData =>
58+
!!currentData &&
59+
Array.isArray(currentData) &&
60+
currentData.some(image => typeof image.media !== `undefined`)
61+
62+
/**
63+
* Tries to detect if a media query matches the current viewport.
64+
* @property media {{media?: string}} A media query string.
65+
* @return {boolean}
66+
*/
67+
const matchesMedia = ({ media }) =>
68+
media ? isBrowser && !!window.matchMedia(media).matches : false
69+
5170
/**
5271
* Find the source of an image to use as a key in the image cache.
5372
* Use `the first image in either `fixed` or `fluid`
54-
* @param {{fluid: {src: string}[], fixed: {src: string}[]}} args
73+
* @param {{fluid: {src: string, media?: string}[], fixed: {src: string, media?: string}[]}} args
5574
* @return {string}
5675
*/
5776
const getImageSrcKey = ({ fluid, fixed }) => {
58-
const data = (fluid && fluid[0]) || (fixed && fixed[0])
77+
const data = fluid ? getCurrentSrcData(fluid) : getCurrentSrcData(fixed)
5978

6079
return data.src
6180
}
6281

82+
/**
83+
* Returns the current src - Preferably with art-direction support.
84+
* @param currentData {{media?: string}[]} The fluid or fixed image array.
85+
* @return {{src: string, media?: string}}
86+
*/
87+
const getCurrentSrcData = currentData => {
88+
if (isBrowser && hasArtDirectionSupport(currentData)) {
89+
// Do we have an image for the current Viewport?
90+
const foundMedia = currentData.findIndex(matchesMedia)
91+
if (foundMedia !== -1) {
92+
return currentData[foundMedia]
93+
}
94+
}
95+
// Else return the first image.
96+
return currentData[0]
97+
}
98+
6399
// Cache if we've seen an image before so we don't bother with
64100
// lazy-loading & fading in on subsequent mounts.
65101
const imageCache = Object.create({})
@@ -416,7 +452,7 @@ class Image extends React.Component {
416452

417453
if (fluid) {
418454
const imageVariants = fluid
419-
const image = imageVariants[0]
455+
const image = getCurrentSrcData(fluid)
420456

421457
return (
422458
<Tag
@@ -516,7 +552,7 @@ class Image extends React.Component {
516552

517553
if (fixed) {
518554
const imageVariants = fixed
519-
const image = imageVariants[0]
555+
const image = getCurrentSrcData(fixed)
520556

521557
const divStyle = {
522558
position: `relative`,

0 commit comments

Comments
 (0)