Automatically change image based on breakpoint in NextJS application/XMCloud Pages editor with Tailwind css
This post is more of a note where I follow an iterative approach to build the correct code for my requirement. So, read it till the end instead of pulling out code from middle and finding you are on the wrong side!
This could be useful if you want to change image based on break point. I've cloned the existing Image component and created a ResponsiveImage component. This component is same as Image.tsx. This file uses the NextImage component and understandably doesn't have the capability to display different images based on breakpoint and that is what this note is about. Here is the original code highlighted for reference:
With that context, In the new ResponsiveImage.tsx file, i added a method named ArtDirectedImage:
//////
///https://www.dhiwise.com/post/how-to-implement-next.js-responsive-image-features | |
const ArtDirectedImage = (props: any) => { | |
return ( | |
<picture> | |
<source media="(max-width: 768px)" srcSet={props.mobileSrc} /> | |
<source media="(max-width: 1200px)" srcSet={props.tabletSrc} /> | |
<Image | |
src={props.desktopSrc} | |
alt="Descriptive text for the image" | |
width={500} | |
height={300} | |
layout="responsive" | |
/> | |
</picture> | |
); | |
} |
/////
Since a new template is created as part of cloning the Image rendering, I modified the template to accommodate the new fields in the new ResponsiveImage template as follows:
Changes on top of responsiveimage.tsx file to accommodate the new fields - MobileImage and TabletImage:
/////////
interface Fields { | |
MobileImage: ImageField & { metadata?: Record<string, unknown> }; | |
TabletImage: ImageField & { metadata?: Record<string, unknown> }; | |
Image: ImageField & { metadata?: Record<string, unknown> }; | |
ImageCaption: Field<string>; | |
TargetUrl: LinkField; | |
} |
///////
Also, ensure Image is imported on top:
The Default method must return this body that accommodates image change on-the-fly:
////////
<div className={`component image ${props.params.styles}`} id={id ? id : undefined}> | |
<div className="component-content"> | |
{sitecoreContext.pageState === LayoutServicePageState.Edit || !props.fields.TargetUrl?.value?.href ? ( | |
<ArtDirectedImage desktopSrc={desktopImg} tabletSrc={tabletImg} mobileSrc={mobileImg} /> | |
) : ( | |
<JssLink field={props.fields.TargetUrl}> | |
<ArtDirectedImage desktopSrc={desktopImg} tabletSrc={tabletImg} mobileSrc={mobileImg} /> | |
</JssLink> | |
)} | |
<Text | |
tag="span" | |
className="image-caption field-imagecaption" | |
field={props.fields.ImageCaption} | |
/> | |
</div> | |
</div> |
///////
End-result in pages.sitecorecloud.io:
{sitecoreContext.pageState === LayoutServicePageState.Edit ? ( | |
<> | |
<JssImage field={props.fields.Image} /> | |
<JssImage field={props.fields.MobileImage} /> | |
<JssImage field={props.fields.TabletImage} /> | |
</> | |
) : ( | |
<JssLink field={props.fields.TargetUrl}> | |
<> | |
<ArtDirectedImage desktopSrc={desktopImg} tabletSrc={tabletImg} mobileSrc={mobileImg} /> | |
</JssLink> | |
</> | |
)} |
Then, you have this capability to select separate images in pages editor:
<div> | |
<div className="visible md:hidden">Mobile image</div> | |
<div className="hidden md:block lg:hidden">Tablet image</div> | |
<div className="hidden md:hidden lg:block">Desktop image</div> | |
</div> |
<> | |
<JssImage className="visible md:hidden" field={props.fields.MobileImage} unoptimized /> | |
<JssImage className="hidden md:block lg:hidden" field={props.fields.TabletImage} unoptimized /> | |
<JssImage className="hidden md:hidden lg:block" field={props.fields.Image} unoptimized /> | |
</> |
.img-aspectratio-916 { | |
aspect-ratio: 9 / 16; | |
} | |
.img-aspectratio-169 { | |
aspect-ratio: 16 / 9; | |
} | |
.img-aspectratio-11 { | |
aspect-ratio: 1 / 1; | |
} | |
.img-aspectratio-21 { | |
aspect-ratio: 2 / 1; | |
} | |
.img-aspectratio-12 { | |
aspect-ratio: 1 / 2; | |
} | |
.img-aspectratio-23 { | |
aspect-ratio: 2 / 3; | |
} | |
.img-aspectratio-32 { | |
aspect-ratio: 3 / 2; | |
} | |
.img-aspectratio-43 { | |
aspect-ratio: 4 / 3; | |
} | |
.img-aspectratio-34 { | |
aspect-ratio: 3 / 4; | |
} | |
.img-object-cover { | |
object-fit: cover; | |
} |
import { | |
Field, | |
ImageField, | |
NextImage as JssImage, | |
LinkField, | |
Text | |
} from '@sitecore-jss/sitecore-jss-nextjs'; | |
interface Fields { | |
MobileAspectRatio: Field<string>; | |
TabletAspectRatio: Field<string>; | |
DesktopAspectRatio: Field<string>; | |
MobileImage: ImageField & { metadata?: Record<string, unknown> }; | |
TabletImage: ImageField & { metadata?: Record<string, unknown> }; | |
Image: ImageField & { metadata?: Record<string, unknown> }; | |
ImageCaption: Field<string>; | |
TargetUrl: LinkField; | |
} | |
interface ImageProps { | |
params: Record<string, string>; | |
fields: Fields; | |
} | |
const ImageDefault = (props: ImageProps): JSX.Element => ( | |
<div className={`component image ${props.params.styles}`.trimEnd()}> | |
<div className="component-content"> | |
<span className="is-empty-hint">Image</span> | |
</div> | |
</div> | |
); | |
export const Default = (props: ImageProps): JSX.Element => { | |
if (props.fields) { | |
const id = props.params.RenderingIdentifier; | |
const mobileAspectRatioClass = (props.fields.MobileAspectRatio.value) ? `img-aspectratio-${props.fields.MobileAspectRatio.value} img-object-cover`.replace('x', '') : "" | |
const tabletAspectRatioClass = (props.fields.TabletAspectRatio.value) ? `img-aspectratio-${props.fields.TabletAspectRatio.value} img-object-cover`.replace('x', '') : "" | |
const desktopAspectRatioClass = (props.fields.DesktopAspectRatio.value) ? `img-aspectratio-${props.fields.DesktopAspectRatio.value} img-object-cover`.replace('x', '') : "" | |
return ( | |
<div className={`component image ${props.params.styles}`} id={id ? id : undefined}> | |
<div className="component-content"> | |
{( | |
<> | |
<JssImage className={`visible md:hidden ${mobileAspectRatioClass}`} field={props.fields.MobileImage} /> | |
<JssImage className={`hidden md:block lg:hidden ${tabletAspectRatioClass}`} field={props.fields.TabletImage} /> | |
<JssImage className={`hidden md:hidden lg:block ${desktopAspectRatioClass}`} field={props.fields.Image} /> | |
</> | |
)} | |
<Text | |
tag="span" | |
className="image-caption field-imagecaption" | |
field={props.fields.ImageCaption} | |
/> | |
</div> | |
</div> | |
); | |
} | |
return <ImageDefault {...props} />; | |
}; |
Comments
Post a Comment