dkern / jquery.lazy

A lightweight, fast, feature-rich, powerful and highly configurable delayed content, image and background lazy loading plugin for jQuery & Zepto.
http://jquery.eisbehr.de/lazy
Other
1.02k stars 237 forks source link

Images may not be completely loaded when afterLoad is called #222

Open mgreter opened 4 years ago

mgreter commented 4 years ago

As the title says, on afterLoad the images may not yet be completely loaded.

afterLoad: function(element){
    console.log('afterLoad', element[0].complete);
},

This is probably due to the use of an intermediate image object internally. Once that image is loaded the src attribute of the original image is set. But there is no guarantee that this image will be available immediately. I guess this is mostly due to UAs decoding images in a background thread today. There might be other circumstances that can trigger this race-condition.

I tested this with Chrome/78.0.3904.108 and http://ocbnet.ch/jqlazy/ To trigger it I only have to scroll from the top of the page. If the threshold is already met on load it seems to be ok. To circumvent this I added another check in afterLoad:

function imageLoaded(image) {
    return new Promise(function(resolve, reject) {
        if(image.complete) {
            resolve();
        }
        function unbind() {
            image.removeEventListener('load', loaded);
            image.removeEventListener('error', rejected);
        }
        const loaded = function() { unbind(); resolve(); };
        const rejected = function() { unbind(); reject(); };
        image.addEventListener('load', loaded);
        image.addEventListener('error', rejected);
    });
}

afterLoad: function(element){
    console.log('afterLoad', element[0].complete);
    imageLoaded(element[0]).then(function() {
        console.log('Finally loaded it', element[0].complete);
    });
},

image

IMHO it would be great if this could be integrated into this plugin. Or at least state this race-condition clearly in the documentation. AFAICT for background-images this might not be solvable cleanly.

dkern commented 4 years ago

I think you just confused two things. The after load of the image object and the after load (or maybe after handle) of Lazy. The callback is just executed after Lazy does it's job. But i totally understand that in this case the name "load" is confusing and as you stated not true. When i have time i will think about that behavior, but this could have a bigger impact to the whole handling.

Thanks for your submission!

mgreter commented 4 years ago

Thanks for the reply, as I mentioned it would at least be great to document this somehow. I guess one can easy get the impression that this would be the case as it mostly does, so it can be hard to debug any regressions that stem from this edge condition.

But it also seems I can only trigger this in chrome when I disable caching (probably also the case when server doesn't allow caching, the images are also downloaded twice as shown in the network tab). It looks like .on('load', function() { ... }) also works out of the box.

FWIW this pretty much describes the same issue: https://github.com/nolimits4web/swiper/issues/3017

AFAICT there is also a small documentation error on the main page as seen below:

image

The bug we're currently investigating is on iOS, but I thought this might be the main reason why it happens. Will probably see tomorrow when we test it on a real device. So hopefully we just have to use another (already existing) callback.

Anyway, maybe you can come up with some solution like re-using the image object or adding the intermediate image to the DOM instead of "transferring" the src attribute. Not sure if HTMLImageElement.decode() could be of any help here and converting it to a Base64 URL might be to CPU intensive.

Thanks und einen schönen Abend noch!