3

I have created a jQuery based slideshow that lives within a DIV on my webpage. The only problem is the images have no transition effect between each other, just one to the next without the first one slowly fading out and the next fading on.

I would like to crossfade these images. What am I missing in my JS?

var urls = ['https://example.com/front.png',
 'https://example.com/interior-scaled.jpeg'];

  var count = 1;
  $('.hero').css('background-image', 'url("' + urls[0] + '")');
  setInterval(function() {
    $('.hero').css('background-image', 'url("' + urls[count] + '")');
    count == urls.length-1 ? count = 0 : count++;
  }, 6000);

});

LINK TO FIDDLE

FSDford
  • 448
  • 5
  • 10
ben.kaminski
  • 986
  • 3
  • 13
  • 24

7 Answers7

1

If you are not opposed to using a jQuery slideshow library then may I suggest using Ken Wheelers Slick carousel jQuery lib.

In your first comment you mentioned...

even if images slide like a carousel would be sufficient.

Well Slick makes easy work of both, plus loads of other cool options, event callbacks and responsive breakpoint settings. It might speed up creating sliding/fading carousels for your project utilising jQuery which you are already using.

I've include 2 hero slideshows in example below, both in fade: false mode.

  • #Hero_1 slideshow runs before images may have or have not loaded.
  • #Hero_2 uses $(window).on('load') to make sure your images have loaded before slideshow runs

// our hero examples as constant variables
const hero_1 = $('#hero_1');
const hero_2 = $('#hero_2');

// our slide image urls in constant variable array 
const slides = [
  'https://concorddentalde.com/wp-content/uploads/2020/10/concord-dental-patient-exam-room.jpeg',
  'https://concorddentalde.com/wp-content/uploads/2020/10/concord-dental-interior-scaled.jpeg',
  'https://concorddentalde.com/wp-content/uploads/2020/10/concord-dental-front.png'
];

// for each of the slide images as key > url
$.each(slides, function(key, url) {

  // append slide to hero carousel div
  $('.carousel', '.hero').append('<div class="slide" style="background-image:url(\'' + url + '\');"></div>');

});

// the below slick js should not run until the above each function has finished appending images in slides array

// slick hero carousel on init
$('.carousel', hero_1).on('init', function(slick) {

  // add show class to hero div to animate height when slick init
  $(hero_1).addClass('show');

// slick carousel options
}).slick({
  slidesToShow: 1,
  slidesToScroll: 1,
  dots: false,
  arrows: false,
  fade: true,
  adaptiveHeight: false,
  autoplay: true,
  infinite: true,
  pauseOnFocus: false,
  pauseOnHover: false,
  autoplaySpeed: 4000,
  speed: 1000,
  draggable: false
});

// use this if you want all background images to load first
// tho may be slow to run depending on how many images and the image size you are loading

$(window).on('load', function() {

  // slick on init
  $('.carousel', hero_2).on('init', function(slick) {

    // add show class to hero div to expand height
    $(hero_2).addClass('show');

  // slick options
  }).slick({
    slidesToShow: 1,
    slidesToScroll: 1,
    dots: false,
    arrows: false,
    fade: true,
    adaptiveHeight: false,
    autoplay: true,
    infinite: true,
    pauseOnFocus: false,
    pauseOnHover: false,
    autoplaySpeed: 4000,
    speed: 1000,
    draggable: false
  });

});
.hero {
  position: relative;
  overflow: hidden;
  background: rgba(0, 0, 0, .75);
  min-height: 0;
  height: 0;
  transition: all 0.5s ease;
  margin: 0 0 .5rem 0;
}

.hero.show {
  min-height: 150px;
  height: 150px;
  /* 
  height:45%;
  height:45vh;
  min-height:400px;
  */
}

.hero .carousel {
  position: absolute;
  top: 0;
  right: 0;
  width: 100%;
  height: 100%;
  opacity: 0;
  transition: opacity 0.5s ease;
}

