Skip to content

Commit 28dc678

Browse files
authored
Merge pull request #60 from thomasKn/fetchpriority
Add fetchpriority, decoding and content-visibility
2 parents 75394a6 + 959e066 commit 28dc678

11 files changed

+158
-95
lines changed

Diff for: app/components/CmsSection.tsx

+2-2
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ function SectionWrapper(props: {
7373

7474
return props.type === 'footer' ? (
7575
<footer
76-
className="section-padding relative bg-background text-foreground"
76+
className="section-padding relative bg-background text-foreground [content-visibility:auto]"
7777
data-footer-type={isDev ? sectionType : null}
7878
>
7979
<style dangerouslySetInnerHTML={{__html: cssVars}} />
@@ -84,7 +84,7 @@ function SectionWrapper(props: {
8484
</footer>
8585
) : (
8686
<section
87-
className="section-padding relative bg-background text-foreground"
87+
className="section-padding relative bg-background text-foreground [content-visibility:auto]"
8888
data-section-type={isDev ? sectionType : null}
8989
id={`section-${data._key}`}
9090
>

Diff for: app/components/ShopifyImage.tsx

+31-11
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import type {HydrogenImageProps} from '@shopify/hydrogen-react/Image';
22
import type {ImageFragmentFragment} from 'storefrontapi.generated';
33

4-
import {Image} from '@shopify/hydrogen';
4+
import {Image, parseGid} from '@shopify/hydrogen';
55

66
import {cn} from '~/lib/utils';
77

@@ -17,22 +17,42 @@ export function ShopifyImage({
1717
className?: string;
1818
data: ImageFragmentFragment;
1919
} & HydrogenImageProps) {
20+
const id = parseGid(data.id || undefined).id;
2021
// Todo => Global image border-radius setting should apply to the wrapper <span/>
22+
// No padding should be applied to the wrapper <span/> or the <img/> tag to avoid blurry LQIP becoming visible
2123
return (
22-
<span className="relative block overflow-hidden">
24+
<span
25+
className="relative block overflow-hidden !p-0"
26+
id={id ? `img-${id}` : undefined}
27+
>
2328
<Image
24-
className={cn(['relative z-[1]', className])}
29+
className={cn('relative z-[1]', className, '!p-0')}
2530
data={data}
2631
{...props}
2732
/>
28-
<Image
29-
className={cn(['absolute inset-0 overflow-hidden blur-xl', className])}
30-
data={{
31-
...data,
32-
url: data.thumbnail,
33-
}}
34-
{...props}
35-
/>
33+
{id && (
34+
<style
35+
// Blurry bg image used as LQIP (Low Quality Image Placeholder)
36+
// while high quality image is loading.
37+
dangerouslySetInnerHTML={{
38+
__html: `
39+
#img-${id}::before {
40+
content: "";
41+
position: absolute;
42+
background: url(${data.thumbnail});
43+
background-size: cover;
44+
background-repeat: no-repeat;
45+
background-position: center;
46+
top: 0;
47+
left: 0;
48+
right: 0;
49+
bottom: 0;
50+
filter: blur(10px);
51+
}
52+
`.trim(),
53+
}}
54+
/>
55+
)}
3656
</span>
3757
);
3858
}

Diff for: app/components/layout/Header.tsx

+6-2
Original file line numberDiff line numberDiff line change
@@ -35,9 +35,13 @@ export function Header() {
3535
<style dangerouslySetInnerHTML={{__html: colorPaletteCssVars}} />
3636
<div className="[--mobileHeaderXPadding:.75rem] lg:container">
3737
<div className="flex items-center justify-between">
38-
<Link prefetch="intent" to={homePath}>
38+
<Link
39+
className="pl-[var(--mobileHeaderXPadding)] lg:pl-0"
40+
prefetch="intent"
41+
to={homePath}
42+
>
3943
<Logo
40-
className="h-auto w-[var(--logoWidth)] pl-[var(--mobileHeaderXPadding)] lg:pl-0"
44+
className="h-auto w-[var(--logoWidth)]"
4145
sizes={logoWidth}
4246
style={
4347
{

Diff for: app/components/product/MediaGallery.tsx

+2
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ export function MediaGallery() {
2222
<ShopifyImage
2323
className="h-auto w-full"
2424
data={media.image}
25+
decoding="sync"
26+
fetchpriority="high"
2527
loading="eager"
2628
sizes="(min-width: 1024px) 50vw, 100vw"
2729
/>

Diff for: app/components/sanity/SanityImage.tsx

+54-37
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,33 @@
11
import imageUrlBuilder from '@sanity/image-url';
2+
import React from 'react';
23

34
import type {SanityImageFragment} from '~/lib/type';
45

56
import {useEnvironmentVariables} from '~/hooks/useEnvironmentVariables';
67
import {cn} from '~/lib/utils';
78

8-
export function SanityImage(props: {
9+
export function SanityImage({
10+
aspectRatio,
11+
className,
12+
data,
13+
decoding = 'async',
14+
fetchpriority,
15+
loading,
16+
sanityEncodeData,
17+
sizes,
18+
style,
19+
}: {
920
aspectRatio?: string;
1021
className?: string;
1122
data?: SanityImageFragment | null;
23+
decoding?: 'async' | 'auto' | 'sync';
24+
fetchpriority?: 'auto' | 'high' | 'low';
1225
loading?: 'eager' | 'lazy';
1326
sanityEncodeData?: string;
1427
sizes?: null | string;
1528
style?: React.CSSProperties;
1629
}) {
1730
const env = useEnvironmentVariables();
18-
const {
19-
aspectRatio,
20-
className,
21-
data,
22-
loading,
23-
sanityEncodeData,
24-
sizes,
25-
style,
26-
} = props;
2731

2832
if (!data) {
2933
return null;
@@ -88,52 +92,65 @@ export function SanityImage(props: {
8892
.concat(`, ${urlDefault} ${data.width}w`);
8993

9094
// Todo => Global image border-radius setting should apply to the wrapper <span/>
95+
// No padding should be applied to the wrapper <span/> or the <img/> tag to avoid blurry LQIP becoming visible
9196
return (
92-
<span className="relative block overflow-hidden">
97+
<span
98+
className="relative block overflow-hidden !p-0"
99+
id={data._ref ? `img-${data._ref}` : undefined}
100+
style={
101+
{
102+
'--focalX': focalCoords.x + '%',
103+
'--focalY': focalCoords.y + '%',
104+
} as React.CSSProperties
105+
}
106+
>
93107
<img
94108
alt={data.altText || ''}
95-
className={cn([
109+
className={cn(
96110
'relative z-[1] object-[var(--focalX)_var(--focalY)]',
97111
className,
98-
])}
112+
'!p-0',
113+
)}
99114
// Adding this attribute makes sure the image is always clickable in the Presentation tool
100115
data-sanity={sanityEncodeData}
116+
decoding={decoding}
117+
fetchpriority={fetchpriority}
101118
height={aspectRatioHeight || data.height}
102119
loading={loading}
103-
sizes={sizes || undefined}
120+
sizes={sizes!}
104121
src={urlDefault}
105122
srcSet={srcSet}
106123
style={
107124
{
108-
'--focalX': focalCoords.x + '%',
109-
'--focalY': focalCoords.y + '%',
110-
aspectRatio: `${aspectRatioWidth || data.width}/${aspectRatioHeight || data.height}`,
111125
...style,
112-
} as React.CSSProperties
113-
}
114-
width={aspectRatioWidth || data.width}
115-
/>
116-
{/* Preview blurry image (30px) that will load before the highres image */}
117-
<img
118-
alt={data.altText || ''}
119-
className={cn([
120-
'absolute inset-0 object-[var(--focalX)_var(--focalY)] blur-2xl',
121-
className,
122-
])}
123-
height={aspectRatioHeight || data.height}
124-
loading="eager"
125-
sizes={sizes || undefined}
126-
src={urlPreview}
127-
srcSet={urlPreview}
128-
style={
129-
{
130-
'--focalX': focalCoords.x + '%',
131-
'--focalY': focalCoords.y + '%',
132126
aspectRatio: `${aspectRatioWidth || data.width}/${aspectRatioHeight || data.height}`,
133127
} as React.CSSProperties
134128
}
135129
width={aspectRatioWidth || data.width}
136130
/>
131+
{data._ref && (
132+
<style
133+
// Blurry bg image used as LQIP (Low Quality Image Placeholder)
134+
// while high quality image is loading.
135+
dangerouslySetInnerHTML={{
136+
__html: `
137+
#img-${data._ref}::before {
138+
content: "";
139+
position: absolute;
140+
background: url(${urlPreview});
141+
background-size: cover;
142+
background-repeat: no-repeat;
143+
background-position: center;
144+
top: 0;
145+
left: 0;
146+
right: 0;
147+
bottom: 0;
148+
filter: blur(10px);
149+
}
150+
`.trim(),
151+
}}
152+
/>
153+
)}
137154
</span>
138155
);
139156
}

Diff for: app/components/sections/CollectionBannerSection.tsx

+2
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@ export function CollectionBannerSection(
3434
aspectRatio="16/9"
3535
crop="center"
3636
data={collection.image}
37+
decoding="sync"
38+
fetchpriority="high"
3739
loading="eager"
3840
sizes="100vw"
3941
/>

Diff for: app/components/sections/ImageBannerSection.tsx

+2
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ export function ImageBannerSection(
3030
<SanityImage
3131
aspectRatio="16/9"
3232
data={data.backgroundImage}
33+
decoding="sync"
34+
fetchpriority="high"
3335
sizes="100vw"
3436
/>
3537
</BannerMedia>

Diff for: app/components/ui/Card.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ const CardMedia = forwardRef<
4646
ref={ref}
4747
{...props}
4848
>
49-
<div className="origin-center scale-[1.005] [transition:transform_0.5s_ease] group-hover/card:scale-[1.03] [&>*]:size-full [&>*]:object-cover">
49+
<div className="origin-center scale-[1.005] [transition:transform_0.5s_ease] group-hover/card:scale-[1.03] [&_img]:size-full [&_img]:object-cover">
5050
{props.children}
5151
</div>
5252
</div>

Diff for: app/graphql/fragments.ts

+3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
export const IMAGE_FRAGMENT = `#graphql
22
fragment ImageFragment on Image {
3+
id
34
altText
45
width
56
height
@@ -10,6 +11,7 @@ export const IMAGE_FRAGMENT = `#graphql
1011

1112
export const PRODUCT_VARIANT_IMAGE_FRAGMENT = `#graphql
1213
fragment ProductVariantImageFragment on Image {
14+
id
1315
altText
1416
width
1517
height
@@ -20,6 +22,7 @@ export const PRODUCT_VARIANT_IMAGE_FRAGMENT = `#graphql
2022

2123
export const PRODUCT_CARD_IMAGE_FRAGMENT = `#graphql
2224
fragment ProductCardImageFragment on Image {
25+
id
2326
altText
2427
width
2528
height

Diff for: custom.d.ts

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import {AriaAttributes, DOMAttributes} from 'react';
2+
3+
declare module 'react' {
4+
interface HTMLAttributes<T> extends AriaAttributes, DOMAttributes<T> {
5+
fetchpriority?: 'high' | 'low' | 'auto';
6+
}
7+
}

0 commit comments

Comments
 (0)