Valyrion Valyrion - 4 months ago 29
Javascript Question

Bootstrap carousel refuses to transition smoothly after css adjustments

For the last 3 hours I've been trying to make a simple adjustment to Bootstrap 3's carousel transitions.
I've tried changing the slide speed where this is the only thing that seems to have any effect:

.carousel-inner .item {
-webkit-transition-duration: 2s;
-moz-transition-duration: 2s;
-o-transition-duration: 2s;
transition-duration: 2s;
}


but it hides the 'leaving' content too soon, and I have no clue what property to modify to fix that.

I've also tried changing it to a fade transition with

.carousel-fade .item {
opacity: 0;
-webkit-transition: opacity 2s ease-in-out;
-moz-transition: opacity 2s ease-in-out;
-ms-transition: opacity 2s ease-in-out;
-o-transition: opacity 2s ease-in-out;
transition: opacity 2s ease-in-out;
left: 0 !important;
}

.carousel-fade .active {
opacity: 1 !important;
}

.carousel-fade .left {
opacity: 0 !important;
-webkit-transition: opacity 0.5s ease-in-out !important;
-moz-transition: opacity 0.5s ease-in-out !important;
-ms-transition: opacity 0.5s ease-in-out !important;
-o-transition: opacity 0.5s ease-in-out !important;
transition: opacity 0.5s ease-in-out !important;
}

.carousel-fade .carousel-control {
opacity: 1 !important;
}


And just about every other snippet to do so that I've come across, but every single one always first removed the leaving content, showing a featureless background, before fading in the next. What am I missing? All I need is some plain CSS to override the existing transition details, but I don't know where to look any more.

Answer

I think different aspects of bootstrap's carousel plugin give the effects you mention.

  1. Active items have display: block while not active items have display: none

    This can be solved by giving all items display: block and then setting the position to absolute with top: 0 and left: 0, resulting in the items overlapping. Setting opacity: 0; makes them invisible by default.

    Less:

    .carousel-inner > .item {
      opacity: 0;
      top: 0;
      left: 0;
      width: 100%;
      display: block;
      position: absolute;
    }
    

    One problem of the position: absolute is that the container does not get a height. The preceding can be solved by setting the position of the first item to relative (it is add the right position already). In Less code, it is as follows:

    .carousel-inner > .item {
      :first-of-type {
        position:relative;
      } 
    }
    
  2. Bootstrap uses translate3ds to change the position of the item in the space. You won't need these transformations, so reset them. Leveraging Less, code shown below:

    .carousel-inner > .item {
      transform: translate3d(0,0,0) !important;
    }
    
  3. The CSS transitions are triggered by adding and removing CSS classes with jQuery. The time between these class changes has been hardcoded in the carousel plugin code (Carousel.TRANSITION_DURATION = 600). So, after 600 ms, one item becomes active (having the .active class). That is the reason for the unexpected behavior if your css transition-duration is greater than 0.6 seconds.

The CSS class changes are as follows:

The active item has class .active -> .active.left -> none The next item has no class -> .next.left -> .active

So the .active.left and .next.left are important (or .prev.right and .active.right when you slide backwards).

Because all images are already stacked, you can use the z-index property to make an image in the stack visible, because we can change the opacity at the same time. You can use the following Less code to fade in the next slide:

.carousel-inner {
  > .next.left {
    transition: opacity 0.6s ease-in-out;
    opacity: 1;
    z-index:2;
  }
  > .active.left {
    z-index:1;
  }
}

To make sure that the controls are visible as well, use:

.carousel-control {
  z-index:4;
}

Putting all together, see the results in this demo, which uses the following Less code:

.carousel-inner {
 > .item {
  opacity: 0;
  top: 0;
  left: 0;
  width: 100%;
  display: block;
  position: absolute;
  z-index:0;
  transition: none;
  transform: translate3d(0,0,0) !important;
  &:first-of-type {
    position:relative;
  } 
  }
 > .active {
  opacity: 1;
  z-index:3;
}

 > .next.left,
 > .prev.right {
  transition: opacity 0.6s ease-in-out;
  opacity: 1;
  left: 0;
  z-index:2;
  }                                                                                                             
 > .active.left,
 > .active.right {
  z-index:1;
  }
}
.carousel-control {
z-index:4;
}

The above code can be compiled with the Less autoprefixer plugin plugin into CSS with the following command:

lessc --autoprefix="Android 2.3,Android >= 4,Chrome >= 20,Firefox >= 24,Explorer >= 8,iOS >= 6,Opera >= 12,Safari >= 6' code.less

which outputs:

.carousel-inner > .item {
  opacity: 0;
  top: 0;
  left: 0;
  width: 100%;
  display: block;
  position: absolute;
  z-index: 0;
  -webkit-transition: none;
       -o-transition: none;
          transition: none;
  -webkit-transform: translate3d(0, 0, 0) !important;
          transform: translate3d(0, 0, 0) !important;
}
.carousel-inner > .item:first-of-type {
  position: relative;
}
.carousel-inner > .active {
  opacity: 1;
  z-index: 3;
}
.carousel-inner > .next.left,
.carousel-inner > .prev.right {
  -webkit-transition: opacity 0.6s ease-in-out;
       -o-transition: opacity 0.6s ease-in-out;
          transition: opacity 0.6s ease-in-out;
  opacity: 1;
  left: 0;
  z-index: 2;
}
.carousel-inner > .active.left,
.carousel-inner > .active.right {
  z-index: 1;
}
.carousel-control {
  z-index: 4;
}