预加载背景图片

6
我正在构建一个页面,循环播放3个不同的背景,每750毫秒更换一次。为了实现这个功能,我会使用JS向body添加一个带有相应背景图片的class。在第一次循环时,由于需要加载图片,所以它们会闪烁。因此,有没有什么方法可以预加载图片呢?
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);
4个回答

6
我会这样做。`loadImages` 返回一个 Promise,在所有图片加载完成后将解决。附加到它的 `.then` 调用了 `cycleImages`,从而启动间隔。由于你需要在 JS 中使用 URL 进行预加载,因此我直接操作 `background-image` 而不是切换类,这样你可以从 CSS 中删除图像 URL 并节省一些冗余字节。这也使得未来扩展图像列表更容易,您只需要将项目添加到图像数组中而不是维护一个复杂的 if 语句。

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>

编辑: 考虑到Just a student的评论,这里有一个版本只会在图片被加载后才切换,但如果已经加载,则立即执行。它还跳过了加载失败的图片。由于cycleImages不再通过.then调用,因此我还将其更改为接受目标元素(作为jQuery对象)以及图像Promise数组。这样,如果您想要在页面上的多个位置使用不同的图像集,就可以轻松使用它。

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>

不要硬编码图像变化之间的间隔,你也可以将其作为参数传递。但在那时,我会重构它以使用配置对象来传递除图像承诺数组外的所有内容:cycleImages(myImages, {target: $('#backgrounds'), interval: 1000});


一点小建议,您可能想考虑将间隔更改为每几秒钟而不是每750毫秒。大多数人会对那么快闪过的东西感到非常恼火。作为一个有注意力缺陷障碍的人,我可以告诉您,在页面上快速闪烁的东西会让人很难集中注意力阅读页面上的任何内容;它只会让人分心。即使是没有注意力缺陷障碍的人也可能会因分心而受到影响。 - Useless Code
很棒的答案!一个建议:目前,所有图像都在第一张显示之前加载。一个不错的替代方案是只在需要时加载图像,并延迟切换直到 Promise 解决。这意味着对于第一个循环,您不能使用间隔,而是需要确保(a)已经过了750毫秒(或您设置的任何时间),并且(b)下一个图像已加载。实际上,这也可以很好地用 Promises 来完成。 - Just a student
哦,当前解决方案的另一个缺点是,即使只有一个背景图像无法访问,也将不会显示任何背景图像。 - Just a student
@Justastudent,根据您的建议,我添加了一种替代方案。感谢您的贡献,它帮助我思考如何改进。我喜欢这个方案让我想到的东西 :-) - Useless Code

1
你可以在 JavaScript 中加载它们。例如:

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

用你自己的图片url替换"url1","url2"和"url3"。浏览器将加载这些图片,但它们不会在任何地方显示。

0

有趣的想法。

你可以潜在地加载它们,就像这样:

<div class="hidden-images">
    <img src="img1.jpg" />
    <img src="img2.jpg" />
    <img src="img3.jpg" />
</div>

然后在CSS中

.hidden-images {
    position: relative;
    z-index: 1;
 }

然后在您所使用的主容器 div 上添加以下内容?

 .main-container {
     position: relative;
     z-index: 2; // Or higher, whatever is needed
 }

0

您也可以使用不同的HTML和CSS结构来实现此操作。

HTML

<div class="backgrounds">
  <div class="background freddy"></div>
</div>

CSS

.backgrounds{
    background-color: transparent;
    background-position: center bottom;
    background-repeat: no-repeat;
    background-size: 130%;
    height: 250px;
    width; 100%;
}

.backgrounds .freddy{
        height: 100%;
        width: 100%;
        background-image: url('http://adrianweb.net/includes/images/hero.jpg');
    }

.backgrounds .irene{
         height: 100%;
        width: 100%;
        background-image: url('http://adrianweb.net/includes/images/projects-blur.png');
    }

.backgrounds .joe{
         height: 100%;
        width: 100%;
        background-image: url('http://adrianweb.net/includes/images/contacts-blur.png');
    }

JS

$(document).ready(function(){
setInterval(function() {
    if ( $('.background').hasClass('freddy') ){
        $('.background').removeClass('freddy').addClass('irene');

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

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

    }
}, 750);
});

也许你可以添加过渡/动画效果,使其看起来淡入淡出。
以下是示例代码:

http://codepen.io/adrianrios/pen/yMEpEv


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接