Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

lazyLoad srcset or <picture> #172

Closed
solemone opened this issue Jun 25, 2015 · 32 comments
Closed

lazyLoad srcset or <picture> #172

solemone opened this issue Jun 25, 2015 · 32 comments

Comments

@solemone
Copy link

It would be great if there is an option to fill the "srcset" attribute instead of the "src" attribute for responsive lazy load images. Or both with a new data attribute for the srcset image array.

@desandro
Copy link
Member

desandro commented Jun 26, 2015

Thanks for this feature request. Add a 👍 reaction to this issue if you would like to see this feature added.

@desandro desandro changed the title Option to change the src attribute to srcset for responsive lazy load images lazyLoad srcset or <picture> Jun 29, 2015
@solemone
Copy link
Author

solemone commented Jul 1, 2015

Hi Dave,

thanks for your reply. I think these are two different things. When the <picture> element is used, the aspect ratio must be considered, as the image may differ in dimensions for different views like landscape/portrait. When a <img> element has the srcset attribute, the aspect ratio should remain the same in the image array. It’s only for the different resolutions/widths.

That’s why the srcset attribute can also be used in the <source> element in a <picture> element like:

<picture>
    <source srcset="img-landscape-100.png 100w, img-landscape-200.png 200w" media="(orientation: landscape)">
    <source srcset="img-portrait-100.png 100w, img-portrait-200.png 200w" media="(orientation: portrait)">
    <img src="img.png" alt="Image">
</picture>

I hope this is correctly described. I would be happy about the lazyLoad srcset attribute for the <img> elements. +1 from me :)

@nicholas-c
Copy link

As I mentioned in #175 I really don't feel srcset's are the way forward. Way, way too unsupported.
Picture tag is completely unsupported in IE, Safari, iOS Safari and only just been released in firefox 38.

http://caniuse.com/#search=srcset
http://caniuse.com/#search=picture

-1.

@africanmatt
Copy link

<picture> has been added into the HTML spec and picturefill.js acts as a solid polyfill

+1 for future friendliness.

@nvartolomei
Copy link

+1 for this 👍

I'm working right now on a website where I need high quality images, especially for retina screens, but this makes a huge overhead for non-retina visitors.

Right now I'm loading all images in low quality like before, and add a data attribute with high quality retina image. Right before initializing flickity I'm checking pixelDensityRation and window size, if it's "huge enough" I'm moving retina images to flickity-lazyload. Using this approach I can show slide images even before javascripts loads and flickity is initialized 💃

However I'm not very happy with it.

@nelga
Copy link

nelga commented Jul 16, 2015

+1 This would be an incredible addition. I have a couple of carousels with a variety of photos, and the strain is too much. LazyLoad with srcset or picture would be great if it can somehow be implemented.

@LagunaCompany
Copy link

+1 from me!

I know Flickity uses a data-flickity-lazyload attribute for lazy loading. If we're worried about current support for picture and srcset, would a short-term solution like a data-flickity-lazyload-retina attribute be useful?

I'm using Unveil.js for a project, and that one works along those lines: one data attribute for the normal image, and a second for a high-res one.

http://luis-almeida.github.io/unveil/

From what I gather, not too dissimilar from what nvartolomei above tried, but it would be a Flickity option.

@cmeeren
Copy link

cmeeren commented Jul 21, 2015

+1

@aFarkas
Copy link

aFarkas commented Jul 22, 2015

You guys know, that you can use flickity already with lazySizes to get lazyloading support for srcset and picture (and many more)?

@desandro
Copy link
Member

@aFarkas Thanks for chiming in. Can I get your expertise? I'd rather defer to lazySizes than build more logic into Flickity.

From what I understand of lazySizes's behavior, it works by loading assets according to scroll Y position. With Flickity's carousels, all cells have the same scroll Y position, so they'd all get loaded at the same time. Ideally, cell assets should only be loaded when they are visible.

To use lazySizes in this manner, looks like you'd set loadMode: 0, so lazySizes does not load any images. Then use the Flickity API to trigger lazySizes.loader.unveil() on unloaded assets on Flickity's cellSelect event. That sound about right?

