Reputation: 12947
I'm using next/image
, which works great, except the actual image loading in is super jarring and there's no animation or fade in. Is there a way to accomplish this? I've tried a ton of things and none of them work.
Here's my code:
<Image
src={source}
alt=""
layout="responsive"
width={750}
height={height}
className="bg-gray-400"
loading="eager"
/>
According to the docs I can use the className
prop, but those are loaded immediately and I can't figure out any way to apply a class after it's loaded.
I also tried onLoad
, and according to this ticket, it isn't supported:
https://github.com/vercel/next.js/issues/20368
Upvotes: 12
Views: 27780
Reputation: 1271
<Image
// ...other props
className="img img--hidden"
onLoadingComplete={(image)=>image.classList.remove("img--hidden")}
/>
CSS:
.img {
opacity: 1;
transition: opacity 3s;
}
.img--hidden {
opacity: 0;
}
Upvotes: 1
Reputation: 551
NextJS now supports placeholder. You can fill the blurDataURL property with the base64 string of the image which you can easily get using the lib plaiceholder on getServerSideProps or getStaticProps. Then to make the transition smoothly you can add transition: 0.3s;
Quick sample:
export const UserInfo: React.FC<TUserInfo> = ({ profile }) => {
return (
<div className="w-24 h-24 rounded-full overflow-hidden">
<Image
src={profile.image}
placeholder="blur"
blurDataURL={profile.blurDataURL}
width="100%"
height="100%"
/>
</div>
);
};
export async function getServerSideProps(props: any) {
const { username } = props.query;
const userProfileByName = `${BASE_URL}/account/user_profile_by_user_name?user_name=${username}`;
const profileResponse = await (await fetch(userProfileByName)).json();
const profile = profileResponse?.result?.data[0];
const { base64 } = await getPlaiceholder(profile.profile_image);
return {
props: {
profile: {
...profile,
blurDataURL: base64,
},
},
};
}
index.css
img {
transition: 0.3s;
}
======== EDIT ==========
If you have the image in the public folder for ex, you don't need to do the above steps, just statically import the asset and add the placeholder type. NextJS will do the rest. Also, make sure to make good use of the size property to load the correct image size for the viewport and use the priority prop for above-the-fold assets. Example:
import NextImage from 'next/image'
import imgSrc from '/public/imgs/awesome-img.png'
return (
...
<NextImage
src={imgSrc}
placeholder='blur'
priority
layout="fill"
sizes="(min-width: 1200px) 33vw, (min-width: 768px) 50vw, 100vw"
/>
)
Upvotes: 10
Reputation: 11
Yes, its possible to capture the event where the actual image loads. I found an answer to this on Reddit and wanted to repost it here for others like me searching for an anwser.
"To get onLoad to work in the NextJS image component you need make sure it's not the 1x1 px they use as placeholder that is the target.
const [imageIsLoaded, setImageIsLoaded] = useState(false)
<Image
width={100}
height={100}
src={'some/src.jpg'}
onLoad={event => {
const target = event.target;
// next/image use an 1x1 px git as placeholder. We only want the onLoad event on the actual image
if (target.src.indexOf('data:image/gif;base64') < 0) {
setImageIsLoaded(true)
}
}}
/>
From there you can just use the imageIsLoaded boolean to do some fadein with something like the Framer Motion library.
Source: https://www.reddit.com/r/nextjs/comments/lwx0j0/fade_in_when_loading_nextimage/
Upvotes: 0
Reputation: 642
I wanted to achieve the same thing and tried to use the onLoad event, therefore. The Image component of nextJs accepts this as prop, so this was my result:
const animationVariants = {
visible: { opacity: 1 },
hidden: { opacity: 0 },
}
const FadeInImage = props => {
const [loaded, setLoaded] = useState(false);
const animationControls = useAnimation();
useEffect(
() => {
if(loaded){
animationControls.start("visible");
}
},
[loaded]
);
return(
<motion.div
initial={"hidden"}
animate={animationControls}
variants={animationVariants}
transition={{ ease: "easeOut", duration: 1 }}
>
<Image
{...p}
onLoad={() => setLoaded(true)}
/>
</motion.div>
);
}
However, the Image does not always fade-in, the onLoad event seems to be triggered too early if the image is not cached already. I suspect this is a bug that will be fixed in future nextJS releases. If someone else finds a solution, please keep me updated!
The solution above however works often, and since onLoad gets triggered every time, it does not break anything.
Edit: This solution uses framer-motion for the animation. This could also be replaced by any other animation library or native CSS transitions
Upvotes: 3
Reputation: 2289
You could try use next-placeholder to achieve this sort of effect
Upvotes: 1