.hero .carousel.slick-initialized {
  opacity: 1;
}

.hero .carousel .slick-list,
.hero .carousel .slick-track {
  height: 100% !important;
}

.hero .carousel .slide {
  background-color: none;
  background-repeat: no-repeat;
  background-size: cover;
  background-position: 50% 50%;
  height: 100%;
}

.hero .overlay {
  color: #fff;
  position: relative;
  text-align: center;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  text-shadow: 1px 1px 2px rgba(0, 0, 0, .75);
}


/* for demo styling purposes */

BODY {
  font-family: helvetica;
}

H1 {
  font-size: 2rem;
  font-weight: 600;
  margin: 0 0 .5rem 0;
}

P {
  margin: 0 0 .5rem 0;
}

.lead {
  font-size: 1.4rem;
  margin: 0 0 .5rem 0;
}

.row {
  margin: 0 -4px 0 -4px;
}

.col {
  float: left;
  width: calc(50% - 8px);
  padding: 0 4px 0 4px;
}
<link href="https://cdnjs.cloudflare.com/ajax/libs/slick-carousel/1.8.1/slick.min.css" rel="stylesheet" />

<div class="row">
  <div class="col">

    <p>
      <code><strong>#hero_1</strong><br/></code>
      <code><small>Slick inits after each function is complete.</small></code>
    </p>

    <div id="hero_1" class="hero">
      <div class="carousel"></div>
      <div class="overlay">
        <h1>Hero 1</h1>
        <p class="lead">
          Tooth Hurty
        </p>
      </div>
    </div>

  </div>
  <div class="col">

    <p>
      <code><strong>#hero_2</strong></code><br/>
      <code><small>Waits for all imgs to load before init slick.</small></code>
    </p>

    <div id="hero_2" class="hero">
      <div class="carousel"></div>
      <div class="overlay">
        <h1>Hero 2</h1>
        <p class="lead">
          Tooth Hurty
        </p>
      </div>
    </div>

  </div>
</div>

<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/slick-carousel/1.8.1/slick.min.js"></script>

Here is the same code above but in fade: false mode...

  • #Hero_1 slideshow runs before images may have or have not loaded.
  • #Hero_2 uses $(window).on('load') to make sure your images have loaded before slideshow runs

// our hero examples as constant variables
const hero_1 = $('#hero_1');
const hero_2 = $('#hero_2');

// our slide image urls in constant variable array 
const slides = [
  'https://concorddentalde.com/wp-content/uploads/2020/10/concord-dental-patient-exam-room.jpeg',
  'https://concorddentalde.com/wp-content/uploads/2020/10/concord-dental-interior-scaled.jpeg',
  'https://concorddentalde.com/wp-content/uploads/2020/10/concord-dental-front.png'
];

// for each of the slide images as key > url
$.each(slides, function(key, url) {

  // append slide to hero carousel div
  $('.carousel', '.hero').append('<div class="slide" style="background-image:url(\'' + url + '\');"></div>');

});

// the below slick js should not run until the above each function has finished appending images in slides array

// slick hero carousel on init
$('.carousel', hero_1).on('init', function(slick) {

  // add show class to hero div to animate height when slick init
  $(hero_1).addClass('show');

// slick carousel options
}).slick({
  slidesToShow: 1,
  slidesToScroll: 1,
  dots: false,
  arrows: false,
  fade: false,
  adaptiveHeight: false,
  autoplay: true,
  infinite: true,
  pauseOnFocus: false,
  pauseOnHover: false,
  autoplaySpeed: 4000,
  speed: 1000,
  draggable: false
});

// use this if you want all background images to load first
// tho may be slow to run depending on how many images and the image size you are loading