@aFarkas
Copy link

aFarkas commented Jul 22, 2015

To use lazySizes in this manner, looks like you'd set loadMode: 0, so lazySizes does not load any images. Then use the Flickity API to trigger lazySizes.loader.unveil() on unloaded assets on Flickity's cellSelect event. That sound about right?

While this would work, I wouldn't do anything with the lazySizes API.

lazySizes is a full automatic lazyloader. Here is a demo with Flickity.

While lazySizes checks whether an image is inside of the viewport (+ some extra offset (called expand) to preload for better user experience). lazySizes automatically also additionally checks overflow: hidden/auto nesting areas ( + removes the expand), if network is already heavily in use (before onload event or more than 3 images loading at the same time).

lazySizes uses several techniques to automatically detect visibility changes, so you really don't need to call anything with lazySizes. (Of course developers can tweak some parameters, like the expand option).

The only thing, that needs to be done, is that flickity must update its layout as soon as an image.onload event happens, because this might change the layout also. From my viewpoint this also fixes your picture support problem. (#161)

Here is some code that should work just fine in all browsers but IE8.

(function(){
  var oldFlickityCreate = window.Flickity.prototype._create;

  window.Flickity.prototype._create = function(){
    var that = this;
    if(this.element.addEventListener){
      this.element.addEventListener('load', function(){
        that.onresize();
      }, true);
    }
    this._create = oldFlickityCreate;
    return oldFlickityCreate.apply(this, arguments);
  };
})();

This approach above obviously doesn't give you 100% control over when an image is transformed and which images are transformed. Everything is handled automatically based on the viewport, the expand option and sometimes the overflow area.

In case you want really 100% control. The following thing would be easier, than using loadmode and the lazySizes.loader.unveil().

You simple write the respimage markup using data-srcset, but you don't add lazySizes' "trigger class" lazyload, as soon as you want an image to be transformed, you add the classes lazyload and the class lazypreload and you are done.

Here is a simple demo of this API: http://jsfiddle.net/trixta/zdqfy9mu/

But as said above, I would claim, that the configurable automatic detection in lazySizes should be good enough for 99% of all use cases.

@johjacb
Copy link

johjacb commented Aug 14, 2015

I'm trying to use Flickity/lazySizes together with srcset and can't quite figure it out. I've got a carousel that takes up the entire width of the window, and I only want 1 image to be loaded on page load (due to the size of the images), and then each subsequent image loaded when requested by the user.

I'm initializing Flickity in my js, because I'm using a bit of js to randomize the initial_load option.

Is there a way through expand or loadMode to restrict the number of images loaded? Or do I need a bit of custom js?

Thanks!

@aFarkas
Copy link

aFarkas commented Aug 15, 2015

Lazysizes is a general lazyloader so it can't give you fine grained flickity specific control. In general you shouldn't need so much fine grained control. (I would be fine if sometimes also two images are loaded.) If lazysiizes detects that a lot of images are loading or before onload it adjusts its loading mode so that only in view images are loaded to get a fast performance.

For loading just one image per slide the expand and the expFactor option can be used. For example: expand = 200 and expFactor = 1.5

In case you have multiple loads before onload. This might happen because flickity has not initialized and all cells are technically in viewport. You can solve this multiple ways. For example you can hide all cells but the first using visibility: hidden using the nth-child selector until flickity has fully initialized itself. Also loadMode = 1 might help.

In case you want 100% control you can set the expand to 1 and dynamically add the class lazypreload to the images you want to load, but I think this might be too much work for tje outcome. I might put a demo together in the future.

@johjacb
Copy link

johjacb commented Aug 17, 2015

@aFarkas thanks for your response. I'd love to see your take on managing the loading 100%!

@aFarkas
Copy link

aFarkas commented Aug 18, 2015

@johjacb
Here is a simple demo:
http://afarkas.github.io/lazyflickity/

Here is also the issue tracker:
https://github.com/aFarkas/lazyflickity/issues

@desandro
Copy link
Member

@aFarkas Thank you for your insightful response. This is great news. I'll be working on incorporating LazySizes to the Flickity docs.

@solemone
Copy link
Author

@aFarkas I just wanted to say thanks. Finally, I was able to try it and it works great. It also helped me with #100

@beneshort
Copy link

+1 would be great to have the best method documented in the Flickity docs :)

