2

I'm currently working on a private dashboard. This dashboard should have changing background images that changes every minute (should be no problem to switch it to an hour or 20 seconds if I got it working then).

In order to do so, I registered for the [Pixabay API][1] and created the following API request

https://pixabay.com/api/?key=[my_key]f&q=nature&image_type=photo&orientation=horizontal&min_width=1920&min_height=1080&page=1&per_page=100

With that request, I get an array of 100 elements, each one containing the following information:

comments: 639
downloads: 785498
favorites: 3020
id: 736885
imageHeight: 1195
imageSize: 186303
imageWidth: 1920
largeImageURL: "https://pixabay.com/get/51e3d34b4257b108f5d0846096293076123ddee2504c704c7c2879d79048c05a_1280.jpg"
likes: 3966
pageURL: "https://pixabay.com/photos/tree-sunset-amazing-beautiful-736885/"
previewHeight: 93
previewURL: "https://cdn.pixabay.com/photo/2015/04/23/22/00/tree-736885_150.jpg"
previewWidth: 150
tags: "tree, sunset, amazing"
type: "photo"
user: "Bessi"
userImageURL: "https://cdn.pixabay.com/user/2019/04/11/22-45-05-994_250x250.jpg"
user_id: 909086
views: 2042402
webformatHeight: 398
webformatURL: "https://pixabay.com/get/51e3d34b4257b10ff3d8992cc62f3f79173fd9e64e507440722d78d39248c7_640.jpg"
webformatWidth: 640

From these 100 elements, I then randomly select one, take the largeImageURL and set it as background, together with a semi-transparent dark overlay to be able to read the text on top of it better. All this is done within a setInterval, so it happens every x milliseconds.

This is the code for it:

setInterval(function(){
        $.post('getBackgroundImages.php', {  }, function(data) {
        var imageCollection = JSON.parse(data);
        var imageNumber = Math.floor(Math.random() * 100);
        var imageLink = imageCollection.hits[imageNumber].largeImageURL;
        $('body').css("background","linear-gradient(rgba(0,0,0,.3), rgba(0,0,0,.3)),url('"+imageLink+"')");
    });
},60000);

