Lazysizes: 'lazyafterunveil' callback?

Created on 3 Jul 2015  Â·  17Comments  Â·  Source: aFarkas/lazysizes

I can't seem to find anything to this effect in the documentation.

Is it possible to have a callback fire after the image has been lazyloaded?

Most helpful comment

I have next structure:

<picture>
<source srcset="path" data-srcset="path">
<img src="path_to_preview" class="img-fluid lazyload">
</picture>

path_to_preview - path to small preview.
lazybeforeunveil - fires even if image (path from srcset) still loads, because <img> has small image, once this small image loaded - this event fires, but in fact image shows later (once loaded from srcset).

It's unreal to use lazybeforeunveil or .load for to check if image loaded - these just does not works or works wrong...

So it would be really great to have event like 'imageloaded' :-)

All 17 comments

No, from my experience you don't need it. What is your (basic) use case? In general all you need is lazybeforeunveil to modify the transformation or load to be informed when the image is loaded.

In some specific circumstances you could use setImmediate/setTimeout inside a lazybeforeunveil``.

Might be a bit of an edge case, but I'm using img with srcset for my hero backgrounds, and I've found that in certain cases the width/height of the image and the width/height of the container do not play nice, so I'm toggling a class on the images on window resize and lazybeforeunveil to adjust for this.

However, I am finding the behaviour quite inconsistent with lazybeforeunveil and would ideally have it trigger once the image has been lazy loaded.

Would load on the image itself trigger whenever a new image from the srcset is loaded, or just when the placeholder loads?

The behavior of lazybeforeunveil is quite consistent from a programming view. It is the right time to change/influence the data-src/data-srcset/data-* transformation.

I think load should be the right event for you. This event is triggered everytime a new image is loaded.

Some general question:
Are you using data-sizes="auto" or can't you solve your problem with CSS? Because it sounds like a layout problem. Maybe I can help more if I fully understand your problem (maybe a testcase?)

Experimented a bit and load definitely sorted the issue out for me. Initially I wasn't sure that it would trigger properly with srcset, but it definitely is. Thanks for the explanation!

To answer your questions, I am using data-sizes="auto", and the fundamental layout issue is that the aspect ratio of the container changes entirely for tablet-sized screens with portrait orientation. The ideal solution is to use picture instead of just image with srcset, but there are certain technical restrictions with the project preventing me from doing so right now. It's an edge case, at best.

Thanks for your help and time :)

Dear aFarkas,

thanks a lot for offering this gorgeous piece of Javascript. It works awesomely.

However, I'm using it in quite a complex application and I'm encountering an issue with another library, sudoSlider. The slider gets confused by the lazy image loading, making it show slides at the wrong moment. Since there is no way to reliably know when lazysizes is done loading images, I'm currently refreshing the slider on lazybeforeunveil inside a setTimeout.

Your README suggests to listen for the native load event of the images, but as you surely know, reliably detecting when images have been loaded is a complex thing.

For this purpose, it would be great if you could trigger a lazyafterunveil event just after unveiling an element. People could register to it and fix things right after the unveil.

Cheers, Dominik


Edit: my code (Coffeescript)

   $sliderContainer.on 'lazybeforeunveil', ->
     setTimeout ->
       sudoSlider.adjust()
       console.log 'adjusted slider'

@codener
Not sure what your exact problem is. And what the proposed solution should be. Should lazyafterunveil be triggered right after the transformation or if the image is loaded? You say it's about onload, but you use a setTimeout?

I know the bugs regarding the load event and especially the complete property very well. And this is why, I can ensure that the following pattern should just work (if really the onload thing is your problem):

$('.sudo-slider').each(function(){
    var sudoSlider = $(this).sudoSlider({
        //your options
    });

    this.addEventListener('load', function(e){
        sudoSlider.adjust();
        console.log(e.target, 'image loaded');
    }, true);
});

The main problem in the past was to detect, wether an image has already loaded/completed before you have added your load event listener. The reason for this was, that the complete property was not implemented in all browsers. And in those which have implemented these, had bugs. But if an image isn't yet loaded and you add a load event handler , this handler is fired reliable in all browsers.

With other words, if you initialize the slider, images might have loaded or not. If an image has loaded the slider has the right image size for it, if not you catch the loading with the load event.

With other words, the known workarounds used to detect image loading in edge situations are quite over used. If you need to know whether an image is loaded for example to manipulate it a canvas you need these workarounds, because the image has to be loaded before you start your program.

If you create a slider on the other hand you don't need these workarounds, you can simple update as soon as a load comes in.

At the end: To my knowledge there is only one minor issue in IE11+ with the complete, that will only hit you in extreme edge cases with invalid HTML markup (no src attribute, reports complete=false, but should be complete=true). All other current browsers starting from IE9+, Safari 8+, Firefox 38+ have fixed the important bugs regarding the complete property. So it isn't that complicated anymore.

Dear @aFarkas, thanks for your reply.

I have tried listening to load and readjusting the slider before, but it did not work. Your snippet worked slightly better than my approach, though:

  • the true argument makes a difference in Chrome Deskop – without, the event handler is not run
  • it still does not work on iPad, for example
  • it does not work when I re-enable caching (which I only disabled for develoment); I'm reading on the web that the load event is only being triggered when an image is really _loaded_ from the internet

