javascript - Preîncărcarea imaginilor de fundal


Construiesc o pagină care trece prin 3 fundaluri diferite, schimbându-se la fiecare 750ms. Pentru a face asta, adaug o clasă la body cu imaginea de fundal corespunzătoare, schimbându-le cu JS. La prima trecere, acestea clipesc pentru că imaginea trebuie să se încarce, deci nu este disponibilă instantaneu. Prin urmare, există metode pe care le pot folosi pentru a preîncărca imaginile?

CSS :

&.backgrounds{
    background-position: center bottom;
    background-repeat: no-repeat;
    background-size: 130%;

    &.freddy{
        background-image: url(/img/illustrations/snapchat/snapchat_holding_page_freddy.jpg);
    }

    &.irene{
        background-image: url(/img/illustrations/snapchat/snapchat_holding_page_irene.jpg);
    }

    &.joe{
        background-image: url(/img/illustrations/snapchat/snapchat_holding_page_joe.jpg);
    }
}

JS :

setInterval(function() {
    if ( $('.backgrounds').hasClass('freddy') ){
        $('.backgrounds').removeClass('freddy').addClass('irene');

    } else if ( $('.backgrounds').hasClass('irene') ){
        $('.backgrounds').removeClass('irene').addClass('joe');

    } else if ( $('.backgrounds').hasClass('joe') ){
        $('.backgrounds').removeClass('joe').addClass('freddy');

    }
}, 750);

Răspuns #1

Aș face ceva de genul acesta. loadImages returnează o Promise care se va rezolva odată ce toate imaginile sunt încărcate. .then atașat la aceasta apelează cycleImages, care pornește intervalul. Deoarece vei avea nevoie de URL-urile în JS oricum pentru a face preîncărcarea, în loc de schimbarea claselor, manipulez direct background-image, în felul acesta poți elimina URL-urile imaginilor din CSS și economisi câțiva octeți redundanți. De asemenea, face mai ușoară și extinderea listei de imagini în viitor; trebuie doar să adaugi un element în array-ul de imagini în loc să menții o declarație if complicată.

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);
}


// the images we are going to display
let myImages = [
  'http://www.gifpng.com/400x200',
  'http://www.gifpng.com/400x200/ffffff/000000',
  'http://www.gifpng.com/400x200/000000/ffffff'
];

// $(document).ready(fn) is deprecated,
// use the $(fn) form instead
$(function() {

  // after the images are loaded this will be called with an array of the loaded images
  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
      $('#backgrounds').css('backgroundImage', 'url("' + images[index] + '")');
      // increment, roll over to 0 if at length after increment
      index = (index + 1) % images.length;
    }, 750);
  }


  // load the images and start cycling through them after they are loaded
  loadImages(myImages).then(cycleImages).catch(function (err) {
    console.error(err);
  });
});
#backgrounds {
  height: 200px;
  width: 400px;
  border: 1px solid #000;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="backgrounds"></div>

Editare: Luând în considerare doar comentariile unui student, iată o versiune care va schimba imaginea doar după ce aceasta este încărcată, dar o va face imediat dacă este deja încărcată. De asemenea, sare peste imaginile care nu au reușit să se încarce. Deoarece cycleImages nu mai este apelat prin .then, l-am modificat astfel încât să accepte un element țintă (ca obiect jQuery) împreună cu un array de promisiuni ale imaginilor. Astfel, poți folosi ușor această funcție în mai multe locuri de pe o pagină cu seturi diferite de imagini, dacă dorești.

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
        resolve(src);
      };
      img.onerror = function (err) {
        reject(err);
      };
      img.src = src;
    });
  };

  // return an array of image-loading promises
  return images.map(function (image) {
    return loader(image);
  });
}


// the images we are going to display
let myImages = [
  'http://www.gifpng.com/400x200',
  'http://www.invalid-domain-name.foo/this-url-certainly-does-not-exist.jpg',
  'http://www.gifpng.com/400x200/ffffff/000000',
  'http://www.gifpng.com/400x200/000000/ffffff'
];

// $(document).ready(fn) is deprecated,
// use the $(fn) form instead
$(function() {

  // this receives an array of the promises for each image
  function cycleImages ($target, images) {
    let index = 0,
      interval = 750; // how many ms to wait before attempting to switch images

    function nextImage () {
      // p is the promise for the current image
      let p = images[index],
        next = function (wait) {
          // increment our counter and wait to display the next one
          index = (index + 1) % images.length;
          setTimeout(nextImage, wait);
        };

      // wait for this image to load or fail to load
      p.then(function (src) {
        // it loaded, display it
        $target.css('backgroundImage', 'url("' + src + '")');
        next(interval);
      }).catch(function (err) {
        // this one failed to load, skip it
        next(0);
      });

    }

    // start cycling
    nextImage();
  }


  // load the images and start cycling through them as they are loaded
  cycleImages($('#backgrounds'), loadImages(myImages));
});
#backgrounds {
  height: 200px;
  width: 400px;
  border: 1px solid #000;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="backgrounds"></div>

În loc să codificați direct intervalul dintre schimbările imaginilor, ați putea să îl transmiteți și ca parametru. În acel punct, totuși, aș refactoriza pentru a folosi un obiect de configurare pentru a transmite totul, cu excepția array-ului de promisiuni pentru imagini: cycleImages(myImages, {target: $ ('#backgrounds'), interval: 1000});

Răspunsul #2

Le poți încărca în javascript. De exemplu:

(new Image()).src = url1;
(new Image()).src = url2;
(new Image()).src = url3;

Schimbați „url1”, „url2”, „url3” cu propriile URL-uri ale imaginilor. Browserul va încărca imaginea, dar acestea nu vor fi vizibile nicăieri.