4

I'm building a carousel (slideshow) effect for my website and I have little issue. At every image change I want to make fade effect. So I've added a class with animation for it. And here the problem comes.

This function is firing every 3 sec (setInterval)

let sliderInterval = setInterval(nextImg, 3000);

function nextImg() {
    imgChange(sliderNum + 1);
}

const heroImg = document.querySelector('.hero__image');

function imgChange(x) {
    heroImg.classList.remove("fade");
    sliderNum = (x + imgLocations.length) % imgLocations.length;
    heroImg.src = imgLocations[sliderNum];
    heroImg.classList.add("fade");
}

Fade effect:

.fade {
    animation: fade 1.5s ease-in-out;
}

@keyframes fade {
    from {opacity: .4}
    to {opacity: 1}
}

<div class="hero">
<img class="hero__image" src="photo1">
</div>

It works only for first image switch. Altough at the start of function it shall remove the class fade I see in function that it stays there in element and won't gone. It doesn't matter if I try to put this effect on hero section or img within it.

Travis J
  • 81,153
  • 41
  • 202
  • 273
muszynov
  • 319
  • 5
  • 22

2 Answers2

6

The problem is that css animations with keyframes will by default only run once. It is possible to alter the amount of times they run, but that will run them constantly in a loop which is undesirable.

Instead, what needs to happen is the animation needs to be reset. In order to do this, the element needs to have its animation name removed (fade in this case). This can be done with animation-name: none;, however, the rule needs to be placed on the element when fade is removed. Note the change to the selector to make the fade animation name take precedence when applied.

On top of this, it is important to note that if you remove and add the same class in a function, due to the way that browsers work, nothing will happen. In order for the browser to recognize any changes made, a page repaint must occur (here is a list of what makes that happen). In order to force the page to repaint, I chose to use offsetHeight, which is why you see heroImg.offsetHeight used in the code (note that it only needs to be read, it doesn't have to be used or assigned).

I mocked your image with a div for convenience.

let sliderInterval = setInterval(imgChange, 3000);

const heroImg = document.querySelector('.imgMock');

function imgChange() {
    heroImg.classList.remove("fade");
    heroImg.offsetHeight; // force repaint to recognize `animation-name: none;`
    heroImg.classList.add("fade");
}
.imgMock.fade {
    animation: fade 1.5s ease-in;
}

@keyframes fade {
    from {opacity: .4}
    to {opacity: 1}
}

.imgMock {
    height:50px;
    width:50px;
    background-color:black;
    animation-name: none;
}
<div class="hero">
 <div class="imgMock"></div>
</div>
Travis J
  • 81,153
  • 41
  • 202
  • 273
  • Thanks! That's kinda solution. But now for whole time of image duration (3 seconds) the animations is firing every 1.5 s. So final goal would be, how to set the animation working only for the image transition? So it will change image with animation and fire at the next slide change? – muszynov May 30 '18 at 19:59
  • 1
    @icelandico - Sorry, you were correct the infinite setting was inaccurate and did not properly maintain the animation durations as a result. Please see my edit, and an improved more accurate way of handling this scenario. I hope you find it satisfactory. – Travis J May 30 '18 at 21:17
0

I know that this is an old problem.

But I just encountered something similar, where I had to add different classes per key press, all of wich where playing esentially the same animation but with different directions.

The solution presented by Travis J worked great but when pressing really quickly, even the same key it would confuse the browsers engine.

Cleaning the classList array solved my issue:

let animateOnKeyup = function (className) {
 drone.classList.remove(...drone.classList)
 drone.offsetHeight; // Repaint DOM in order to replay animation.
 drone.classList.add(className)
}
Fofo
  • 1
  • thank you for your answer! Personally, it feels like your answer addresses a situation that is a bit too far from the proposed question here. I will review your answer as "looks OK" as I believe you are addressing the question with a unique answer, but I suggest you consider writiting a new question with your own scenario and a fully fledged solution and a link to this question as a reference. I will leave it up to you to make that decision though, as I am not sure if your situation is actually unique enough to justify a new question or not, so I leave you with this comment :) – Miss Skooter Apr 11 '23 at 14:23
  • Also, as a general rule of thumb, always try your best to make sure your question is unique and elaborate as much as possible on your specific issue. When possible, provide [a minimal reproducible example](https://stackoverflow.com/help/minimal-reproducible-example). When writing an answer, make sure to be as detailed as possible and justify any ambiguity in your answer with references when applicable (if you decide to post a new question) – Miss Skooter Apr 11 '23 at 14:38