190

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?

3
  • 4
    Just assign the same URL to Image() object which does have onload event. Commented Feb 20, 2011 at 15:43
  • 2
    make sure to wrap the css url in url() Commented Oct 17, 2012 at 18:55
  • Imagesloaded would do such thing imagesloaded.desandro.com Commented Jan 14, 2022 at 14:06

11 Answers 11

317

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})`; }); 
Sign up to request clarification or add additional context in comments.

16 Comments

Seems like the image would be loaded twice for no reason.
@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.
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.
Enter this in any page that have jquery: javascript:void($('<img/>').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.
A comparison between this test and this one shows that you have to call .remove() on $('<img/>') 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.
|
25

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>

5 Comments

Underrated solution.
@TomThomson thanks Tom, I found image transition didnt work properly, fixed it
This is a GREAT answer.
Interesting solution. What do you think about using the <link> tag to preload: <link rel="preload" href=cat.jpg as=image imagesrcset="cat.jpg 1x, cat-2x.jpg 2x">
@bytrangle this will increase loading priority which rarely needed
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); 

3 Comments

This plugin is also great for being able to show loading progress using the each callback.
@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 You're not using it correctly. The second callback is per image. Also, delay() doesn't work like that.
19

There are no JS callbacks for CSS assets.

2 Comments

Thanks for the fast answer. Any ideas for a workaround solution?
but there is JS solution for those who's looking stackoverflow.com/questions/5057990/…
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(); }); } }); 

3 Comments

hello ... this seems to work, though I set the bg.replace to bg.replace( ... , my_src ). But my question is: once the $('<img>') loads, what exactly happens with that <img> ... I mean it's not actually real - it's just a dummy placeholder, right?
Right. The <img> is never inserted into the DOM; it's just used to "fool" the browser into loading the image file into cache.
helpful if you don't want to invoke jQuery
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(); 

Comments

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

Comments

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

Comments

3

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

Comments

1

https://github.com/alexanderdickson/waitForImages

$('selector').waitForImages({ finished: function() { // ... }, each: function() { // ... }, waitForAll: true }); 

Comments

0

Here is my Blazor Flavor. It's a blocking JavaScript function that returns true or false. Optionally, there is a timeout parameter to avoid waiting for large images.

waitForImageCache: function (imgUrl, timeout = Infinity) { return new Promise((resolve, reject) => { const image = new Image(); const startTime = Date.now(); var imageEvaluated = false; // Image loaded. image.onload = function () { imageEvaluated = true; resolve(true); }; // Failed to download image into cache (image doesn't exist). image.onerror = function () { imageEvaluated = true; resolve(false); }; // Load the image source. image.src = imgUrl; if (timeout !== Infinity) { // Check periodically if the image has loaded or if the timeout has expired (Can't use a blocking while loop). const monitorInterval = 100; // Check every 100 milliseconds const monitorTimeout = () => { if (!imageEvaluated) { if (Date.now() - startTime > timeout) { resolve(false); } // Timeout expired else if (image.complete) { resolve(true); } // Image has loaded else { setTimeout(monitorTimeout, monitorInterval); } // Continue checking } }; // Start checking monitorTimeout(); } }); } 

Invoke it like so;

bool isCached = await _js.InvokeAsync<bool>("domHelpers.waitForImageCache", "some\image\url.png"); 

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.