@raywongjr
Copy link

I can attest that the combination of lazysizes and flickity works wonderfully! We've been using the two together as a standard for our sites over the past several months. @aFarkas and @desandro deserve a big thumbs up.

@downFast
Copy link

I am trying to open an image in a new window and load the correct image based on srcset in order to open the right image size based on resolution. ideally I would like to use photoswipe tho * but for now I am trying to achieve a simple correct image opening

I am doing this:

$(".gallery-cell").on("click", function(e){
   e.preventDefault();
   var img_to_load = $(this).find('img').attr('data-srcset');
   var $str = img_to_load.split(' ');
   var $retina = $str[0];
   imgWindow = window.open(img_to_load, 'imgWindow');
});

The above doesn't open different images based on resolutions but this https://farm4.staticflickr.com/3059/2835191823_e3636abb34_m.jpg

I believe is because of $str[0]; yet I have tried not to insert an index [0] and that takes me to a not found image

Example: http://codepen.io/aFarkas/pen/OVoavw

@downFast
Copy link

+1

@oskarrough
Copy link

@downFast while this is not related to Flickity, what you're looking for is currentSrc. Something like this:

$(".gallery-cell").on("click", function(e){
   e.preventDefault();
   var src = $(this).find('img').get(0).currentSrc; // get DOM element's currentSrc
   imgWindow = window.open(src, 'imgWindow');
});

@downFast
Copy link

It is related to flickity as fullscreen functionity is another open topic and a feature request if you follow up. People have applied and provided solutions in regards too, check it around flickity issues on git.

Said that Thanks a lot, i will try this out as i can and i'll get back to you.

Cheers :)

@downFast
Copy link

@aFarkas @desandro thanks a lot for your code but in safari doesn't work, the browser is picking up only the small image size in the gallery, any help please?

<img alt="100%x200" data-srcset="https://farm4.staticflickr.com/3059/2835191823_e3636abb34_m.jpg 240w, https://farm4.staticflickr.com/3059/2835191823_e3636abb34_n.jpg 320w, https://farm4.staticflickr.com/3059/2835191823_e3636abb34.jpg 500w, https://farm4.staticflickr.com/3059/2835191823_e3636abb34_z.jpg 640w, https://farm4.staticflickr.com/3059/2835191823_e3636abb34_b.jpg 1920w" data-sizes="auto" class="gallery-cell-image lazyload">

@downFast
Copy link

@aFarkas @desandro I've removed the data-sizes="auto" and added sizes="(min-width: 1000px) 930px, 90vw" and everything works fine in safari too.

This is the full code, notice I am also using bootstrap modal for an img popup:

      window.lazySizesConfig = window.lazySizesConfig || {};
      window.lazySizesConfig.loadMode = 1;
      window.lazySizesConfig.expand = 222;
      window.lazySizesConfig.expFactor = 1.6;
      //load picturefill, if no respimage support
      function loadJS(u) {
          var r = document.getElementsByTagName("script")[0],
              s = document.createElement("script");
          s.src = u;
          r.parentNode.insertBefore(s, r);
      }
      if (!window.HTMLPictureElement) {
          document.createElement('picture');
          //generating the config array
          window.picturefillCFG = window.picturefillCFG || [];
          picturefillCFG.push(["algorithm", "saveData"]);
          loadJS("https://cdnjs.cloudflare.com/ajax/libs/picturefill/3.0.0-beta1/picturefill.min.js");
      }
      (function(){
        var oldFlickityCreate = window.Flickity.prototype._create;   
        window.Flickity.prototype._create = function(){
          var that = this;
          if(this.element.addEventListener){
            this.element.addEventListener('load', function(){
              that.onresize();
            }, true);
          }
          this._create = oldFlickityCreate;
          return oldFlickityCreate.apply(this, arguments);
        };
      })();
      if(!('currentSrc' in document.createElement('img'))) {
          Object.defineProperty(HTMLImageElement.prototype, 'currentSrc', {
              set: function () {
                  if (window.console && console.warn) {
                      console.warn('currentSrc can\'t be set on img element');
                  }
              },
              get: function () {
                  return this.src || '';
              },
              enumerable: true,
              configurable: true
          });
      }
      var src;
      $(".gallery-main .gallery-cell").on("click", function(e){
            e.preventDefault();
            src = $(this).find('img').get(0).currentSrc;
            $("#imgModal").modal();
            // $("#imgModal .modal-body").load(src, function(data){
            // });
        });
        $('#imgModal').on('show.bs.modal', function (e) {
          $("#imgModal .modal-body").html('<img class="img-responsive" alt="..." src="'+src+'">');
      });