`getBackgroundImages.php' does nothing more then printing the content of the API-request.

The question now is the following: In the implemented solution, everything works, the new photo is displayed as background and switching works. However, the background is always set to a grey background for about half a second, before the image is displayed, which looks really not good, especially when often switching images.

What I'd like to get is a switching of the background without this grey background for a short time, propably even with a transition, so the change is not so abrupt...

I found a solution to first display a blured preview of the image before display the full resolution one. However, I think that this shouldn't be needed, as basically, the image has enough time to load and the background should change AFTER the image has loaded.. I do not care, if the change happens every 62 seconds, even though I set it to 60 seconds, because the image needs to load first.

Can anybody give me a hint on how to get this working better?

Thanks in advance! [1]: https://pixabay.com/api/docs/

nameless
  • 1,483
  • 5
  • 32
  • 78
  • 1
    Maybe try warming the image cache up as a prerequisite to actually performing the transition. See if doing something like this: [my answer to loading image through promise](https://stackoverflow.com/a/52060802/691711) gets the actual image cached and ready for immediate display and *then* perform the transition. I can't tell if the issue is that there is an intermediate gray background or if there is no image loaded and a "pop in" happens. You don't need to render through canvas, you just need the browser to not need a separate request. – zero298 Aug 12 '20 at 13:19
  • @zero298got it working with the hint of javascripts `promise`... Now the image change happens in about 100ms I would say, so acceptable... Now I'm trying to get a transition happening there, so the change is not so rough – nameless Aug 12 '20 at 13:52
  • If you're trying to cross-fade, you'll likely need 2 container elements. There isn't a clean way to do cross-fade with image backgrounds in pure CSS yet. That's why you'll likely need to mix a double-buffer-like solution with some signaler to tell you that the second buffer is loaded and ready to render. Then just fade the opacity on the "current" buffer/container. – zero298 Aug 12 '20 at 13:55
  • @zero298 hmmm... I found a solution with jQuery's `fadeTo` function, doing `$('body').fadeTo('slow', 0.3, function() { $(this).css("background","linear-gradient(rgba(0,0,0,.3), rgba(0,0,0,.3)),url('"+images[index]+"')"); }).fadeTo('slow', 1);`, however, then the hole content on the screen fades, rather then only the backgroundImage... – nameless Aug 12 '20 at 13:57

2 Answers2

0

Maybe the most simple would be to alternate between two containers that works like backgrounds :

HTML :

<body>
    <div class='bg' id='firstBg'></div>
    <div class='bg' id='secondBg'></div>

    <...Your Stuff...>

</body>

CSS :

body {
    background: transparent;
}

.bg {
    position: fixed;
    left: 0;
    top: 0;
    width: 100vw;
    height: 100vh;
    background-position: center;
    z-index: -1;
    background-size: cover;
    transition: 3s ease-in;
}

#secondBg {
    display: none;
}

JS :

setInterval(function(){
    $.post('getBackgroundImages.php', {  }, function(data) {
        var imageCollection = JSON.parse(data);
        var imageNumber = Math.floor(Math.random() * 100);
        var imageLink = imageCollection.hits[imageNumber].largeImageURL;
        if ($('#firstBg').css('display') == 'none') {
            $('#firstBg').css("background-image","url('"+imageLink+"')");
            $('#firstBg').fadeIn();
            $('#secondBg').fadeOut();
        }
        else {
            $('#secondBg').css("background-image","url('"+imageLink+"')");
            $('#secondBg').fadeIn();
            $('#firstBg').fadeOut(); 
        }
    });
},60000);
0

I did the following solution now, thanks to the hint of @zero298.

<script>
function loadImages (images) {
  // each image will be loaded by this function.
  // it returns a Promise that will resolve once
  // the image has finished loading
  let loader = function (src) {
    return new Promise(function (resolve, reject) {
      let img = new Image();
      img.onload = function () {
        // resolve the promise with our url so it is
        // returned in the result of Promise.all
        resolve(src);
      };
      img.onerror = function (err) {
        reject(err);
      };
      img.src = src;
    });
  };

  // create an image loader for each url
  let loaders = [];
  images.forEach(function (image) {
    loaders.push(loader(image));
  });

  // Promise.all will return a promise that will resolve once all of of our
  // image loader promises resolve
  return Promise.all(loaders);
}

 function cycleImages (images) {
    let index = 0;
    setInterval(function() {
      // since we need an array of the image names to preload them anyway,
      // just load them via JS instead of class switching so you can cut them
      // out of the CSS and save some space by not being redundant
      $('body').css("background","linear-gradient(rgba(0,0,0,.3), rgba(0,0,0,.3)),url('"+images[index]+"')");
      // increment, roll over to 0 if at length after increment
      index = (index + 1) % images.length;
    }, 28800000);
  }

$(function(){
    $.post('getBackgroundImages.php', {  }, function(data) {
        var imageCollection = JSON.parse(data);
        var imageNumber = Math.floor(Math.random() * 100);
        var imageLink = imageCollection.hits[imageNumber].largeImageURL;
        $('body').css("background","linear-gradient(rgba(0,0,0,.3), rgba(0,0,0,.3)),url('"+imageLink+"')");
    });
    $.ajax('getBackgroundImages.php',{
        success:function(data) {
            var parsed = JSON.parse(data);
            var images = parsed.hits;
            var imageUrls = [];
            images.forEach(function(item,index){
                imageUrls.push(item.largeImageURL);
            })
            loadImages(imageUrls).then(cycleImages).catch(function (err) {
                console.error(err);
            });
        }   
    });
});
</script>

This first puts all imageUrls into an array, then loads all images with Promise to then display then without a big delay.. I didn't really manage to get a nice transition between switching the images, as jQuerys fade-to method lets the content of the page fade out as well, rather then only the background image...

Adding more div's / changing the structure of the page is not too easy by the way, as there is a lot of floating and other css rules to make the elements appear on various positions on the page. Adding a div around all content in order to try to give that div the background-image destroyed the hole layout...

All in all, I'm confident with this solution, however, if anybody has a good idea to make the switch more smooth, feel free to tell me :)

nameless
  • 1,483
  • 5
  • 32
  • 78