支持事件的图像预加载JavaScript

9
我正在寻找一个图片预加载脚本。 虽然我找到了几个,但它们都不支持在预加载完成时触发事件。 有没有人知道任何可以做到这一点的脚本或jQuery插件? 希望这个问题适合放在stackoverflow上 - 如果不是,请随时将其删除。
4个回答

35

这是一个函数,它可以从数组中预加载图像,并在最后一个图像加载完成时调用您的回调函数:

function preloadImages(srcs, imgs, callback) {
    var img;
    var remaining = srcs.length;
    for (var i = 0; i < srcs.length; i++) {
        img = new Image();
        img.onload = function() {
            --remaining;
            if (remaining <= 0) {
                callback();
            }
        };
        img.src = srcs[i];
        imgs.push(img);
    }
}

// then to call it, you would use this
var imageSrcs = ["src1", "src2", "src3", "src4"];
var images = [];

preloadImages(imageSrcs, images, myFunction);

现在我们已经进入使用Promise进行异步操作的时代,因此这是上述内容的一个版本,它使用Promise并通过ES6标准的Promise通知调用者:

function preloadImages(srcs) {
    function loadImage(src) {
        return new Promise(function(resolve, reject) {
            var img = new Image();
            img.onload = function() {
                resolve(img);
            };
            img.onerror = img.onabort = function() {
                reject(src);
            };
            img.src = src;
        });
    }
    var promises = [];
    for (var i = 0; i < srcs.length; i++) {
        promises.push(loadImage(srcs[i]));
    }
    return Promise.all(promises);
}

preloadImages(["src1", "src2", "src3", "src4"]).then(function(imgs) {
    // all images are loaded now and in the array imgs
}, function(errImg) {
    // at least one image failed to load
});

这里有一个使用2015年jQuery promises的版本:

function preloadImages(srcs) {
    function loadImage(src) {
        return new $.Deferred(function(def) {
            var img = new Image();
            img.onload = function() {
                def.resolve(img);
            };
            img.onerror = img.onabort = function() {
                def.reject(src);
            };
            img.src = src;
        }).promise();
    }
    var promises = [];
    for (var i = 0; i < srcs.length; i++) {
        promises.push(loadImage(srcs[i]));
    }
    return $.when.apply($, promises).then(function() {
        // return results as a simple array rather than as separate arguments
        return Array.prototype.slice.call(arguments);
    });
}

preloadImages(["src1", "src2", "src3", "src4"]).then(function(imgs) {
    // all images are loaded now and in the array imgs
}, function(errImg) {
    // at least one image failed to load
});

2
为了完善这个函数,你可以添加onerror处理程序、onabort处理程序和超时设置,这样即使其中一个或多个图像加载出现问题且无法成功完成,你仍然可以调用回调函数。如果图像没有加载错误,它将正常工作。 - jfriend00
是的,那很不错。我本来想展示的,但是我做得不够好。 - Simon
添加了preloadImages()函数的版本,使用了Promise,包括标准的ES6 Promise和jQuery Promise。 - jfriend00
在我看来,新添加的内容似乎缺少img.src = src;,这应该在var img = new Image();之后添加。 - Andris Zalitis

2

为了更稳健的解决方案,请考虑使用带有一些回调函数的PRELOADER函数(jsFiddle)。

保持简单:

在这个例子中,我将回调和图像哈希传递到Object字面量PRELOADER_OBJECT中,然后在PRELOADER中覆盖回调函数:

