JavaScript and CSS failing to implement lazy loading with fade-in effect

Is there a way to add the fade-in animation to an image when it is loaded with JavaScript or CSS? Currently, the code I have only fades in the image once it is 25% visible in the viewport. How can I modify it to include the fade effect upon image load?

let options = {
  root: null,
  rootMargin: '0px',
  threshold: 0.25 // Visible by 25%
};

let callback = (entries, observer) => {
  entries.forEach(entry => {
    if (entry.isIntersecting &&
      entry.target.className === 'lazyImage') {
      let imageUrl = entry.target.getAttribute('data-img');
      if (imageUrl) {
        entry.target.src = imageUrl;
        observer.unobserve(entry.target);
      }
    }
  });
}

let observer = new IntersectionObserver(callback, options)
observer.observe(document.querySelector('#lazyImageId'))
.lazyImage {
  height: 100%;
  width: 100%;
  position: absolute;
  top: 0px;
  left: 0px;
  object-fit: cover;
  animation-duration: 1s;
  animation-fill-mode: both;
  animation-name: fadeIn;
}

@keyframes fadeIn {
  0% {
    opacity: 0;
  }
  100% {
    opacity: 1;
  }
}
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">
<div class="col-md-6 col-sm-12 full-height">
  <img id="lazyImageId" class="lazyImage" data-img="./img/dog.jpeg" alt="" loading="lazy">
</div>

Answer №1

If it's clear what you're asking, it seems like you want to trigger the fade-in animation only after the image has loaded completely.

When checking if an image has finished loading, I typically rely on two methods:

  1. The HTMLImageElement complete attribute is a useful read-only attribute that indicates whether the image has fully loaded. However, continuously checking this attribute until it turns true before running the animation can be cumbersome.

  2. The onload EventHandler triggers once the image has been entirely loaded. This allows us to wait for slow-loading images to finish loading before applying our animation effects.

By combining both these methods, your code will cater to scenarios where the image is already loaded or still in the process of loading while the script executes.

I slightly modified your callback function to incorporate the use of .complete and the .onload event handler

let callback = (entries, observer) => {
    entries.forEach(entry => {
        if(entry.isIntersecting && entry.target.className === 'lazyImage') {
              // Storing the img element in a variable since we'll need it
              let imgEle = entry.target;
              let imageUrl = imgEle.getAttribute('data-img');
            
              if(imageUrl) {
                  imgEle.src = imageUrl;
                
                  // Check if the image has already loaded, add our animation class if it has
                  if (imgEle.complete) {
                      imgEle.classList.add('animate');
                  } else {
                      // If the image hasn't fully loaded yet, add a listener to apply the class once it's done
                      imgEle.onload = () => {
                          imgEle.classList.add('animate');
                      };
                  }
  
                  observer.unobserve(entry.target);
              }
          }
      });
}

To handle the actual animation, I created an .animate class with the necessary CSS animations, which I then add to the image elements upon completion of loading.

.animate {
    animation-duration: 1s;
    animation-fill-mode: both;
    animation-name: fadeIn;
}

You can also access a jsFiddle containing all the above content for testing purposes.

If this doesn't fully address your query, feel free to reach out for further adjustments or clarifications.

Answer №2

My approach to this situation would involve assigning a class to the img element once it becomes visible, either .complete or onload:

if (imageUrl) {
   entry.target.src = imageUrl;
   observer.unobserve(entry.target);

   if (entry.target.complete) {
        entry.target.classList.add("visible");
      } else {
        entry.target.onload = function() {
        entry.target.classList.add("visible");
      }
    }
 }

[Above edited with onload function mentioned by @Amir]

To ensure that your img remains hidden until the animation is triggered, set its CSS property to opacity: 0;. Here's an example of CSS implementation:

.full-height {
  /* height for demonstration effect */
  height: 2000px;
}

.lazyImage {
  height: 100%;
  width: 100%;
  position: absolute;
  /* top position for effect*/
  top: 1000px;
  left: 0px;
  object-fit: cover;
  opacity: 0;
}

.lazyImage.visible {
  animation: fadeIn 1s ease forwards;
}

@keyframes fadeIn {
  0% {
    opacity: 0;
  }

  100% {
    opacity: 1;
  }
}

Once the img has the "visible" class, it can start animating.

Keep in mind that since the img tag is present in the DOM before the src loads, it may execute its animation before becoming visible.

.full-height {
  /* height for demonstration effect */
  height: 2000x;
}

.lazyImage {
  height: 100%;
  width: 100%;
  position: absolute;
  /* top position for effect*/
  top: 1000px;
  left: 0px;
  object-fit: cover;
  opacity: 0;
}

.lazyImage.visible {
  animation: fadeIn .5s ease forwards;
}

@keyframes fadeIn {
  0% {
    opacity: 0;
  }

  100% {
    opacity: 1;
  }
}
<div class="col-md-6 col-sm-12 full-height">
  <h1>Scroll Down</h1>
  <img id="lazyImageId" class="lazyImage" data-img="https://images.unsplash.com/photo-1620125587503-54b5365229de?ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&ixlib=rb-1.2.1&auto=format&fit=crop&w=2550&q=80" alt="" loading="lazy">
