javascriptcsscss-animationskeyframecross-fade

How to use @keyframes for cross-fade gallery of images in css?


I have a fiddle (Fiddle A) in which cross-fade gallery of images happen for 2 images (2 tiles). Here is the snippets of html/css which I have used.

<div class="featured-block" style="display:flex; justify-content: center;">
  <a href="https://www.google.com/" class="featured-block__item cf">
    <div class="featured-block__item-inner">
      <figure class="featured-block__image img-fit" itemprop="image" itemscope="" itemtype="http://schema.org/ImageObject">
        <img class="default-opacity" src="https://i.imgur.com/EUqZ1Er.png" data-fallback-img="https://i.imgur.com/EUqZ1Er.png" alt="Outburst">
      </figure>
    </div>
  </a>
</div>

Here are the @keyframes which I have used 2 images (2 tiles) for the above html:

@keyframes cf4FadeInOut {
    0% {
        opacity: 0;
    }
    20% {
        opacity: 1;
        z-index: 999;
    }
    33% {
        opacity: 1;
    }
    53% {
        opacity: 0;
        z-index: 1;
    }
    100% {
        opacity: 0;
    }
}

The above css-animation in Fiddle A is working perfectly fine(which is exactly what I want) when there are 2 tiles (2 images).

Problem Statement:

The above fiddle (Fiddle A) is working perfectly fine for 2 images. I want the same css-animation/cross-fade gallery of images happens when there 3 and 4 images.

Here is the fiddle for 4 images(4 tiles) https://jsfiddle.net/zwjt8qko/1/embedded/result (Fiddle B)

Here is the fiddle for 3 images(3 tiles) https://jsfiddle.net/f6gr7kL1/embedded/result (Fiddle C)

I am wondering what changes I should make in the keyframes in the Fiddle B (4 images) and Fiddle C (3 images) above so that the same css-animation/cross-fade happens which is happening in Fiddle A right now.

I am open to a JavaScript solution as well.


Solution

  • JavaScript method

    Basic approach:

    1. In your CSS, all pictures default to opacity: 0.
    2. In your HTML, set a class name on one of the pictures that changes that picture's opacity to 1.
    3. In JavaScript, periodically toggle that class throughout the pictures.
    4. Don't even bother with keyframes and just do our delaying in JavaScript directly. This lets you more easily modify how long you want different parts of the pulsing animation to last instead of having to calculate the percentage of the total animation-duration like you'd have to with keyframes.

    const pics = document.querySelectorAll('.pic');
    const lastPic = pics.length - 1;
    const transitionDuration = 800; // matches CSS
    const transitionDelay = 3000; // up to you
    const totalDelay = transitionDuration + transitionDelay;
    const intervalDelay = (transitionDuration * 2) + transitionDelay; // time to fade out + time to fade in + time to stay active
    
    function toggleClass() {
      const activePic = document.querySelector('.pic.active');
      const activeIndex = Array.prototype.indexOf.call(pics, activePic);
      const nextIndex = activeIndex === lastPic ? 0 : activeIndex + 1;
      const nextPic = pics[nextIndex];
    
      setTimeout(() => activePic.classList.remove('active'), transitionDelay);
      setTimeout(() => nextPic.classList.add('active'), totalDelay);
    }
    
    setInterval(toggleClass, intervalDelay);
    .wrapper {
      width: 400px;
      height: 300px;
      position: relative;
    }
    .pic {
      position: absolute;
      top: 0;
      left: 0;
      width: 100%;
      opacity: 0;
      transition: opacity 800ms ease; /* immediately start fading out when active class is lost */
    }
    .pic.active {
      opacity: 1;
    }
    <div class="wrapper">
      <img class="pic active" src="https://via.placeholder.com/400x300?text=picture%201" alt="">
      <img class="pic" src="https://via.placeholder.com/400x300?text=picture%202" alt="">
      <img class="pic" src="https://via.placeholder.com/400x300?text=picture%203" alt="">
      <img class="pic" src="https://via.placeholder.com/400x300?text=picture%204" alt="">
    </div>

    Keyframes method

    I won't go into full detail here, but it would probably look something like this:

    @keyframes pulse1 {
      0% {
        opacity: 1;
      }
      20% {
        opacity: 0;
      }
    }
    
    @keyframes pulse2 {
      0% {
        opacity: 0;
      }
      25% {
        opacity: 1;
      }
      45% {
        opacity: 0;
      }
    }
    
    @keyframes pulse3 {
      0% {
        opacity: 0;
      }
      50% {
        opacity: 1;
      }
      70% {
        opacity: 0;
      }
    }
    
    @keyframes pulse4 {
      0% {
        opacity: 0;
      }
      75% {
        opacity: 1;
      }
    }
    

    Note that we don't even toggle the z-index because there's no point: only one of them is ever visible at a time. Just position them all on top of each other from the start, and their z-index won't matter.

    (I don't think the z-index portion of the animation you have in your question is even doing anything because z-index isn't animatable.)