// preloder object stores image hash
// and event handler callbacks
var PRELOADER_OBJECT = {

    imgArray:"http://torwars.com/wp-content/uploads/2012/02/chewbacca-w-han-solo-anh.jpg http://torwars.com/wp-content/uploads/2012/02/chewbacca-w-han-solo-anh.jpg http://torwars.com/wp-content/uploads/2012/02/chewbacca-w-han-solo-anh.jpg http://torwars.com/wp-content/uploads/2012/02/chewbacca-w-han-solo-anh.jpg http://torwars.com/wp-content/uploads/2012/02/chewbacca-w-han-solo-anh.jpg http://torwars.com/wp-content/uploads/2012/02/chewbacca-w-han-solo-anh.jpg http://torwars.com/wp-content/uploads/2012/02/chewbacca-w-han-solo-anh.jpg http://torwars.com/wp-content/uploads/2012/02/chewbacca-w-han-solo-anh.jpg http://torwars.com/wp-content/uploads/2012/02/chewbacca-w-han-solo-anh.jpg http://torwars.com/wp-content/uploads/2012/02/chewbacca-w-han-solo-anh.jpg http://torwars.com/wp-content/uploads/2012/02/chewbacca-w-han-solo-anh.jpg http://torwars.com/wp-content/uploads/2012/02/chewbacca-w-han-solo-anh.jpg http://torwars.com/wp-content/uploads/2012/02/chewbacca-w-han-solo-anh.jpg http://torwars.com/wp-content/uploads/2012/02/chewbacca-w-han-solo-anh.jpg http://torwars.com/wp-content/uploads/2012/02/chewbacca-w-han-solo-anh.jpg http://torwars.com/wp-content/uploads/2012/02/chewbacca-w-han-solo-anh.jpg http://torwars.com/wp-content/uploads/2012/02/chewbacca-w-han-solo-anh.jpg http://torwars.com/wp-content/uploads/2012/02/chewbacca-w-han-solo-anh.jpg http://torwars.com/wp-content/uploads/2012/02/chewbacca-w-han-solo-anh.jpg http://torwars.com/wp-content/uploads/2012/02/chewbacca-w-han-solo-anh.jpg http://torwars.com/wp-content/uploads/2012/02/chewbacca-w-han-solo-anh.jpg http://torwars.com/wp-content/uploads/2012/02/chewbacca-w-han-solo-anh.jpg http://torwars.com/wp-content/uploads/2012/02/chewbacca-w-han-solo-anh.jpg http://torwars.com/wp-content/uploads/2012/02/chewbacca-w-han-solo-anh.jpg http://torwars.com/wp-content/uploads/2012/02/chewbacca-w-han-solo-anh.jpg http://torwars.com/wp-content/uploads/2012/02/chewbacca-w-han-solo-anh.jpg http://torwars.com/wp-content/uploads/2012/02/chewbacca-w-han-solo-anh.jpg http://torwars.com/wp-content/uploads/2012/02/chewbacca-w-han-solo-anh.jpg http://torwars.com/wp-content/uploads/2012/02/chewbacca-w-han-solo-anh.jpg http://torwars.com/wp-content/uploads/2012/02/chewbacca-w-han-solo-anh.jpg http://torwars.com/wp-content/uploads/2012/02/chewbacca-w-han-solo-anh.jpg http://torwars.com/wp-content/uploads/2012/02/chewbacca-w-han-solo-anh.jpg http://torwars.com/wp-content/uploads/2012/02/chewbacca-w-han-solo-anh.jpg http://torwars.com/wp-content/uploads/2012/02/chewbacca-w-han-solo-anh.jpg http://torwars.com/wp-content/uploads/2012/02/chewbacca-w-han-solo-anh.jpg http://torwars.com/wp-content/uploads/2012/02/chewbacca-w-han-solo-anh.jpg http://torwars.com/wp-content/uploads/2012/02/chewbacca-w-han-solo-anh.jpg http://torwars.com/wp-content/uploads/2012/02/chewbacca-w-han-solo-anh.jpg http://torwars.com/wp-content/uploads/2012/02/chewbacca-w-han-solo-anh.jpg http://torwars.com/wp-content/uploads/2012/02/chewbacca-w-han-solo-anh.jpg http://torwars.com/wp-content/uploads/2012/02/chewbacca-w-han-solo-anh.jpg http://torwars.com/wp-content/uploads/2012/02/chewbacca-w-han-solo-anh.jpg http://torwars.com/wp-content/uploads/2012/02/chewbacca-w-han-solo-anh.jpg http://torwars.com/wp-content/uploads/2012/02/chewbacca-w-han-solo-anh.jpg http://torwars.com/wp-content/uploads/2012/02/chewbacca-w-han-solo-anh.jpg".split(" "),

    progressCallback : function( percent )
    {
        $( '#preloader_progress' ).html( 'preload progress complete : ' + percent + '%' );
        console.log( 'preload progress complete : ', percent );
    },

    completeCallback : function( scope )
    {
        // hide preload indicator, do something when finished
        console.log( 'preload complete!' );
        $( '#preloader_modal' ).delay( 1000 ).animate( { opacity : 0 }, function( )
        {
            $( '.preload_class' ).each( function( index )
            {
                $( this ).delay( index * 100 ).animate( { opacity : 0 } );
            } );
        } );
    }

/*Localize params and create PRELOADER object. 
Needs to loadImages( ); iterate through hash and 
call onPreloadProgress( ) and onPreloadComplete( )
each time until finished. If you're still within
bounds of the image hash, call progressCallback( )
recursively. When finished, fire onCompleteCallback( )*/

var PRELOADER = function( object )
{
    // preloader modal overlay
    this.modal = undefined;

    // progress indicator container
    this.progressIndicator = undefined;

    // image preload progress
    this.progress = undefined;

    // progress callback
    this.progressCallback = undefined;

    // complete callback
    this.completeCallback = undefined;

    // hash to store key : value pairs for image paths
    this.imgArray = undefined; 

    // store images in preloadArray
    this.preloadArray = [];

    // initialize and localize our data
    this.initialize = function( )
    {
        // create preload indicator and overlay modal
        this.createPreloaderModal( );

        // image hash
        this.imgArray = object.imgArray;

        // progress callback event handler
        this.progressCallback = object.progressCallback;

        // complete callback event
        this.completeCallback = object.completeCallback;

        // load images
        this.loadImages( );
    };

    this.progressCallback = function( ) {}; // function to override

    this.completeCallback = function( ) {}; // function to override

    // load images into DOM and fire callbacks
    this.loadImages = function( )
    {
        var that = this;

        // iterate through hash and place images into DOM
        $.each( PRELOADER_OBJECT.imgArray, function( index, object )
        {
            this.image = $( "<img/>", { "src" : object, "class": "preload_class" } ).appendTo( 'body' );

            // mark progress and call progressCallback( ) event handler
            that.progress = Math.ceil( ( index / PRELOADER_OBJECT.imgArray.length ) * 100 );
            that.progressCallback( this.progress );

            that.preloadArray.push( this.image );
        } );

        // check for array bounds and call completeCallback( )
        if ( PRELOADER_OBJECT.imgArray.length )
        {
            this.progressCallback( 100 );
            this.completeCallback( this );
        }
    };

    // create modal to display preload data
    this.createPreloaderModal = function( )
    {
        this.modal = $( '<div/>', { 'id' : 'preloader_modal' } ).appendTo( 'body' );
        this.progressIndicator = $( '<h1/>', { 'id' : 'preloader_progress' } ).appendTo( this.modal );
    };
};

// trigger event chain when DOM loads
$( document ).ready( function( )
{    
    // instantiate PRELOADER instance and pass
    // our JSON data model as a parameter
    var preloader = new PRELOADER( PRELOADER_OBJECT );

    // initialize preloader
    preloader.initialize( );
} );

};​

