12

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

5 Answers 5

10

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"
  />
)
Sign up to request clarification or add additional context in comments.

Comments

3

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

4 Comments

what is this useAnimation() ?
@yoyo See framer motion library docs
Can you please declare what is useAnimation()
From Next.js v11.0.2-canary.4 onward we can use the onLoadingComplete prop.
1

You could try use next-placeholder to achieve this sort of effect

1 Comment

Not sure why. I didn't think it faded in?
1
   <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;
}

1 Comment

smart, this worked for me! Using tailwind I did image.classList.remove("opacity-0")
0

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/

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.