11

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

Zsolt Meszaros
  • 21,961
  • 19
  • 54
  • 57
Citizen
  • 12,430
  • 26
  • 76
  • 117

4 Answers4

9

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"
  />
)
Gabriel Linassi
  • 429
  • 7
  • 13
2

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

Mika
  • 534
  • 5
  • 14
  • 1
    what is this `useAnimation() ?` – yoyo Apr 25 '21 at 17:53
  • 1
    @yoyo See framer motion library [docs](https://www.framer.com/api/utilities/#useanimation) – Mika Apr 27 '21 at 08:52
  • Can you please declare what is `useAnimation()` – Serkan AKMAN Jun 24 '21 at 10:22
  • 2
    From Next.js [v11.0.2-canary.4](https://github.com/vercel/next.js/releases/tag/v11.0.2-canary.4) onward we can use the [`onLoadingComplete`](https://nextjs.org/docs/api-reference/next/image#onloadingcomplete) prop. – Amos Aug 11 '21 at 14:19
1

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

oldo.nicho
  • 2,149
  • 2
  • 25
  • 39
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/