$(window).on('load', function() {

  // slick on init
  $('.carousel', hero_2).on('init', function(slick) {

    // add show class to hero div to expand height
    $(hero_2).addClass('show');

  // slick options
  }).slick({
    slidesToShow: 1,
    slidesToScroll: 1,
    dots: false,
    arrows: false,
    fade: false,
    adaptiveHeight: false,
    autoplay: true,
    infinite: true,
    pauseOnFocus: false,
    pauseOnHover: false,
    autoplaySpeed: 4000,
    speed: 1000,
    draggable: false
  });

});
.hero {
  position: relative;
  overflow: hidden;
  background: rgba(0, 0, 0, .75);
  min-height: 0;
  height: 0;
  transition: all 0.5s ease;
  margin: 0 0 .5rem 0;
}

.hero.show {
  min-height: 150px;
  height: 150px;
  /* 
  height:45%;
  height:45vh;
  min-height:400px;
  */
}

.hero .carousel {
  position: absolute;
  top: 0;
  right: 0;
  width: 100%;
  height: 100%;
  opacity: 0;
  transition: opacity 0.5s ease;
}

.hero .carousel.slick-initialized {
  opacity: 1;
}

.hero .carousel .slick-list,
.hero .carousel .slick-track {
  height: 100% !important;
}

.hero .carousel .slide {
  background-color: none;
  background-repeat: no-repeat;
  background-size: cover;
  background-position: 50% 50%;
  height: 100%;
}

.hero .overlay {
  color: #fff;
  position: relative;
  text-align: center;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  text-shadow: 1px 1px 2px rgba(0, 0, 0, .75);
}


/* for demo styling purposes */

BODY {
  font-family: helvetica;
}

H1 {
  font-size: 2rem;
  font-weight: 600;
  margin: 0 0 .5rem 0;
}

P {
  margin: 0 0 .5rem 0;
}

.lead {
  font-size: 1.4rem;
  margin: 0 0 .5rem 0;
}

.row {
  margin: 0 -4px 0 -4px;
}

.col {
  float: left;
  width: calc(50% - 8px);
  padding: 0 4px 0 4px;
}
<link href="https://cdnjs.cloudflare.com/ajax/libs/slick-carousel/1.8.1/slick.min.css" rel="stylesheet" />

<div class="row">
  <div class="col">

    <p>
      <code><strong>#hero_1</strong><br/></code>
      <code><small>Slick inits after each function is complete.</small></code>
    </p>

    <div id="hero_1" class="hero">
      <div class="carousel"></div>
      <div class="overlay">
        <h1>Hero 1</h1>
        <p class="lead">
          Tooth Hurty
        </p>
      </div>
    </div>

  </div>
  <div class="col">

    <p>
      <code><strong>#hero_2</strong></code><br/>
      <code><small>Waits for all imgs to load before init slick.</small></code>
    </p>

    <div id="hero_2" class="hero">
      <div class="carousel"></div>
      <div class="overlay">
        <h1>Hero 2</h1>
        <p class="lead">
          Tooth Hurty
        </p>
      </div>
    </div>

  </div>
</div>

<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/slick-carousel/1.8.1/slick.min.js"></script>
joshmoto
  • 4,472
  • 1
  • 26
  • 45
  • I'm giving this the check as it's the only true crossfade shown. Unfortunately, maybe -- I found a simpler solution on my own that I will share as an answer below, but @joshmoto you will get any credit for this. – ben.kaminski Nov 12 '20 at 00:32
  • 1
    Thanks @ben.kaminski, OK no worries if you found your own solution. I will take a look at your solution code tomorrow and if I can see anyway of improving or optimising it. I will post an update to my answer. – joshmoto Nov 12 '20 at 02:02
0

You can use transition: background-image. It might not be supported in all browsers, but most modern browsers should be fine.

Add

-webkit-transition: background-image 0.5s ease-in-out;
transition: background-image 0.5s ease-in-out;

to the css of the div which has the background image.

Here's a forked fiddle with a working example: https://jsfiddle.net/bmh2qu0e/1/

tudor.gergely
  • 4,800
  • 1
  • 16
  • 22
0