</div>
<script>
  let options = {
    root: null,
    rootMargin: '0px',
    threshold: 0.25 // Visible by 25%
  };
  let callback = (entries, observer) => {
    entries.forEach(entry => {
      if (entry.isIntersecting &&
        entry.target.className === 'lazyImage') {
        let imageUrl = entry.target.getAttribute('data-img');
        if (imageUrl) {
          entry.target.src = imageUrl;
          observer.unobserve(entry.target);
          if (entry.target.complete) {
            entry.target.classList.add("visible");
          } else {
            entry.target.onload = function() {
              entry.target.classList.add("visible");
            }
          }
        }
      }
    });
  }
  let observer = new IntersectionObserver(callback, options)
  observer.observe(document.querySelector('#lazyImageId'))
</script>

Codepen

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Experiencing issues with transferring JSON response from Axios to a data object causing errors

When I try to assign a JSON response to an empty data object to display search results, I encounter a typeerror: arr.slice is not a function error. However, if I directly add the JSON to the "schools" data object, the error does not occur. It seems like th ...

What is causing Mocha.js to be unable to locate the module?

Having trouble with mocha.js not being able to locate my path! Here's the directory structure I have set up for my node course project: ///////Root --package.json --node_modules/ --playground --server -server.js -db -models ...

Creating a hash map for an iteration through a jQuery collection

Using the jQuery .each function, I traverse through various div elements. By using console.log, the following output is obtained: 0.24 240, 0.1 100, 0.24 240, 0.24 240, The first number on each line represents a factor and the last number is the res ...

Is it possible to prioritize loading JavaScript before images on a webpage?

My goal is to prioritize loading the js first and then the images for a specific reason. I want the blue rollover effect to be applied immediately upon loading. As the number of images on this page will eventually double, this could potentially become a la ...

Having trouble launching the Socket.io Chat Demo? Encounter an issue with the error message at ..//

Hello, I am a novice in the world of programming and recently attempted to run the socket.io chat demo. Unfortunately, I encountered an error message at line 5 stating that it cannot find ('../..'). Can someone please shed some light on why this ...

Traverse each child element sequentially using setTimeout or a delay function

Looking to dynamically apply a CSS filter to a list of divs with a delay between each iteration. Here are my attempted solutions: $(this).children().each(function() { $(this).delay(5000).css("-webkit-filter", "brightness(2)"); }); And this alternativ ...

Video background mute feature malfunctioning

I've searched through numerous resources and reviewed various solutions for this issue, yet I still haven't been able to figure it out. Could someone please guide me on how to mute my youtube embedded video? P.s. I am a beginner when it comes to ...

Typescript HashMap implementation with Lists as values

Currently delving into TypeScript, I am attempting to generate a collection in a manner akin to the following Java example: HashMap<String, List<String>> hashMap = new HashMap<String,List<String>>(); Struggling to locate any releva ...

Is there a way to display the inbox area upon completion of the document loading process?

I'm currently working on creating an inbox feature for my personal portfolio, mostly as a learning exercise rather than for any practical use. However, I've run into an issue where all emails are being displayed by default when the page loads wit ...

Issue with showing error messages in view when using ejs templates

I am a beginner with node.js and I'm struggling to show error messages in the view using ejs templates. I want to display This user already exists. Here is my code: node.js router.post('/signup', (req, res) => { var username = req. ...

Discovering the final pattern using regular expressions

var inputString = 'a.b.c.d.e.f'; var pattern = inputString.match(/([^\.]+)\.[^\.]+$/)[1]; console.log(pattern); I have successfully implemented the code using regular expressions, but I am open to exploring more efficient solution ...

Tips on customizing the appearance of the "time" input type

Are there any resources that explain how to style the input type "time" completely? I have been searching but unable to find a comprehensive guide with all the necessary style attributes! The only example I came across is: input[type="time"]{ /**styl ...

Tips for repairing a button using a JavaScript function in an HTML document

I am currently working on extracting titles from body text. To achieve this, I have created a button and linked my function to it. The issue I am facing is that when I click on the button, it disappears from its original position. I intend to keep it in pl ...

Postpone the execution of the toggleClass function

I have a tab that, when clicked, fades in to reveal content loaded with AJAX. However, the content loads immediately upon clicking the button. I attempted to use toggleClass with delay, but it was unsuccessful. How can I effectively delay the loading of t ...

What is the formula to determine the precise hue-rotate needed for producing a particular shade?

I'm looking to change the color of a white background image in a div to match the main theme color. I know I can use filters like sepia(), saturate(10000%), and hue-rotate() to achieve this, but is there a way to calculate these values beforehand? Sin ...

What are some ways to ensure that the promise from postgres is fulfilled before moving forward with my code execution?

I am currently developing a Node-js application that requires retrieving information from the database before making another database call. However, I am facing an issue where the first check is not always resolved before proceeding to the next step. I hav ...

Dividing the code into a function and invoking it does not produce the desired outcome

Everything seemed to be working perfectly until I attempted to encapsulate the code into a function and call it within my expression. Unfortunately, this approach did not yield the desired results. Functional code: render: function() { return ( ...

The functionality of React-router-dom protected routes seems to be malfunctioning

Understanding Protected Routes in React.js: While looking at the implementation of protected routes, you may notice that 'false' is being directly used in the if statement. However, even with this condition, the page is still accessible. Why doe ...

Tips for postponing a function's execution in order to ensure it has completely loaded

I am facing an issue with a dynamic page that is populated by an ajax call. Here is the current code snippet: function loadPage() { var container = document.querySelector(".container"); var xhr = new XMLHttpRequest(); xhr.open('GET ...

Webkit causing complications with straightforward CSS3 animation

I'm currently working on a website and you can check it out here: Unfortunately, I've encountered an issue where the animations aren't triggering specifically on webkit browsers like Chrome and Safari: @-webkit-keyframes slideright{ 0% {tr ...