Offir
Offir

Reputation: 3491

Slick Slider with ken-burns-out image jumps bug

After the slide animation (scale(1.3) to scale(1)) finishes and moving back to the slide I get this annoying jump.

I want that each active slide will be already in scale(1.3) without the jump.

  $('.home-slider').slick({
     slidesToScroll: 1,
        slidesToShow: 1,
        //arrows: true,
        autoplay: true,
        autoplaySpeed: 7000,
        dots: false
  })
body,
html {
  height: 100%;
  background: #333;
  font-family: 'Roboto', sans-serif;
}

.slideshow {
  position: relative;
  z-index: 1;
  height: 100%;
  max-width: 700px;
  margin: 50px auto;
}

.slideshow .item {
  height: 100%;
  position: relative;
  z-index: 1;
}
.slideshow .item img {
  width: 100%;
  
}
.slideshow .item.slick-active img {
  -webkit-animation: ken-burns-out 8s 1 ease-in-out forwards;
  animation: ken-burns-out 8s 1 ease-in-out forwards;
}


/*//The animation: from 1.3 scale to 1*/
@-webkit-keyframes ken-burns-out {
    0% {
        -webkit-transform: scale(1.3);
        transform: scale(1.3)
    }

    to {
        -webkit-transform: scale(1);
        transform: scale(1)
    }
}

@keyframes ken-burns-out {
    0% {
        -webkit-transform: scale(1.3);
        transform: scale(1.3)
    }

    to {
        -webkit-transform: scale(1);
        transform: scale(1)
    }
}
<link href="https://cdnjs.cloudflare.com/ajax/libs/slick-carousel/1.6.0/slick-theme.min.css" rel="stylesheet"/>
<link href="https://cdnjs.cloudflare.com/ajax/libs/slick-carousel/1.6.0/slick.min.css" rel="stylesheet"/>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/2.2.4/jquery.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/jquery-easing/1.3/jquery.easing.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/slick-carousel/1.6.0/slick.min.js"></script>





<div class="slideshow">
  <div class="home-slider">
    <div class="item">
      <img src="https://images.unsplash.com/photo-1532386236358-a33d8a9434e3?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=978&q=80" />
    </div>
    <div class="item">
      <img src="https://images.unsplash.com/photo-1518791841217-8f162f1e1131?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=1050&q=80" />
    </div>
  </div>
</div>

Upvotes: 3

Views: 2527

Answers (2)

Don
Don

Reputation: 4167

Your images are starting with a scale of 1, then when they become active the animation starts them off at 1.3 which is what causes the jump. What you need to do is start all the images off at a scale(1.3) by adding this to the .item img css definition. Doing only that causes the images to overflow onto one another. To combat that make sure the contents of each .item don't overflow by adding overflow: hidden which results in:

EDIT

Which results in code which still doesn't quite work correctly. We still need everything from the previous answer but we end up with the opposite issue where the images jump to the fully zoomed size when they're switched off.

This became a bit of a rabbit hole, but there is an animation-play-state property in css which we can use to pause the animation at the state it is in when an item becomes inactive, so I made the animation play on every .item element, which means that when it's scrolled away and the active class is lost, it will pause the animation where it is.

This almost gets us to the desired result, but then when an image is brought back into view, it resumes from where it has left off. It turns out there isn't a pretty way to get a css animation to restart, so what I'm doing is I've bound to the slide change event for slick and when a slide has changed (which I believe guarantees only the active slide is visible) I reset the .item animation. In order to do this in a way which doesn't interrupt the active slide I've added a class .animated which I can remove, then add back later in a setTimeout. The delay is needed in order for the animation to restart.

You'd hope that was the end, but you'd be wrong. THEN Slick considers a started swipe, which then lands back on the same image to be a slide change ("change") so I added a current slide variable to keep track of which slide we are on and only reset the .animated class if the slide did actually change. Important Note: in a real project I would wrap this currentSlide in some sort of class/object to track the state of an individual slideshow, because as it stands you couldn't have more than one slideshow on a single page without them interfering. But I wasn't going to write all of that for a simple example.

After all of this, we end up with slightly different CSS and a bit more JS to get a working result:

$('.home-slider').slick({
  slidesToScroll: 1,
  slidesToShow: 1,
  //arrows: true,
  autoplay: true,
  autoplaySpeed: 7000,
  dots: false
});
var currentSlide = 0;
$('.home-slider').on('afterChange', function(event, slick, newSlide){
  var items = $('.home-slider .item');
  if(currentSlide != newSlide){
    currentSlide = newSlide;
    items.removeClass('animated');
    setTimeout(function(){
      items.addClass('animated');
    });
  }
});
body,
html {
  height: 100%;
  background: #333;
  font-family: 'Roboto', sans-serif;
}

.slideshow {
  position: relative;
  z-index: 1;
  height: 100%;
  max-width: 700px;
  margin: 50px auto;
}

.slideshow .item {
  height: 100%;
  position: relative;
  overflow: hidden;
}
.slideshow .item img {
  width: 100%;
  -webkit-transform: scale(1.3);
  transform: scale(1.3);
}
.slideshow .item.animated img {
  -webkit-animation: ken-burns-out 8s 1 ease-in-out forwards;
  animation: ken-burns-out 8s 1 ease-in-out forwards;
  animation-play-state: paused;
}
.slideshow .item.slick-active img {
  animation-play-state: running;
}


