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

Is it possible to use Ajax to prompt a pop-up window for basic authentication when logging in?

While attempting to access the reed.co.uk REST web API in order to retrieve all related jobs, I am encountering an issue. Despite passing my username and password, a popup window keeps appearing when I call the URL. The alert message displayed reads: i ...

Seeking guidance on creating an uncomplicated CSS tooltip

Can anyone help me troubleshoot why my CSS tooltip isn't working correctly? I've been trying to create a simple method for tooltips, but they are displaying inconsistently across different browsers. In Firefox, the tooltips appear in random locat ...

Exploring VueJs 3's Composition API with Jest: Testing the emission of input component events

I need help testing the event emitting functionality of a VueJs 3 input component. Below is my current code: TextInput <template> <input v-model="input" /> </template> <script> import { watch } from '@vue/composition-api&ap ...

Tips for modifying the language of an Angular Application's OneTrust Cookie Banner

I'm currently developing an Angular application and utilizing OneTrust for managing cookie consent. The issue I'm encountering is that while the rest of the components on the login page are properly translated into the target language, the OneTru ...

Applying Tailwind styles to dynamically inserted child nodes within a parent div

Currently, I am in the process of transitioning my website stacktips.com from using Bootstrap to Tailwind CSS. While initially, it seemed like a simple task and I was able to replace all the static HTML with tailwind classes successfully. However, now I ha ...

What could be the reason for the body background color refusing to change

I'm currently working on a JavaScript calculator project and I'm using this code snippet from codepen. The issue I'm facing is that despite setting the body background color to black, it still appears white. Could there be a spacing problem ...

Having trouble retrieving data from the json file

Using Ajax to obtain a JSON update: $(document).ready(function(){ $('form').submit(function(event){ event.preventDefault(); var form = JSON.stringify($('form').serializeArray()); $.ajax ({ u ...

The "smiley" character added to the information during an Ajax call

Encountering an unusual issue. A colon (:) character is being appended to the JSON data sent to the server via AJAX request. https://example.com/image1.png The colon character seems to appear after sending the JSON, but it does not show up when inspectin ...

What is causing my reusable component to malfunction when used in conjunction with the "setInterval" function?

I have created a custom <Loader /> component in which I can pass the text it is rendering and the speed as props. The code for the component is shown below: class Loader extends Component { constructor(props) { super(props); this.state = { ...

Maintain the div's aspect ratio by adjusting its width according to its height

I am seeking a way to maintain the width of the .center div in relation to its height, following a 4:3 aspect ratio (4 units wide for every 3 units high). The height of the div should be set to 100%, and I also need to align the div horizontally at the cen ...

What is the best method to activate a button only when the appropriate radio buttons have been chosen?

Just dipping my toes into the world of javascript. I've created a form with a set of "Yes/No" radio buttons, and I attempted to create a function that will disable the "Submit Form" button if any of the following conditions are met: If one or more r ...

Background cutting off right side of HTML website

While updating my supervisor's university website, I noticed a problem with the responsiveness. When resizing the browser window, a horizontal scroll bar would appear and the right side of the website would get covered by the background. On mobile dev ...

Issues have been identified with the functionality of the Am charts v3 XY in conjunction with a

I'm currently working on a project with angularJS and utilizing the npm package amcharts3 "^3.21.15". I've encountered a minor issue regarding the logarithmic scale in my XY chart. Below is my chart without the logarithmic scale: View working ch ...

Using a single package manager for both backend and frontend development - is it possible? (Yarn/NPM)

Before, I relied on NPM for server-side tasks and Bower for frontend. NPM would install packages in the node_modules/ directory, while a .bowerrc file directed package installations to public/lib. Recently, I've made the switch to Yarn from NPM, and ...

Vue automatically refreshes momentjs dates prior to making changes to the array

I am dealing with a situation where my child component receives data from its parent and, upon button click, sends an event to the parent via an event bus. Upon receiving the event, I trigger a method that fetches data using a Swagger client. The goal is ...

The Input element's onChange event is not functioning as expected

I am experiencing some issues with my code. I am trying to dynamically change the background color based on user input, but I am struggling to make it work. It seems like a simple task, so I must be missing something. Below is my HTML markup and jQuery: ...

Share a URL and display small previews from it - php

Have you ever noticed that on certain websites, such as Facebook, when you post a link it automatically displays thumbnails from the linked website? Like in the image below: Ever wondered how to achieve that effect? ...

What purpose does the "io" cookie serve in Socket.IO?

Can someone explain the purpose of Socket.IO using the io cookie as a session cookie? I understand that it can be disabled, but I couldn't find any information on it in the documentation. Why is it turned on by default and what consequences would ther ...

A guide on incorporating unique font weights into Material UI

Looking to customize the Material theme by incorporating my own font and adjusting the font weights/sizes for the Typography components. I am attempting to set 100/200/300/400/500/600/700 as options for each specific typography variant, but it seems that o ...

Choose a Different Value for Another HTML Element's Class

Is there a way to preselect an option on another page before it loads? Consider two pages, A and B. If a user clicks a button on page A, I want the default option on page B to be changed to "something" before redirecting them. How can this be achieved s ...