You can use transition on opacity and toggle opacity on background change, something like:

$(document).ready(function() {
  var urls = ['https://concorddentalde.com/wp-content/uploads/2020/10/concord-dental-patient-exam-room.jpeg',
    'https://concorddentalde.com/wp-content/uploads/2020/10/concord-dental-interior-scaled.jpeg',
    'https://concorddentalde.com/wp-content/uploads/2020/10/concord-dental-front.png'
  ];

  var count = 1;
  var $hero = $('.hero');

  $hero.css('background-image', 'url("' + urls[0] + '")');
  setInterval(function() {
    setTimeout(function() {
      $hero.toggleClass('transparent');

      setTimeout(function() {
        $hero.css('background-image', 'url("' + urls[count] + '")');
        count == urls.length - 1 ? count = 0 : count++;
        $hero.toggleClass('transparent');
      }, 300);
    }, 300);
  }, 6000);

});
.transparent {
  opacity: 0;
}

.hero {
  height: 45%;
  height: 45vh;
  min-height: 400px;
  background-color: none;
  text-align: center;
  background-repeat: no-repeat;
  background-size: cover;
  background-position: 50% 50%;
  -webkit-transition: opacity 0.5s ease-in-out;
  transition: opacity 0.5s ease-in-out;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class="hero"></div>

If you want to take a step further, you can make it more generic with class:

class ImageSlider {
  imagePos = 0;
  intevalHandle = null;
  intervalMS = 6000;

  constructor(elem, images, startImmediately) {
    this.images = images || [];
    this.elem = $(elem);

    if (startImmediately) {
      this.startSlider();
    }
  }

  startSlider() {
    if (this.startTimer()) {
      this.imagePos = 0;
      this.onTimerInterval();
    }
  };

  pauseSlider() {
    this.clearTimer();
  }

  resumeSlider() {
    this.startTimer();
  }

  stopSlider() {
    this.clearTimer();
    this.imagePos = 0;
  };

  startTimer() {
    if (this.intervalHandle != null) {
      return false;
    }

    this.intervalHandle = setInterval(() => this.onTimerInterval(), this.intervalMS);
    return true;
  };

  clearTimer() {
    if (this.intervalHandle) {
      this.clearInterval(this.intervalHandle);
      this.intervalHandle = null;
    }
  }

  onTimerInterval() {
    if (this.images.length <= 0) {
      return;
    }

    setTimeout(() => {
      this.elem.toggleClass('transparent');

      setTimeout(() => {
        if (this.imagePos >= this.images.length) {
          this.imagePos = 0;
        }

        this.elem.css('background-image', 'url("' + this.images[this.imagePos] + '")');
        this.imagePos++;
        this.elem.toggleClass('transparent');
      }, 300);
    }, 300);
  }
}

$(document).ready(function() {
  var urls = ['https://concorddentalde.com/wp-content/uploads/2020/10/concord-dental-patient-exam-room.jpeg',
    'https://concorddentalde.com/wp-content/uploads/2020/10/concord-dental-interior-scaled.jpeg',
    'https://concorddentalde.com/wp-content/uploads/2020/10/concord-dental-front.png'
  ];
  var slider1 = new ImageSlider('#ss1', urls, true);
  var slider2 = new ImageSlider('#ss2', [...urls].reverse(), true);
});
.transparent {
  opacity: 0;
}

.hero {
  height: 45%;
  height: 45vh;
  min-height: 400px;
  background-color: none;
  text-align: center;
  background-repeat: no-repeat;
  background-size: cover;
  background-position: 50% 50%;
  -webkit-transition: opacity 0.5s ease-in-out;
  transition: opacity 0.5s ease-in-out;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div id="ss1" class="hero"></div>
<div id="ss2" class="hero"></div>
Dipen Shah
  • 25,562
  • 1
  • 32
  • 58
0

You can't cross-fade a single background image using CSS.

A possible solution is to have two containers inside the hero <div> you have there.

E.g:

<div class="hero">
    <div class="img-container" id="first"></div>
    <div class="img-container" id="second"></div>
</div>

For your desired effect of the crossfade you will need these images to cover the desired area on top of the hero <div>.

This can be done by these CSS rules:

.img-container {
    background-repeat: no-repeat;
    background-size: cover;
    background-position: 50% 50%;
    background-color: transparent;
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
}

Now we need to have the images load in and cross-fade over one another.

$(document).ready(function() {
  var urls = [
    'imgURL1',
    'imgURL2',
    'imgURL3'
  ];

  // Preload the images
  var tempImg = []
  for (var i = 0; i < urls.length; i++) {
    (new Image()).src = urls[i]
  }

  // The currently shown image's index
  var currentShown = 0;
  
  // Get the containers
  var first = $("#first");
  var second = $("#second");

  // This shows whether the second object is on top or not
  var secondOnTop = true;

  // Set the first container's value so that there is something on the screen and load the second image on top.
  first.css('background-image', 'url("' + urls[urls.length - 1] + '")');
  second.css({
    backgroundImage: 'url("' + urls[0] + '")',
    opacity: 1
  });

  // Change the image every X seconds
  setInterval(function() {

    var animationSpeed = 1000; // In milliseconds

    // Increment currently shown image index
    currentShown === urls.length - 1 ? currentShown = 0 : currentShown++;

    // Determine which object has visual priority
    var primaryObj = first;
    var auxObj = second;
    if (secondOnTop) {
      primaryObj = second;
      auxObj = first;
    }
    secondOnTop = !secondOnTop;

    // Show aux obj background
    auxObj.css({backgroundImage: 'url("' + urls[currentShown] + '")'});
    auxObj.animate({
      opacity: 1
    }, animationSpeed);

    // Change shown object's background and set to 0
    primaryObj.animate({
      opacity: 0,
    }, animationSpeed, function() {
      // Change the primary's background to the next in queue
      var nextImg = currentShown === urls.length - 1 ? 0 : currentShown + 1;
      primaryObj.css('background-image', 'url("' + urls[nextImg] + '")');
    });

  }, 6000);

});

I have created a fork of your fiddle available here: https://jsfiddle.net/YeloPartyHat/uLfr389g/88/

Liam Pillay
  • 592
  • 1
  • 4
  • 19
0

Here is a solution: (I had to replace shortened url with full url otherwise SO wouldn't let me save the answer)

$(document).ready(function(){
var urls = ['https://concorddentalde.com/wp-content/uploads/2020/10/concord-dental-patient-exam-room.jpeg',
     'https://concorddentalde.com/wp-content/uploads/2020/10/concord-dental-interior-scaled.jpeg',
     'https://concorddentalde.com/wp-content/uploads/2020/10/concord-dental-front.png'];

        var totalLayers = 2;
      var layerIndex = 0;
      var count = 0;
      $('.layer-' + layerIndex)
    .removeClass('layer')
    .css('background-image', 'url("' + urls[count] + '")');
    console.log({first: layerIndex, second: (layerIndex + 1) % totalLayers, count})

      setInterval(function() {
        var outLayer = layerIndex
      var inLayer = ++layerIndex % totalLayers
      layerIndex = inLayer
        count = ++count % urls.length;
      console.log({first: outLayer, second: inLayer, count})
          $('.layer-' + outLayer)
      .addClass('animateXFadeOut');
        $('.layer-' + inLayer)
      .removeClass('layer')
      .css('background-image', 'url("' + urls[count] + '")')
      .addClass('animateXFadeIn');
      setTimeout(function() {
        $('.layer-' + outLayer).css({backgroundImage: 'none', opacity: 1});
        $('.layers').removeClass('animateXFadeIn animateXFadeOut');
      }, 1000);
      }, 6000);

    });
.hero {
/*  height: 45%;
    height: 45vh;
    min-height: 400px;
     */
  background-color: none;
    text-align: center;
    background-repeat: no-repeat;
    background-size: cover;
    background-position: 50% 50%;
}
@keyframes xfadein {
  from {
    opacity: 0;
  }
  to {
    opacity: 1;
  }
}
@keyframes xfadeout {
  from {
    opacity: 1;
  }
  to {
    opacity: 0;
  }
}
.animateXFadeIn {
  animation-name: xfadein;
  animation-duration: 1s;
}
.animateXFadeOut {
  animation-name: xfadeout;
  animation-duration: 1s;
}
.layer-0, .layer-1 {
  display: block;
  position: absolute;
    height: 45%;
    height: 45vh;
    min-height: 400px;
  width: 100%;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class="hero">
  <div class="layers layer-0"></div>
  <div class="layers layer-1"></div>
</div>
Ethan Doh
  • 508
  • 1
  • 7
  • 9
0

you can use one little class and one line of jquery to do that

$(document).ready(function(){
var urls = ['image_one_url',
     'image_two_url',
     'image_three_url'];

      var count = 1;
    
      $('.hero').css('background-image', 'url("' + urls[0] + '")');
    $('.hero').addClass('animatedinout');
    
      setInterval(function() {
        $('.hero').css('background-image', 'url("' + urls[count] + '")');
        count == urls.length-1 ? count = 0 : count++;
      }, 6000);

    });
.animatedinout{
  animation: fadeinout;
  animation-duration: 6000ms;
  animation-timing-function: ease-in-out;
  animation-iteration-count: infinite;
}

@keyframes fadeinout{
  0%{
    opacity: 0;
  }
  10%{
    opacity: 1;
  }
  90%{
    opacity: 1;
  }
  100%{
    opacity: 0;
  }
}

i just add a css class called animatedinout that use an animation forever for every 6000 mili seconds and add

$('.hero').addClass('animatedinout'); 

right before your setInterval.

0

I thank everyone for their hard work. The solution I found (because I was on a tight deadline) is actually pretty simple and combines JS and CSS to make for a "true" crossfade transition.

HTML:

<div id="background-images">
    <div class="bgImages active" id="bgImg1"></div>
    <div class="bgImages" id="bgImg2"><br></div>
    <div class="bgImages" id="bgImg3"><br><br></div>
    <div class="bgImages" id="bgImg4"><br></div>
    <div class="bgImages" id="bgImg5"><br><br></div>
</div>

jQuery:

function cycleImages() {
  var $active = $("#background-images .active");
  var $next = $active.next().length > 0
    ? $active.next()
    : $("#background-images div:first");
  $next.css("z-index", 2); // move the next img up the stack
  $active.fadeOut(1500, function() {
    //fade out the top image
    $active.css("z-index", 1).show().removeClass("active"); //reset z-index and unhide the image
    $next.css("z-index", 3).addClass("active"); //make the next image the top one
  });
}

$(document).ready(function() {
  $("#cycler img").show();
  // run every 6 seconds
  setInterval(cycleImages, 6000);
});

CSS:

#background-images {
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  height: 670px;
  z-index: -5;
}
#bgImg1, #bgImg2, #bgImg3, #bgImg4, #bgImg5 {
  width: 100%;
  height: 100%;
  position: fixed;
  background-position-x: center;
  background-position-y: center;
  background-size: cover;
  background-repeat: no-repeat;
}
#bgImg1 { background-image: url("https://image1.jpg"); }
#bgImg2 { background-image: url("https://image2.png"); z-index: 2; }
#bgImg3 { background-image: url("https://image3.jpeg"); }
#bgImg4 { background-image: url("https://image4.jpg"); }
#bgImg5 { background-image: url("https://image5.jpeg"); }
  

It was a clever way of using z-index combined with active status to get the images to actually crossfade with no white "blink".

Found it on a CodePen here.

ben.kaminski
  • 986
  • 3
  • 13
  • 24