/*//The animation: from 1.3 scale to 1*/
@-webkit-keyframes ken-burns-out {
    0% {
        -webkit-transform: scale(1.3);
        transform: scale(1.3);
    }

    to {
        -webkit-transform: scale(1);
        transform: scale(1)
    }
}

@keyframes ken-burns-out {
    0% {
        -webkit-transform: scale(1.3);
        transform: scale(1.3)
    }

    to {
        -webkit-transform: scale(1);
        transform: scale(1)
    }
}
<link href="https://cdnjs.cloudflare.com/ajax/libs/slick-carousel/1.6.0/slick-theme.min.css" rel="stylesheet"/>
<link href="https://cdnjs.cloudflare.com/ajax/libs/slick-carousel/1.6.0/slick.min.css" rel="stylesheet"/>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/2.2.4/jquery.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/jquery-easing/1.3/jquery.easing.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/slick-carousel/1.6.0/slick.min.js"></script>





<div class="slideshow">
  <div class="home-slider">
<div class="item animated">
  <img src="https://images.unsplash.com/photo-1532386236358-a33d8a9434e3?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=978&q=80" />
</div>
<div class="item animated">
  <img src="https://images.unsplash.com/photo-1518791841217-8f162f1e1131?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=1050&q=80" />
</div>
  </div>
</div>

Upvotes: 1

Alon Shmiel
Alon Shmiel

Reputation: 7121

The problem happens because all of the images gets the same z-index.

My code doesn't work when you navigate from first slide to last slide (slide right), but it's something to start with.

first of all, delete the z-index from this selector: .slideshow .item.

Then, add transform to every image in order every image to start with this scale (maybe add class name to each image and then change the selector):

img {
  transform: scale(1.3);
}

Add a function to get slick by index (there are cloned slicks elements so I use the selector that gets un-cloned slicks):

function getSlickByIndex(index) {
  return $('.item:not(.slick-cloned)[data-slick-index="' + index + '"]');
}

Then, add init event (the logic makes the first slide to get the bigger zIndex=1 of all of his siblings that have zIndex=0), and beforeChange (the logic makes the next slick to get the bigger index and the others to get the smaller index):

$('.home-slider').on('init', function(event, slick){
  getSlickByIndex(0).css('zIndex', '1');
}).on('beforeChange', function(event, slick, currentSlide, nextSlide){
  getSlickByIndex(currentSlide).css('zIndex', '0');
  getSlickByIndex(nextSlide).css('zIndex', '1');
});

Demo

img {
  transform: scale(1.3);
}

body,
html {
  height: 100%;
  background: #333;
  font-family: 'Roboto', sans-serif;
}

.slideshow {
  position: relative;
  z-index: 1;
  height: 100%;
  max-width: 700px;
  margin: 50px auto;
}

.slideshow .item {
  height: 100%;
  position: relative;
  z-index: 1;
}
.slideshow .item img {
  width: 100%;
  
}
.slideshow .item.slick-active img {
  -webkit-animation: ken-burns-out 8s 1 ease-in-out forwards;
  animation: ken-burns-out 8s 1 ease-in-out forwards;
}


/*//The animation: from 1.3 scale to 1*/
@-webkit-keyframes ken-burns-out {
    0% {
        -webkit-transform: scale(1.3);
        transform: scale(1.3)
    }

    to {
        -webkit-transform: scale(1);
        transform: scale(1)
    }
}

@keyframes ken-burns-out {
    0% {
        -webkit-transform: scale(1.3);
        transform: scale(1.3)
    }

    to {
        -webkit-transform: scale(1);
        transform: scale(1)
    }
}
<link href="https://cdnjs.cloudflare.com/ajax/libs/slick-carousel/1.6.0/slick.min.css" rel="stylesheet"/>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/2.2.4/jquery.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/jquery-easing/1.3/jquery.easing.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/slick-carousel/1.6.0/slick.min.js"></script>





<div class="slideshow">
  <div class="home-slider">
    <div class="item">
      <img src="https://images.unsplash.com/photo-1532386236358-a33d8a9434e3?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=978&q=80" />
    </div>
    <div class="item">
      <img src="https://images.unsplash.com/photo-1518791841217-8f162f1e1131?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=1050&q=80" />
    </div>
    <div class="item">
      <img src="https://images.unsplash.com/photo-1532386236358-a33d8a9434e3?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=978&q=80" />
    </div>
    <div class="item">
      <img src="https://images.unsplash.com/photo-1518791841217-8f162f1e1131?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=1050&q=80" />
    </div>
  </div>
</div>

<script>

  function getSlickByIndex(index) {
    return $('.item:not(.slick-cloned)[data-slick-index="' + index + '"]');
  }
  
$('.home-slider').on('init', function(event, slick){
  getSlickByIndex(0).css('zIndex', '5555');
}).on('beforeChange', function(event, slick, currentSlide, nextSlide){
  getSlickByIndex(currentSlide).css('zIndex', '0');
  getSlickByIndex(nextSlide).css('zIndex', '5555');
  
  
});
    $('.home-slider').slick({
     slidesToScroll: 1,
        slidesToShow: 1,
       
        //arrows: true,
        autoplay: true,
        autoplaySpeed: 7000,
        dots: false
  });

</script>

This is a working fiddle: https://codepen.io/alonsh/pen/JQmZjj

Upvotes: 3

Related Questions