191

I want to set a background image on the body tag, then run some code - like this:

$('body').css('background-image','http://picture.de/image.png').load(function() {
    alert('Background image done loading');
    // This doesn't work
});

How can I make sure the background image is fully loaded?

Wesley Murch
  • 101,186
  • 37
  • 194
  • 228
Peter
  • 11,413
  • 31
  • 100
  • 152

10 Answers10

314

try this:

$('<img/>').attr('src', 'http://picture.de/image.png').on('load', function() {
   $(this).remove(); // prevent memory leaks as @benweet suggested
   $('body').css('background-image', 'url(http://picture.de/image.png)');
});

this will create a new image in memory and use load event to detect when the src is loaded.

EDIT: in Vanilla JavaScript it can look like this:

var src = 'http://picture.de/image.png';
var image = new Image();
image.addEventListener('load', function() {
   body.style.backgroundImage = 'url(' + src + ')';
});
image.src = src;

it can be abstracted into handy function that return a promise:

function load(src) {
    return new Promise((resolve, reject) => {
        const image = new Image();
        image.addEventListener('load', resolve);
        image.addEventListener('error', reject);
        image.src = src;
    });
}

const image = 'http://placekitten.com/200/300';
load(image).then(() => {
   body.style.backgroundImage = `url(${image})`;
});
jcubic
  • 61,973
  • 54
  • 229
  • 402
  • 13
    Seems like the image would be loaded twice for no reason. – HyderA Feb 20 '11 at 16:42
  • 54
    @gAMBOOKa They are loaded once because they stay in browser memory. This is the same, when you put hidden images in top of your page and then set it in CSS — so images are start downloading before css file — it's called preloading. – jcubic Feb 20 '11 at 17:05
  • 6
    I am not too sure, but I think you're referring to cache. I don't think there's such a thing as browser memory where assets are stored. If you're referring to cache, remember that you're adding an extra HTTP request and all clients might not have cache enabled. – HyderA Feb 20 '11 at 17:09
  • 7
    Enter this in any page that have jquery: `javascript:void($('').attr('src', 'http://farm6.static.flickr.com/5049/5220175127_5693faf952.jpg').load(function() { $('html').css('background-image', 'url(http://farm6.static.flickr.com/5049/5220175127_5693faf952.jpg)'); }))` and check HTTP requests in Firebug. If I have opened flicker page with this image opened in another tab it don't do any HTTP requests just show this picture instantly. and when I clear the cache and run it there was one request. – jcubic Feb 20 '11 at 21:40
  • How would that work for a responsive site? Would you have to load all images at once? – jonasll Mar 30 '13 at 17:05
  • 28
    A comparison between [this test](http://jsfiddle.net/qe4nx/9/) and [this one](http://jsfiddle.net/qe4nx/10/) shows that you have to call `.remove()` on `$('')` object to avoid a memory leak. You should see a stable memory consumption on the first test and a memory increase on the second one. After few hours running on Chrome 28, the first test takes 80MB and the second one 270MB. – benweet Aug 13 '13 at 22:13
  • @Peter IMO: This entry does not answer the question but provides an alternative solution to address the problem. The question title should be changed. – hitautodestruct Oct 20 '14 at 08:50
  • Does anyone know if we need to check if(this.complete)? In case images are cached and fired before load() event handler? – Mladen Janjetovic Jan 26 '15 at 16:07
  • 1
    Would somebody mind explaining the memory-leak issue in more detail? – Inigo Mar 21 '15 at 20:52
  • 1
    @Inigo you should ask another question. – jcubic Mar 22 '15 at 12:56
  • 2
    Could you please add a description that how does this code work. It will be more helpful for the community, we should avoid answers that are just for copy and paste and won't add anything to the reader. Thanks :) – Mohammad Kermani Oct 30 '16 at 11:33
  • 1
    @Kermani the code is self explanatory, but I add some description. – jcubic Oct 31 '16 at 08:30
  • 1
    Beware! With Jquery 3.0 and onwards the above code will not work but instead bring you "indexOf.url is not a function". Luckily there is this [question](http://stackoverflow.com/questions/37738732/jquery-3-0-url-indexof-error/37915907#37915907) which set me on track. – Dawid Stróżak Apr 28 '17 at 12:16
  • 1
    @DawidStróżak thanks for pointing that out, changed the code to use `on('load'` – jcubic Apr 28 '17 at 12:59
  • I assume `$(this).remove()` removes the `onload` once it has run? – Ken Sharp Jul 07 '20 at 16:58
  • @KenSharp it remove the element, but I think it will also remove the event. See the comment by "benweet" it was his suggestion. – jcubic Jul 08 '20 at 09:56
23

I have a jQuery plugin called waitForImages that can detect when background images have downloaded.

$('body')
  .css('background-image','url(http://picture.de/image.png)')
  .waitForImages(function() {
    alert('Background image done loading');
    // This *does* work
  }, $.noop, true);
alex
  • 479,566
  • 201
  • 878
  • 984
  • This plugin is also great for being able to show loading progress using the `each` callback. – Soviut Oct 09 '13 at 04:42
  • 1
    @alex, How should I check each background image for each element in a class? I tried this but doesn't seem to do it right. $('.user_main_photo_thumb').waitForImages(function() { $(this).parents('.photoThumbFrame').delay(1000).slideDown(); }, function(loaded, count, success) { }); – Relm Mar 26 '15 at 08:20
  • @Relm You're not using it correctly. The second callback is per image. Also, `delay()` doesn't work like that. – alex Mar 29 '16 at 15:04
23

pure JS solution that will add preloader, set the background-image and then set it up for garbage collection along with it's event listener:

Short version:

const imageUrl = "https://www.google.com/images/branding/googlelogo/1x/googlelogo_color_272x92dp.png";
let bgElement = document.querySelector("body");
let preloaderImg = document.createElement("img");
preloaderImg.src = imageUrl;

preloaderImg.addEventListener('load', (event) => {
    bgElement.style.backgroundImage = `url(${imageUrl})`;
    preloaderImg = null;
});

A bit longer with nice opacity transition:

const imageUrl = "https://www.google.com/images/branding/googlelogo/1x/googlelogo_color_272x92dp.png";
let bgElement = document.querySelector(".bg-lazy");
bgElement.classList.add("bg-loading");
let preloaderImg = document.createElement("img");
preloaderImg.src = imageUrl;

preloaderImg.addEventListener('load', (event) => {
  bgElement.classList.remove("bg-loading");
  bgElement.style.backgroundImage = `url(${imageUrl})`;
  preloaderImg = null;
});
.bg-lazy {
  height: 100vh;
  width: 100vw;
  transition: opacity 1s ease-out;
}

.bg-loading {
  opacity: 0;
}
<div class="bg-lazy"></div>
godblessstrawberry
  • 4,556
  • 2
  • 40
  • 58
19

There are no JS callbacks for CSS assets.

Adrian Pacala
  • 1,011
  • 1
  • 8
  • 12
16

Something like this:

var $div = $('div'),
  bg = $div.css('background-image');
  if (bg) {
    var src = bg.replace(/(^url\()|(\)$|[\"\'])/g, ''),
      $img = $('<img>').attr('src', src).on('load', function() {
        // do something, maybe:
        $div.fadeIn();
      });
  }
});
Colin
  • 2,109
  • 1
  • 20
  • 22
  • hello ... this seems to work, though I set the bg.replace to `bg.replace( ... , my_src )`. But my question is: once the $('') loads, what exactly happens with that `` ... I mean it's not actually real - it's just a dummy placeholder, right? – dsdsdsdsd May 17 '13 at 23:44
  • Right. The `` is never inserted into the DOM; it's just used to "fool" the browser into loading the image file into cache. – Colin Jun 04 '13 at 19:35
  • helpful if you don't want to invoke jQuery – vwvan Jun 23 '13 at 19:59
7

I've located a solution that worked better for me, and which has the advantage of being usable with several images (case not illustrated in this example).

From @adeneo's answer on this question :

If you have an element with a background image, like this

<div id="test" style="background-image: url(link/to/image.png)"><div>

You can wait for the background to load by getting the image URL and using it for an image object in javascript with an onload handler

var src = $('#test').css('background-image');
var url = src.match(/\((.*?)\)/)[1].replace(/('|")/g,'');

var img = new Image();
img.onload = function() {
    alert('image loaded');
}
img.src = url;
if (img.complete) img.onload();
Paperjuice
  • 69
  • 1
  • 3
6

Here is a small plugin I made to allow you to do exactly this, it also works on multiple background images and multiple elements:

Read the article:

http://catmull.uk/code-lab/background-image-loaded/

or go straight to the plugin code:

http://catmull.uk/downloads/bg-loaded/bg-loaded.js

So just include the plugin and then call it on the element:

<script type="text/javascript" src="http://catmull.uk/downloads/bg-loaded/bg-loaded.js"></script>
<script type="text/javascript">
   $('body').bgLoaded();
</script>

Obviously download the plugin and store it on your own hosting.

By default it adds an additional "bg-loaded" class to each matched element once the background is loaded but you can easily change that by passing it a different function like this:

<script type="text/javascript" src="http://catmull.uk/downloads/bg-loaded/bg-loaded.js"></script>
<script type="text/javascript">
   $('body').bgLoaded({
      afterLoaded : function() {
         alert('Background image done loading');
      }
   });
</script>

Here is a codepen demonstrating it working.

http://codepen.io/catmull/pen/Lfcpb

Jon Catmull
  • 11,873
  • 5
  • 19
  • 17
3

I did a pure javascript hack to make this possible.

<div class="my_background_image" style="background-image: url(broken-image.jpg)">
<img class="image_error" src="broken-image.jpg" onerror="this.parentElement.style.display='none';">
</div>

Or

onerror="this.parentElement.backgroundImage = "url('image_placeHolder.png')";

css:

.image_error {
    display: none;
}
Gino
  • 1,834
  • 2
  • 19
  • 20
2

Here is a simple vanilla hack ~

(function(image){
  image.onload = function(){ 
                   $(body).addClass('loaded-background');
                   alert('Background image done loading');
                   // TODO fancy fade-in
                 };
  image.src    = "http://picture.de/image.png";
})(new Image());
redolent
  • 4,159
  • 5
  • 37
  • 47
1

https://github.com/alexanderdickson/waitForImages

$('selector').waitForImages({
    finished: function() {
       // ...
    },
    each: function() {
       // ...
    },
    waitForAll: true
});
yazabara
  • 1,253
  • 4
  • 21
  • 39