feat: use <picture> elements for responsive images @W-18714493#2724
feat: use <picture> elements for responsive images @W-18714493#2724
<picture> elements for responsive images @W-18714493#2724Conversation
🎉 Snyk checks have passed. No issues have been found so far.✅ security/snyk check is complete. No issues have been found. (View Details) ✅ license/snyk check is complete. No issues have been found. (View Details) |
65eab27 to
dfd8db9
Compare
packages/template-retail-react-app/app/utils/responsive-image.js
Outdated
Show resolved
Hide resolved
packages/template-retail-react-app/app/utils/responsive-image.js
Outdated
Show resolved
Hide resolved
packages/template-retail-react-app/app/utils/responsive-image.js
Outdated
Show resolved
Hide resolved
packages/template-retail-react-app/app/utils/responsive-image.js
Outdated
Show resolved
Hide resolved
packages/template-retail-react-app/app/utils/responsive-image.js
Outdated
Show resolved
Hide resolved
packages/template-retail-react-app/app/utils/responsive-image.js
Outdated
Show resolved
Hide resolved
packages/template-retail-react-app/app/utils/responsive-image.js
Outdated
Show resolved
Hide resolved
packages/template-retail-react-app/app/utils/responsive-image.js
Outdated
Show resolved
Hide resolved
packages/template-retail-react-app/app/utils/responsive-image.js
Outdated
Show resolved
Hide resolved
joeluong-sfcc
left a comment
There was a problem hiding this comment.
Can you add details/demo on how to test this?
packages/template-retail-react-app/app/utils/responsive-image.js
Outdated
Show resolved
Hide resolved
packages/template-retail-react-app/app/utils/responsive-image.js
Outdated
Show resolved
Hide resolved
packages/template-retail-react-app/app/utils/responsive-image.js
Outdated
Show resolved
Hide resolved
bc7239b to
0a86480
Compare
<picture> elements for responsive images and support image densities @W-18714493
packages/template-retail-react-app/app/components/dynamic-image/index.jsx
Outdated
Show resolved
Hide resolved
packages/template-retail-react-app/app/components/dynamic-image/index.jsx
Show resolved
Hide resolved
2af870d to
facbc52
Compare
packages/template-retail-react-app/app/utils/responsive-image.js
Outdated
Show resolved
Hide resolved
packages/template-retail-react-app/app/utils/responsive-image.js
Outdated
Show resolved
Hide resolved
packages/template-retail-react-app/app/utils/responsive-image.js
Outdated
Show resolved
Hide resolved
packages/template-retail-react-app/app/utils/responsive-image.js
Outdated
Show resolved
Hide resolved
packages/template-retail-react-app/app/utils/responsive-image.js
Outdated
Show resolved
Hide resolved
packages/template-retail-react-app/app/utils/responsive-image.js
Outdated
Show resolved
Hide resolved
0edf32c to
5315c47
Compare
packages/template-retail-react-app/app/utils/responsive-image.js
Outdated
Show resolved
Hide resolved
Irrelevant reasons. Also misses the point that we simply have to make that change to
5315c47 to
d7a3000
Compare
<picture> elements for responsive images and support image densities @W-18714493<picture> elements for responsive images and support image densities @W-18714493
d7a3000 to
b7ccb8d
Compare
<picture> elements for responsive images and support image densities @W-18714493<picture> elements for responsive images @W-18714493
d7e8992 to
5051b3a
Compare
| () => getResponsiveImageAttributes({src, widths, breakpoints: theme.breakpoints}), | ||
| [src, widths, theme.breakpoints] | ||
| ) | ||
| const [responsiveImageProps, numSources, effectiveImageProps, responsiveLinks] = useMemo(() => { |
There was a problem hiding this comment.
Glad to see this is memoized
| const vwValue = /^\d+vw$/ | ||
| const pxValue = /^\d+px$/ | ||
| const emValue = /^\d+em$/ |
There was a problem hiding this comment.
For a moment I was worried as I traversed a few function call depths that we weren't catching / throwing for values outside the pattern. Looks good 👍
| expect(elements[0]).toHaveAttribute('decoding', 'async') | ||
| expect(wrapper.firstElementChild).toBe(elements[0]) | ||
| }) | ||
|
|
||
| test('renders an image with explicit widths', () => { | ||
| const {getByTestId, getAllByTitle} = renderWithProviders( | ||
| <DynamicImage | ||
| data-testid={'dynamic-image'} | ||
| src={src} | ||
| imageProps={{ | ||
| ...imageProps, | ||
| loading: 'lazy' | ||
| }} | ||
| widths={['50vw', '50vw', '20vw', '20vw', '25vw']} | ||
| /> | ||
| ) | ||
|
|
||
| const wrapper = getByTestId('dynamic-image') | ||
| const elements = getAllByTitle(imageProps.title) | ||
| expect(elements).toHaveLength(1) | ||
| expect(elements[0]).toHaveAttribute('src', src) | ||
| expect(elements[0]).toHaveAttribute('loading', 'lazy') | ||
| expect(elements[0]).toHaveAttribute('decoding', 'async') | ||
| expect(elements[0]).not.toHaveAttribute('sizes') | ||
| expect(elements[0]).not.toHaveAttribute('srcset') | ||
|
|
||
| expect(wrapper.firstElementChild).not.toBe(elements[0]) | ||
| expect(wrapper.firstElementChild.tagName.toLowerCase()).toBe('picture') | ||
|
|
||
| const sourceElements = Array.from(wrapper.querySelectorAll('source')) | ||
| expect(sourceElements).toHaveLength(5) | ||
| expect(sourceElements[0]).toHaveAttribute('media', '(min-width: 80em)') | ||
| expect(sourceElements[0]).toHaveAttribute('sizes', '25vw') | ||
| expect(sourceElements[0]).toHaveAttribute( | ||
| 'srcset', | ||
| [384, 768].map((width) => `${src} ${width}w`).join(', ') | ||
| ) | ||
| expect(sourceElements[1]).toHaveAttribute('media', '(min-width: 62em)') | ||
| expect(sourceElements[1]).toHaveAttribute('sizes', '20vw') | ||
| expect(sourceElements[1]).toHaveAttribute( | ||
| 'srcset', | ||
| [256, 512].map((width) => `${src} ${width}w`).join(', ') | ||
| ) | ||
| expect(sourceElements[2]).toHaveAttribute('media', '(min-width: 48em)') | ||
| expect(sourceElements[2]).toHaveAttribute('sizes', '20vw') | ||
| expect(sourceElements[2]).toHaveAttribute( | ||
| 'srcset', | ||
| [198, 396].map((width) => `${src} ${width}w`).join(', ') | ||
| ) | ||
| expect(sourceElements[3]).toHaveAttribute('media', '(min-width: 30em)') | ||
| expect(sourceElements[3]).toHaveAttribute('sizes', '50vw') | ||
| expect(sourceElements[3]).toHaveAttribute( | ||
| 'srcset', | ||
| [384, 768].map((width) => `${src} ${width}w`).join(', ') | ||
| ) | ||
| expect(sourceElements[4]).not.toHaveAttribute('media') | ||
| expect(sourceElements[4]).toHaveAttribute('sizes', '50vw') | ||
| expect(sourceElements[4]).toHaveAttribute( | ||
| 'srcset', | ||
| [240, 480].map((width) => `${src} ${width}w`).join(', ') | ||
| ) | ||
|
|
||
| expect(Helmet.peek()?.linkTags ?? []).toStrictEqual([]) |
There was a problem hiding this comment.
Not seeing an explicit test for the error thrown by invalid component usage, e.g.
<DynamicImage
data-testid={'dynamic-image'}
src={src}
imageProps={{
...imageProps,
loading: 'lazy'
}}
widths={['INVALID']}
/>
There was a problem hiding this comment.
Hmm, in that case it wasn't there before either. Didn't add/change anything to the widths logic/handling.
| // Each product image can take up the full 50% of the screen width | ||
| '50vw', // base <= 479px | ||
| '50vw', // sm >= 480px ; <= 767px |
There was a problem hiding this comment.
It does feel good to move away from the density logic that was here in the previous iteration 👍
There was a problem hiding this comment.
I'm not quite sure yet. The unconditional addition of images for double density and thus having no influence on the behavior at all is not necessarily the real thing either.
bfeister
left a comment
There was a problem hiding this comment.
Looks good to me. Very interested to hear the lab improvements yielded by this
Signed-off-by: Steffen Eckardt <3235219+seckardt@users.noreply.github.com>
Description
This PR introduces using
<picture>elements as the base for responsive images created by the<DynamicImage/>component. This change is needed to support art direction of images on our pages, but most importantly paves the way for using modern image formats like AVIF or WebP in our Dynamic Imaging Service (DIS), which is targeted for the next minor release already. The change also adds responsive preloading to the mix, which is otherwise tricky to get right.This PR additionally adjusts the sizes of the responsive images on the PLP, because previously defined dimensions (see PR #2446) forgot to take the existence and the impact of the search refinements panel into account, which lead to a significant over-fetching of images (~400-500kB per page on e.g. a larger Mac desktop).
Types of Changes
Changes
How to Test-Drive This PR
Checklists
General
Accessibility Compliance
You must check off all items in one of the follow two lists:
or...
Localization