Fade in Next.js Images on load - example or plugin?

14 replies
Last updated: Mar 15, 2022
hello! someone has an example or plugin to fadein next/images when loads ?
AI Update

Hey there! This is actually a Next.js/React question rather than a Sanity-specific one, but I'm happy to help since many Sanity users work with Next.js!

For fading in Next.js <Image> components when they load, here's a common approach using the onLoad callback (which replaced the deprecated onLoadingComplete):

import Image from 'next/image';
import { useState } from 'react';

function MyImage({ src, alt }) {
  const [isLoaded, setIsLoaded] = useState(false);
  
  return (
    <Image
      src={src}
      alt={alt}
      width={800}
      height={600}
      onLoad={() => setIsLoaded(true)}
      className={`transition-opacity duration-500 ${
        isLoaded ? 'opacity-100' : 'opacity-0'
      }`}
    />
  );
}

With Tailwind CSS (which you mentioned), you can make it even cleaner:

import Image from 'next/image';
import { useState } from 'react';

export default function FadeInImage({ src, alt, ...props }) {
  const [isLoaded, setIsLoaded] = useState(false);
  
  return (
    <div className="relative">
      <Image
        src={src}
        alt={alt}
        onLoad={() => setIsLoaded(true)}
        className={`
          transition-opacity duration-700 ease-in-out
          ${isLoaded ? 'opacity-100' : 'opacity-0'}
        `}
        {...props}
      />
    </div>
  );
}

If you're using Sanity images specifically, you'd typically use @sanity/image-url to generate the URLs:

import imageUrlBuilder from '@sanity/image-url';
import Image from 'next/image';
import { useState } from 'react';

const builder = imageUrlBuilder(sanityClient);

function SanityImage({ image, alt }) {
  const [isLoaded, setIsLoaded] = useState(false);
  
  return (
    <Image
      src={builder.image(image).width(800).url()}
      alt={alt || image.alt || ''}
      width={800}
      height={600}
      onLoad={() => setIsLoaded(true)}
      className={`transition-opacity duration-500 ${
        isLoaded ? 'opacity-100' : 'opacity-0'
      }`}
    />
  );
}

You can adjust the duration-500 (500ms) to whatever timing feels right for your design. Some people prefer duration-700 or duration-300 depending on the feel they want.

Hope this helps! Let me know if you need any clarification on integrating this with your Sanity content.

Show original thread
14 replies
nice! I’ll give it a closer 👀
user E
What was the answer to this?
user M
I make it works using framer-Motion connected with two useState one to trigger when my image is loaded and another useState to trigger when my image is on the viewport.
• framer-motion for the animation job
• react-intersection-observer for the element in viewport true/false
• onLoadingComplete from next/image to set true/false
thanks for the suggestion
user E
🙏
user S

You don't need to use react-intersection-observer for this, take a look at
https://www.framer.com/docs/examples/#scroll-triggered-animations and whileInView and viewport.
Sample:


const bounceVariants: Variants = {
  offscreen: {
    y: 100,
    opacity: 0
  },
  onscreen: {
    y: 0,
    opacity: 1,
    transition: {
      type: "spring",
      bounce: 0.3,
      duration: 1.0
    }
  }
};

const BounceInScroll = ({ children}) => (
  <motion.div
    initial="offscreen"
    whileInView="onscreen"
    viewport={{ once: true, amount: 0.4 }}  
    data-testid="bounceinscroll"
  >
    <motion.div variants={bounceVariants}>{children}</motion.div>
  </motion.div>
);

This is so nice!! way less code to handle 🙌
import { motion } from "framer-motion";

const FadeElement = ({ children, hasBeenLoaded, getIndex }) => {
  return (
    <motion.div
      initial={{ opacity: 0 }}
      whileInView={{ opacity: hasBeenLoaded ? 1 : 0 }}
      transition={{ duration: 0.2, delay: getIndex * 0.1 }}
      viewport={{ once: true }}
    >
      {children}
    </motion.div>
  );
};

export default FadeElement;
What's hasBeenLoaded?
is for when next/image set true to
onLoadingComplete
probably this is pretty messy, but can give you a better perspective, I started to read about react and next just a month ago 👶

const Thumbnails = () => {
  const [loaded, setLoaded] = useState(false);
  return (
    <FadeElement hasBeenLoaded={loaded} getIndex={id}>
      <Image
        src={urlFor(mainImage).url()}
        alt="alt"
        layout="fill"
        loading="lazy"
        objectFit="cover"
        onLoadingComplete={() => setLoaded(true)}
      />
    </FadeElement>
  );
};

export default Thumbnails;
I don't think you need the hasBeenLoaded, you can just animate it when it it comes into scroll view, though
I don't think you need the hasBeenLoaded, you can just animate it when it it comes into scroll view, though
interesting, makes kind of sense! I’ll make a few tests.. my question is what happens if the motion.div is inside the viewport but the image is still loading
I'm no framer expert, you have to experiment and try it out yourself. 😉
Woah thanks so much!

Sanity – Build the way you think, not the way your CMS thinks

Sanity is the developer-first content operating system that gives you complete control. Schema-as-code, GROQ queries, and real-time APIs mean no more workarounds or waiting for deployments. Free to start, scale as you grow.

Was this answer helpful?