0

I have four DIVs with different background-images. I am trying to get the DIVs to shuffle through randomly selected background-images every 2 seconds but only one DIV at a time.

I have put together this vanilla javascript with the help of others while searching for a solution, but I am running into the following problems:

  1. The change of the background-image is very abrupt. I would like to have a fade-out -> fade-in effect when the change happens.
  2. I would like to prevent the case that it can select a background-image that is already used in that moment by one of the DIVs.
  3. I would like to convert this code to jQuery but I am really stuck with that task.

Here is the code I have so far, any help is very much appreciated.

var urls = [
  'url(https://placekitten.com/g/350/150)',
  'url(https://placekitten.com/g/200/600)',
  'url(https://placekitten.com/g/550/250)',
  'url(https://placekitten.com/g/700/300)',
  'url(https://placekitten.com/g/300/550)',
  'url(https://placekitten.com/g/400/200)',
  'url(https://placekitten.com/g/350/750)'
];
var active = Math.floor(Math.random() * (urls.length));
setInterval(function() {
  let rand = Math.floor(Math.random() * 4);
  if (rand <= 2 && rand == document.getElementsByClassName('image')[0].getAttribute("data-changed")) {
    rand = rand + 1;
  } else if (rand == 2 && rand == document.getElementsByClassName('image')[0].getAttribute("data-changed")) {
    rand = rand - 1;
  }

  document.getElementsByClassName('image')[rand].style.backgroundImage = urls[active];
  document.getElementsByClassName('image')[0].setAttribute("data-changed", rand);

  active++;
  if (active == urls.length) active = 0;
  //})
}, 2000);
.image {
  background-size: cover;
  display: inline-block;
  width: 200px;
  height: 200px;
  border: 1px solid red;
}