I believe the last point matters most for me: I need to be notified not only on real loads, but whenever lazysizes lazy-loads anything. It's because the slider needs to measure its slides and give them fixed dimensions; this does not work when the slide is empty (without image).

Maybe the noscript plugin I'm using has influence on this?

As for your questions: lazyafterunveil should be triggered after the unveil code has finished. Since transitions need to be implemented manually, I believe their handling should be too. I'm using setTimeout as a "hack" to have the sudoSlider.adjust() run right after the unveil (see my above comment: I've added my original code).

Thanks for your time. Cheers from Augsburg!

the true argument makes a difference in Chrome Deskop – without, the event handler is not run

This is not a bug. All browsers do it this way. The load is not bubbling and the third argument for the addEventListener method means, that you want to use capture mode instead of bubbling.

... I'm reading on the web that the load event is only being triggered when an image is really loaded from the internet

This is clearly a wrong conclusion. I already explained the answer above. The image was loaded before you have added the event handler. The load event happened already in the past.

You can detect this with the complete property:

function imageLoaded(img, callback){
    var onload;

    if(img.complete){
        callback(img);
        return;
    }

    onload = function(){
        img.removeEventListener('load', onload);
        img.removeEventListener('error', onload);
        callback(img);
    };
    img.addEventListener('load', onload);
    img.addEventListener('error', onload);
}

it still does not work on iPad, for example

Same as above. I assume it was cached and your code was executed after the image was loaded.

Inside lazysizes I use the load event to detect when an image loads, if this event would not work, I wouldn't be able to add the class lazyloaded like I do already.

Also google pagespeed's lazyloader uses the following technique to initialize lazyloading images:

<img src="low-quality.jpg" onload="initLazyLoading(this);" onerror="initLazyLoading(this);" />
<img src="data:,a" onload="initLazyLoading(this);" onerror="initLazyLoading(this);" />

Because the onload handler is there at the time the image is created, it will be always fired. As you see this even works with data uris in all browsers (... that support data uris). If this wouldn't work with cached images google would have a big problem.

This is also why I don't wan't to replicate this event, there is simply no need for this.


In case your setTimeout works even with cache disabled, it seems, that you doesn't actually need the load event at all. Your problem is not about the loaded images, it's about the DOM structure.

I'm little bit reluctant to add this event for you though. It's an edge case (your special needs in combination with noscript plugin) and the setTimeout is clearly a workaround, but it is not an async problem with a race condition that might work sometimes or not. It will always work. Because lazybeforeunveil is fired right before it is done. Also this will add some confusion for other developer seeking for the "missing" load event.

Maybe you add it yourself and simply abstract this ugliness away:

(function(){
    'use strict';
    var after = window.Promise && Promise.resolve ? Promise.resolve() : {then: function(fn){setTimeout(fn);}};

    $(window).on('lazybeforeunveil', function(e){
        after.then(function(){
            $(e.target).trigger('lazyafterunveil');
        });
    });
})();

Thanks for your detailed explanation.
I will simply stick with the setTimeout trick now, as it is too complicated for me to hook into the load event. That's not too bad.

I am now also looking for an lazyafterunveilevent.
I need to to stuff only after / when lazysizes added the class lazyloaded to the img.

Is there by now a way to listen for this event?

lazysizes sets the lazyloaded class after the load event is fired. lazysizes does not use any magic here. If lazysizes can listen for the native load event you can probably too.

Not sure if this is quite the same issue...

I am trying to get the actual height of an image after it has lazyloaded. Something like

newHeight = $(this).find("img.lazyloaded").height();

...but the height it returns is wrong, much too small.

For example I have an image that I know is 300px high after it has properly loaded, but the code above says it is 24px high.

Is there a better way to get the final height of a lazyloaded image?

Thanks, great plugin.

as there is no such JS event, that fires when a class is added, it is not possible, to know when the class lazyloaded is added.

That is why some people like me need an event like lazyafterunveil or lazyloaded that triggers for each specific image.

@Metis77
lazySizes adds the class if the browser triggers a load event on the img.

So there is clearly an event to hook into.

I have next structure:

<picture>
<source srcset="path" data-srcset="path">
<img src="path_to_preview" class="img-fluid lazyload">
</picture>

path_to_preview - path to small preview.
lazybeforeunveil - fires even if image (path from srcset) still loads, because <img> has small image, once this small image loaded - this event fires, but in fact image shows later (once loaded from srcset).

It's unreal to use lazybeforeunveil or .load for to check if image loaded - these just does not works or works wrong...

So it would be really great to have event like 'imageloaded' :-)

also looking for lazyafterunveil, so i can trigger reinit on a dynamic positioned elements.. hopefully this will be udpated soon.. it would really be a great feature

also looking for lazyafterunveil, so i can trigger reinit on a dynamic positioned elements.. hopefully this will be udpated soon.. it would really be a great feature

This doesn't sound how you should do things. But there is already an event for it. Simply RTFM!

lazyloaded: After the image is fully loaded lazysizes dispatches a lazyloaded event. While this often duplicates the native load event it is often more convenient to use.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

virginiarcruz picture virginiarcruz  Â·  3Comments

ymantijunk picture ymantijunk  Â·  5Comments

obzenner picture obzenner  Â·  5Comments

zenderol picture zenderol  Â·  5Comments

dariusrosendahl picture dariusrosendahl  Â·  4Comments