如果网站加载足够大,需要图像预加载器,则模态文本显示可以轻松修改以支持数据驱动的jQuery动画。


0

预加载需要额外的工作,例如创建新的图像元素,监视它们是否全部加载完毕,并在DOM中用现有的元素替换它们。但是,您可以直接在DOM上多次执行此操作,而无需替换它们。

我们可以使用Fetch API访问图像,在promise.all()内等待它们全部下载,然后一次性使用window.requestAnimationFrame()在最合适的时间替换img元素的src属性。

在以下示例中,我刷新了img元素的src属性10次。根据延迟,我使用从API加载4张图片所需的时间。因此,一旦我们加载了所有图像,我立即通过递归调用相同的refreshImagesNTimes函数来发起新的请求。

当然,您可以选择一次加载任意数量的图像数据块,并通过使用简单的setTimeout机制在精确的时间间隔内分组显示它们。

function refreshImagesNTimes(nodeList,count = -1){
  var imgPromises = Array.from({length: nodeList.length})
                         .map(_ => fetch("https://unsplash.it/480/640/?random").then(res => res.blob()));
  Promise.all(imgPromises)
         .then(function(blobs){
                 window.requestAnimationFrame(_ => nodeList.forEach((img, i) => img.src = (window.URL || window.webkitURL).createObjectURL(blobs[i])));
                 --count && refreshImagesNTimes(nodeList, count);
               });
}

var images = document.querySelectorAll("#container img");
refreshImagesNTimes(images,10);
#container {
  display: flex;
  flex-wrap: wrap;
  justify-content: space-evenly;
  align-items: center;
  margin: auto;
  width: 75vw;
  height: 56.25vw;
  background-color: #000;
  box-sizing: border-box;
}

img {
  width: 45%;
  height: 45%;
  background-color: thistle;
}
<div id="container">
  <img>
  <img>
  <img>
  <img>
</div>


0

预加载和加载是同一件事。您可以插入图像(创建新图像或更改现有图像的“src”属性),但使用$("element").hide()或类似的方法隐藏元素。在执行此操作之前,请附加一个load事件处理程序,如下所示:

var images = ["src1", "src2", "src3", "src4"];
var len = images.length;

for(i=0; i<len; i++){
    $("parent element").html('<img id="new-img" style="display:none;" src="'+images[i]+'"/>');
    $("new-img").load(function(){
        //Your image is now "preloaded"

        //Now you can show the image, or do other stuff
        $(this).show();
    });
}

谢谢,Simon。我正在尝试为联系地图设置这个,其中有一些要显示的国家部分,它们每个都是PNG格式的,非常巨大。我想预加载整个内容,然后向其容器添加类名以使其可见。这就是全部。但是,附加一个回调来触发单个图像的加载并不是我要寻找的。 - SquareCat
“预加载”是什么意思?我猜你想在用户需要查看之前就将其加载,这样当他/她想要查看时,它不会花费太长时间。使用上面的代码来加载图像,一旦加载完成,您可以存储对它的引用,以便在用户需要查看时引用它。这有效地预加载了图像。 - Simon
谢谢,我明白,但这并不是我正在寻找的东西。我正在尝试找到一种脚本,可以提供一个图像源数组,它可以在后台加载它们,然后触发事件。 - SquareCat
每次一张图片加载完成,就执行 var++,如果 var == 预加载的图片数量,则运行“所有内容已加载”例程。 - Thilo Savage

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