.one { 
  background-image: url(https://placekitten.com/g/350/150); 
}
.two { 
  background-image: url(https://placekitten.com/g/300/550); 
}
.three { 
  background-image: url(https://placekitten.com/g/400/200); 
}
.four { 
  background-image: url(https://placekitten.com/g/350/750); 
}
<div class="image one"></div>
<div class="image two"></div>
<div class="image three"></div>
<div class="image four"></div>

And here is the code on jsFiddle.

Jascha Goltermann
  • 1,074
  • 2
  • 16
  • 31

2 Answers2

1

I modified your code just a bit to make this new version, that use Fade out / Fade In functions from here, using visibility CSS attribute. This will just make the image disappear, the background will be visible a moment, and then the new image will appear. There are many ways of doing a Fade effect, and this is one of them. You could want something else (for example, the image becomes black and then the new image appears). If you want to try another types of Fade, you can search through CSS filters list.

var urls = [
  'url(https://placekitten.com/g/350/150)',
  'url(https://placekitten.com/g/200/600)',
  'url(https://placekitten.com/g/550/250)',
  'url(https://placekitten.com/g/700/300)',
  'url(https://placekitten.com/g/300/550)',
  'url(https://placekitten.com/g/400/200)',
  'url(https://placekitten.com/g/350/750)'
];
var active = Math.floor(Math.random() * (urls.length));

setInterval(function() {
  let rand = Math.floor(Math.random() * 4);
  if (rand <= 2 && rand == document.getElementsByClassName('image')[0].getAttribute("data-changed")) {
    rand = rand + 1;
  } else if (rand == 2 && rand == document.getElementsByClassName('image')[0].getAttribute("data-changed")) {
    rand = rand - 1;
  }


  document.getElementsByClassName('image')[rand].classList.remove("visible");
  document.getElementsByClassName('image')[rand].classList.add("hidden");

  setTimeout(function(){
    document.getElementsByClassName('image')[rand].style.backgroundImage = urls[active];
    document.getElementsByClassName('image')[0].setAttribute("data-changed", rand);

    document.getElementsByClassName('image')[rand].classList.remove("hidden");
    document.getElementsByClassName('image')[rand].classList.add("visible");

  },400); // 400 ms because animation duration is 0.4s

  active++;
  if (active == urls.length) active = 0;
  //})
}, 2000);
.image {
  background-size: cover;
  display: inline-block;
  width: 200px;
  height: 200px;
  border: 1px solid red;
}

.one { 
  background-image: url(https://placekitten.com/g/350/150); 
}
.two { 
  background-image: url(https://placekitten.com/g/300/550); 
}
.three { 
  background-image: url(https://placekitten.com/g/400/200); 
}
.four { 
  background-image: url(https://placekitten.com/g/350/750); 
}

.visible {
      opacity: 1;
      transition: opacity 0.4s ease;
}

.hidden {
  opacity: 0;
  transition: opacity 0.4s ease;
}
<div class="image visible one"></div>
<div class="image visible two"></div>
<div class="image visible three"></div>
<div class="image visible four"></div>

However, this is not the best way of doing it. So I modified the code, and added some JQuery stuff.

var urls = [
  'url(https://placekitten.com/g/350/150)',
  'url(https://placekitten.com/g/200/600)',
  'url(https://placekitten.com/g/550/250)',
  'url(https://placekitten.com/g/700/300)',
  'url(https://placekitten.com/g/300/550)',
  'url(https://placekitten.com/g/400/200)',
  'url(https://placekitten.com/g/350/750)'
];

// Select the next image, may be one of the actual displayed images
var active = Math.floor(Math.random() * (urls.length));


setInterval(function() {
  // Select randomnly the next div to change
  var rand = Math.floor(Math.random() * 4);
  
  // Store this list, so that we only make one call to the page
  // equiv : document.getElementsByClassName('image')
  let images = $('.image');
  
  // equiv : images[0].getAttribute("data-changed")
  let datachanged = images.attr("data-changed");
  
  // This conditions work, but their is a better way of doing it. See comment below the snippet
  if (rand <= 2 && rand == datachanged ) {
    rand += 1;
  } else if (rand >= 2 && rand == datachanged ) {
    rand -= 1;
  }

  // Jquery selector for the targetted div
  let current = $('.image:nth-child('+(rand+1)+')');
  
  // Now we can use JQuery methods, such as toggleClass
  current.toggleClass("visible hidden");
  
  // The fade effect takes 0.4ms, or 400ms
  // So we use a setTimeout to change the bg in 400ms and not immediatly
  // Once the background is changed, the "visible" class we be added
  // If you want to change the duration, remember to also change it in the CSS
  setTimeout(function(){
  
    // equiv : images[rand].style.backgroundImage = urls[active];
    current.css('background-image', urls[active]);
    
    
    images[0].setAttribute("data-changed", rand);
  
    current.toggleClass("hidden visible");
  
  },400); // 400 ms because CSS animation duration is 0.4s

  // Change active value so that the background will not be same next time
  active++;
  
  // Faster way to write if(...) { active = 0 }
  active = (active == urls.length) ? 0 : active ;
  

}, 2000);
.image {
  background-size: cover;
  display: inline-block;
  width: 200px;
  height: 200px;
  border: 1px solid red;
  transition: opacity 0.4s ease;
}

.one { 
  background-image: url(https://placekitten.com/g/350/150); 
}
.two { 
  background-image: url(https://placekitten.com/g/300/550); 
}
.three { 
  background-image: url(https://placekitten.com/g/400/200); 
}
.four { 
  background-image: url(https://placekitten.com/g/350/750); 
}

.visible {
      opacity: 1;
}

.hidden {
  opacity: 0;
}
<!-- JQuery 3.3.1 -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div>
  <div class="image one" data-changed="0"></div>
  <div class="image two"></div>
  <div class="image three"></div>
  <div class="image four"></div>
</div>

Also, note that you could change

rand = Math.floor(Math.random() * 4);
if (rand <= 2 && rand == datachanged ) {
   rand += 1;
} else if (rand >= 2 && rand == datachanged ) {
  rand -= 1;
 }

by

while(rand == datachanged)
  rand = Math.floor(Math.random() * 4);

So that you have a better "random".

I put a lot of comment, I hope it helped.

Dony
  • 1,543
  • 2
  • 10
  • 25
  • Hi, thank you for the great explanation. The comments really help to understand your code! However, it seems that it still sometimes chooses an image which is currently already visible. I would like to prevent the case that it can select a background-image that is already used in that moment by one of the DIVs. – Jascha Goltermann May 13 '20 at 17:55
  • @JaschaGoltermann Yes, it is the case. As it was in your original code, I thought it was intentional, so I didn't modify this part. To prevent that case, replace `var active = Math.floor(Math.random() * (urls.length));` (line 12) by `var active = Math.floor(Math.random() * (urls.length - 4)) + 1`. This will create a Random number between 1 and 3 instead of 0 and 6, so that the first background-image chosen will not be one the actual 4 – Dony May 14 '20 at 01:14
  • 1
    thank you, this prevents it from choosing one of the four images that the DIVs use at the beginning but it does not prevent it from choosing an image that is used by one of the DIVs at a later time. Sometimes, after waiting for a while, you will see that it chooses an image that is at that moment being used by one of the other DIVs. But I will mark your answer as accepted because it was very thorough and well commented! Just let me know if you're able to achieve this last requirement. – Jascha Goltermann May 14 '20 at 06:22
  • Yes, to achieve that, I think you must have an array that contains all the background urls that you are currently using. When you change the background of a div, then update the array. And finally, when you pick a new random bg url, check if the array already contains it or no. This might be a solution – Dony May 14 '20 at 19:59
  • Hi @Dony that sounds good. Would you also know how to integrate that idea into the code above? I am was only able to put that code together with the help of others but I would've never been able to write or understand the code myself.. – Jascha Goltermann May 15 '20 at 06:27
  • I'll answer it with pleasure, but if you need help on a new topic, but please make a new question for that ;) – Dony May 15 '20 at 17:29
  • Hi Don, thank you for the suggestion! I have posted a new question for that here: https://stackoverflow.com/questions/61796547/apply-a-css-value-with-jquery-but-check-if-it-is-specified-already – Jascha Goltermann May 16 '20 at 16:10
0

var urls = [
  'url(https://placekitten.com/g/350/150)',
  'url(https://placekitten.com/g/200/600)',
  'url(https://placekitten.com/g/550/250)',
  'url(https://placekitten.com/g/700/300)',
  'url(https://placekitten.com/g/300/550)',
  'url(https://placekitten.com/g/400/200)',
  'url(https://placekitten.com/g/350/750)'
];
var active = Math.floor(Math.random() * (urls.length));
setInterval(function() {
  let rand = Math.floor(Math.random() * 4);
  if (rand <= 2 && rand == document.getElementsByClassName('image')[0].getAttribute("data-changed")) {
    rand = rand + 1;
  } else if (rand == 2 && rand == document.getElementsByClassName('image')[0].getAttribute("data-changed")) {
    rand = rand - 1;
  }
  
  const imageElements = document.getElementsByClassName('image')
  
  for(var i = 0; i < imageElements.length; i++){
  imageElements[i].className =  imageElements[i].className.replace('animate-fade-in','')
  }

  imageElements[rand].style.backgroundImage = urls[active];
  
  imageElements[rand].className = "image animate-fade-in";

  imageElements[0].setAttribute("data-changed", rand);

  active++;
  if (active == urls.length) active = 0;
  //})
}, 2000);
.image {
  background-size: cover;
  display: inline-block;
  width: 200px;
  height: 200px;
  border: 1px solid red;
}

.one { 
  background-image: url(https://placekitten.com/g/350/150); 
}
.two { 
  background-image: url(https://placekitten.com/g/300/550); 
}
.three { 
  background-image: url(https://placekitten.com/g/400/200); 
}
.four { 
  background-image: url(https://placekitten.com/g/350/750); 
}

.animate-fade-in {
  animation: fadeIn 0.5s linear;
  animation-fill-mode: both;
}

@keyframes fadeIn {
  0% {
    opacity: 0;
  }

  50% {
    opacity: 0.5;
  }

  100% {
    opacity: 1;
  }
}
<div class="image one animate-fade-in"></div>
<div class="image two animate-fade-in"></div>
<div class="image three animate-fade-in"></div>
<div class="image four animate-fade-in"></div>
Dibakar Halder
  • 203
  • 3
  • 8
  • That fade effect is great! But I think it is still sometimes selecting a background-image that is already used by one of the DIVs in that moment. Can we prevent that? – Jascha Goltermann May 13 '20 at 16:02
  • That is problem in the logic. I will add the fix – Dibakar Halder May 13 '20 at 16:39
  • Hey Dibakar, do you think you'll still be able to help me with this? I've kept on trying but couldn't find a solution. This is what I have so far: https://jsfiddle.net/sublines/bykes31d/26/ – Jascha Goltermann May 15 '20 at 08:06