title: JavaScript - lazy-loading image fallbacks don't appear on iOS11 Safari
body: <p>I've created a script that utilises the  element with webp and jpg fallbacks with some lazy-loading to serve very high-quality images. So far it works perfectly in every 'modern' browser except iOS11 Safari.</p>
  
  <p>I have implemented a fallback for IntersectionObserver, as I know this isn't entirely supported yet, which uses getClientBoundingRect() to find the position of the images within the viewport. I've checked on iOS 10, 11 and then upgraded one of the same devices to iOS 12 where the images began loading.</p>
  
  <pre class="lang-html prettyprint-override"><code>&lt;div class="lazy-container"&gt;
              &lt;picture class="lazy-thumbnail"&gt;
                  &lt;source srcset="{{ asset( '/images/media/placeholders/3.webp' ) }}" type="image/webp"&gt;
                  &lt;source srcset="{{ asset( '/images/media/placeholders/3.jpg' ) }}" type="image/jpeg"&gt;
                  &lt;img class="" src="{{ asset( '/images/media/placeholders/3.jpg' ) }}" /&gt;
              &lt;/picture&gt;
              &lt;picture data-lazy&gt;
                  &lt;source data-srcset="{{ asset( '/images/media/3.webp' ) }}" type="image/webp"&gt;
                  &lt;source data-srcset="{{ asset( '/images/media/3.jpg' ) }}" type="image/jpeg"&gt;
                  &lt;img class="" src="{{ asset( '/images/media/3.jpg' ) }}" /&gt;
              &lt;/picture&gt;
          &lt;/div&gt;
  </code></pre>
  
  <pre><code>const lazyObserver = new IntersectionObserver( ( entries, observer ) =&gt; {
              // Break into function to allow immediate calling
              const exchangeImage = ( image, source ) =&gt; {
                  source.forEach( ( s ) =&gt; {
                      s.setAttribute( 'srcset',
                          s.getAttribute( 'data-srcset' ) );
                  });
                  image.previousElementSibling.classList.remove( 'lazy-thumbnail' );
                  image.previousElementSibling.classList.add( 'ex-lazy-thumbnail' );
                  image.classList.add( 'ex-lazy' );
                  image.removeAttribute( 'data-lazy' );
                  // If the image is in view and src has been swapped out, stop observing the image
                  lazyObserver.unobserve( image );
              };
              entries.forEach( ( entry ) =&gt; {
                  const image = entry.target;
                  const source = image.querySelectorAll( 'source' );
                  // If the image is either in view or data-lazy='immediate', load the image.
                  if( entry.target.dataset.lazy === 'immediate' || entry.isIntersecting ){
                      exchangeImage( image, source );
                  }
              });
          });
  </code></pre>
  
  <p>The picture element should go through and load the first image srcset that works for the browser, webp in most instances, then with these selected the JS should run when the image is in view, swap out the srcset for the data-srcset with the large image and load. This works on every browser except 
  
  <pre><code>.lazy-container {
    overflow: hidden;
    position: relative;
  
    picture {
      width: 100%;
      position: absolute;
      top: 0;
      left: 0;
  
      img {
        display: block;
        width: 100%;
        height: auto;
      }
    }
  }
  .ex-lazy {
    filter: blur(0px);
    transition: all 500ms ease-in-out;
  }
  .lazy-thumbnail {
    opacity: 1;
    filter: blur(8px);
    transition: all 500ms ease-in-out;
    z-index: 1;
  }
  .ex-lazy-thumbnail {
    opacity: 0;
    filter: blur(0px);
    transition: all 500ms ease-in-out;
    z-index: 0;
  }
  [data-lazy]{
    width: 100%;
    height: auto;
    filter: blur(8px);
    transition: all 500ms ease-in-out;
  }
  </code></pre>