@spacecat
Copy link

+1 flickity + lazysizes sounds like a good idea. Hope to see an updated docs version soon with an example of how to use flickity with lazysizes together with <picture> element and srcset.

@johndugan
Copy link

johndugan commented Feb 27, 2017

Hi Folks,

I think I've got a pretty elegant solution going on.

  1. Add APP.setSrc function to build
  2. Ensure your Modernizr build tests for srcset
  3. Overwrite Flickity.LazyLoader.prototype.load function
  4. Update your markup

setSrc is a simple helper/utility function that I wrote as a method on my APP object. I use it in for a couple different scripts/modules. It's pretty simple and works just fine for adding a retina value along with a srcset value to images in your Flickity slider(s).

Step 1: Add setSrc to your build.

// ------------------------------------
// ---------- SET SRC/SRCSET ----------
// ------------------------------------

APP.setSrc = (function() {

    var ATTR = {
        src: 'data-src',
        retina: 'data-src-retina',
        srcset: 'data-srcset'
    };
    ATTR.optimal = window.devicePixelRatio > 1 ? ATTR.retina : ATTR.src;

    return function(images, sources) {

        images = Array.isArray(images) ? images : [images];

        ATTR = typeof sources === 'object' ? Object.assign({}, ATTR, sources) : ATTR;

        images.forEach((image) => {
            var srcset = Modernizr.srcset && image.getAttribute(ATTR.srcset);

            image.addEventListener('load', () => image.classList.add('-loaded'));

            if (srcset) {
                image.setAttribute('srcset', srcset);
            } else {
                // fallback to `ATTR.src` when on retina and `ATTR.retina` attribute is not present on element
                image.setAttribute('src', image.getAttribute(ATTR.optimal) || image.getAttribute(ATTR.src));
            }
        });

        return images;

    };

}());

Step 2: Add Modernizr's srcset test to your build

Step 3: Overwrite Flickity.LazyLoader.prototype.load as shown below.

Flickity.LazyLoader.prototype.load = function() {
    this.img.addEventListener('load', this);
    this.img.addEventListener('error', this);
    // load image
    APP.setSrc(this.img, {
        src: 'data-flickity-lazyload'
    });
    // remove attr
    this.img.removeAttribute('data-flickity-lazyload');
};

Step 4: Update your markup with the new data attributes. ...something similar to PHP example below.

<img
    src="<?php echo $placeholder; ?>"
    width="<?php echo $imageWidth; ?>"
    height="<?php echo $imageWidth; ?>"
    alt="<?php echo $alt; ?>"
    class="gridproduct__media__image flickity-image flickity-lazyimage"
    data-flickity-lazyload="<?php echo $image->resize($imageWidth, $imageHeight); ?>"
    data-src-retina="<?php echo $image->resize($imageWidth * 2, $imageHeight * 2); ?>"
    data-srcset="<?php
        echo $image->resize(375, 375).' 375w, '; // iPhone portrait @1x
        echo $image->resize(375 * 2, 375 * 2).' '.(375*2).'w, '; // iPhone portrait @2x
        echo $image->resize(768 * 1.5, 768 * 1.5).' '.(768*1.5).'w'; // iPad portrait @1.5x
    ?>"
    sizes="(min-width: 64em) 50vw, (min-width: 80) 40vw, (min-width: 98) 33vw, 100vw"
/>

@davidhellmann
Copy link

