Detect When an Image Is Loaded in jQuery

Detecting when dynamically loaded images are completely loaded in jQuery can be tricky due to browser caching and asynchronous rendering. Standard load event listeners often fail if an image is retrieved from the browser cache before the event handler is bound. This guide explains the most reliable techniques for ensuring an image is fully loaded, covering both newly created elements and dynamically updated existing elements.

The Problem with Simple .on('load')

Binding only a standard load handler leads to race conditions:

// Unreliable for cached images
$('#myImage').on('load', function() {
    console.log('Image loaded');
}).attr('src', 'new-image.jpg');

If the image is already cached, some browsers will not trigger the load event, causing the callback to never execute.

The Most Robust Method: Check .complete and naturalWidth

The most reliable approach combines binding the load event with checking the HTMLImageElement's native complete property and naturalWidth attribute. This guarantees execution whether the image loads from the network, returns from cache, or fails.

function onImageReady($img, callback) {
    var img = $img[0];

    // If the image is already loaded and valid
    if (img.complete && img.naturalWidth !== 0) {
        callback.call(img);
        return;
    }

    // Otherwise, listen for the load or error event once
    $img.one('load', function() {
        callback.call(this);
    }).one('error', function() {
        console.error('Failed to load image:', img.src);
    });
}

Loading Newly Created Images

When creating an <img> element entirely from scratch in jQuery, ensure all event handlers are attached before the src attribute is assigned:

$('<img />')
    .one('load', function() {
        console.log('Image is ready:', this.src);
        $('#container').append(this);
    })
    .one('error', function() {
        console.error('Image failed to load.');
    })
    .attr('src', 'https://example.com/photo.jpg');

By defining the load listener prior to setting src, you prevent race conditions where the image loads synchronously from the cache before the listener is registered.

Handling Asynchronous Workflows with Promises

To integrate dynamic image loading into modern asynchronous workflows, wrap the detection logic in a Promise:

function loadImage(src) {
    return new Promise(function(resolve, reject) {
        $('<img>')
            .one('load', function() {
                resolve(this);
            })
            .one('error', function(err) {
                reject(new Error('Image failed to load: ' + src));
            })
            .attr('src', src);
    });
}

// Usage
loadImage('https://example.com/photo.jpg')
    .then(function(img) {
        $('#gallery').append(img);
    })
    .catch(function(err) {
        console.error(err);
    });

This Promise-based pattern offers a clean, non-blocking interface that works smoothly with async/await and eliminates cached-image bugs across all modern browsers.