Hm I'm to stupid to use Flickity + LazySizes
He calculate some stuff wrong what you can see here:
image

The small margin is right. The larger Maring is wrong. When I resize the Window by myself its all fine you can see here:
image

I call the Flickity via Data-Attributes and use this little Snippet to trigger a resize but the problem stil exists:

import Flickity from 'flickity'

// Load Resize Hack
window.addEventListener('load', () => {
    setTimeout(() => {
        const sliders = Array.from(document.querySelectorAll('.js-imageSlider'))
        if (sliders) {
            sliders.forEach((slider) => {
                const flkty = Flickity.data(slider)
                flkty.resize()
                slider.classList.add('is-visible')
            })
        }
    }, 250)
})

Any Ideas?

@solemone
Copy link
Author

Hi @davidhellmann,

did you see this comment by @aFarkas . I think you need to resize your carousel when an image is loaded. Not when the window is loaded. He also made a demo. Your code seems to be for the height bug from issue #205. The resize() executes just once after the window is loaded not when an image is lazyloaded.

@metafizzy metafizzy deleted a comment from alexyakir Nov 17, 2017
desandro added a commit that referenced this issue Mar 16, 2018
check for data-flickity-lazyload-srcset
check for data-flickity-lazyload-src

✅ test & sandbox lazyload srcset

For #172
@desandro
Copy link
Member

desandro commented Mar 20, 2018

🎉 Flickity v2.1.0 has been released with all new support for srcset with lazyLoad. See docs! 🎉

Set the image's srcset with data-flickity-lazyload-srcset. You can also set data-flickity-lazyload-src as well to set src for browsers that do not support srcset.

<img class="carousel-cell-image"
  data-flickity-lazyload-srcset="
    walrus-large.jpg 720w,
    walrus-med.jpg 360w"
  sizes="(min-width: 1024px) 720px, 360px"
  data-flickity-lazyload-src="walrus-large.jpg"
  />

I'm delighted to finally ship this feature. I'm now closing this issue. For <picture> support please add your 👍 to #161. Thanks for all the +1's and 👍 over the years. 🌈🐻

@spacecat
Copy link

Very nice. This will speed things up for sure. Thanks for the effort!

@the-hotmann
Copy link

the-hotmann commented May 18, 2019

Hi and thank you for this nice slider!

For me this problem is not solved.
LazyLoading is not working properly when using <picture> and <source> link adviced and recommended by Google --> LINK

For me my html looks like this:

<picture>
    <source data-flickity-lazyload-srcset="/img/picture-1440.jpg 1x, /img/picture-2880.jpg 2x" media="(min-height: 1440px)">
    <source data-flickity-lazyload-srcset="/img/picture-992.jpg 1x, /img/picture-1984.jpg 2x" media="(min-height: 992px)">
    <img src="/img/1.png" data-flickity-lazyload-src="/img/picture.jpg">
</picture>

just /img/picture.jpg gets loaded! No matter which resolution

The lazyLoading itself works, but it does not "activate" the ''. With this LazyLoading the <img> always gets loaded and all the other resolutions are not getting loaded!

So this does not work for <picture> tags.
Why does this not work with <source>??

If I just replace "source" with "img" then it looks like this:

<picture>
    <img data-flickity-lazyload-srcset="/img/picture-1440.jpg 1x, /img/picture-2880.jpg 2x" media="(min-height: 1440px)">
    <img data-flickity-lazyload-srcset="/img/picture-992.jpg 1x, /img/picture-1984.jpg 2x" media="(min-height: 992px)">
    <img src="/img/1.png" data-flickity-lazyload-src="/img/picture.jpg">
</picture>

these images gets ALL loaded:
/img/picture.jpg
/img/picture-1440.jpg
/img/picture-992.jpg

Now it just loads the Images in the Viewport but ALL (all resolutions).
That means it replaces all the things how it should at <img> tags but not at <source> tags.
Also means: if it would also replace everything at also <source> tags native responsive images with picture and source would work!

@desandro do you think you can fix this?

Thanks in advance!

#EDIT: I think I made it.. will share my code soon!

#EDIT2: PLS watch HERE for my demo

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Development

No branches or pull requests