Skip to content

Instantly share code, notes, and snippets.

@artemgurzhii
Last active November 14, 2018 07:56

Revisions

  1. artemgurzhii revised this gist Mar 12, 2017. No changes.
  2. artemgurzhii revised this gist Mar 11, 2017. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion README.md
    Original file line number Diff line number Diff line change
    @@ -1,3 +1,3 @@
    The [Intersection Observer API](https://developer.mozilla.org/ru/docs/Web/API/Intersection_Observer_API) provides a way to asynchronously observe changes in the intersection of a target element with an ancestor element or with a top-level document's viewport.
    [Polyfill](https://github.com/WICG/IntersectionObserver/tree/gh-pages/polyfill)
    Polyfill can be founded [here](https://github.com/WICG/IntersectionObserver/tree/gh-pages/polyfill).
    PS: Stolen from [sample-media-pwa](https://github.com/GoogleChrome/sample-media-pwa/) created by [@paullewis](https://github.com/paullewis).
  3. artemgurzhii revised this gist Mar 11, 2017. 1 changed file with 1 addition and 0 deletions.
    1 change: 1 addition & 0 deletions README.md
    Original file line number Diff line number Diff line change
    @@ -1,2 +1,3 @@
    The [Intersection Observer API](https://developer.mozilla.org/ru/docs/Web/API/Intersection_Observer_API) provides a way to asynchronously observe changes in the intersection of a target element with an ancestor element or with a top-level document's viewport.
    [Polyfill](https://github.com/WICG/IntersectionObserver/tree/gh-pages/polyfill)
    PS: Stolen from [sample-media-pwa](https://github.com/GoogleChrome/sample-media-pwa/) created by [@paullewis](https://github.com/paullewis).
  4. artemgurzhii revised this gist Mar 11, 2017. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion README.md
    Original file line number Diff line number Diff line change
    @@ -1,2 +1,2 @@
    The [Intersection Observer API](https://developer.mozilla.org/ru/docs/Web/API/Intersection_Observer_API) provides a way to asynchronously observe changes in the intersection of a target element with an ancestor element or with a top-level document's viewport.
    PS: Mostly stolled from [sample-media-pwa](https://github.com/GoogleChrome/sample-media-pwa/) created by [@paullewis](https://github.com/paullewis).
    PS: Stolen from [sample-media-pwa](https://github.com/GoogleChrome/sample-media-pwa/) created by [@paullewis](https://github.com/paullewis).
  5. artemgurzhii revised this gist Mar 11, 2017. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion README.md
    Original file line number Diff line number Diff line change
    @@ -1,2 +1,2 @@
    The [Intersection Observer API](https://developer.mozilla.org/ru/docs/Web/API/Intersection_Observer_API) provides a way to asynchronously observe changes in the intersection of a target element with an ancestor element or with a top-level document's viewport.
    PS: Mostly stolled from [sample-media-pwa](https://github.com/GoogleChrome/sample-media-pwa/) created by @paullewis.
    PS: Mostly stolled from [sample-media-pwa](https://github.com/GoogleChrome/sample-media-pwa/) created by [@paullewis](https://github.com/paullewis).
  6. artemgurzhii created this gist Mar 11, 2017.
    2 changes: 2 additions & 0 deletions README.md
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,2 @@
    The [Intersection Observer API](https://developer.mozilla.org/ru/docs/Web/API/Intersection_Observer_API) provides a way to asynchronously observe changes in the intersection of a target element with an ancestor element or with a top-level document's viewport.
    PS: Mostly stolled from [sample-media-pwa](https://github.com/GoogleChrome/sample-media-pwa/) created by @paullewis.
    128 changes: 128 additions & 0 deletions lazy-load-images.js
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,128 @@
    import { preloadImage } from './utils';

    /**
    * Observe images that doesn't match selector.
    *
    * @class
    * @classdesc If the image gets within 50px in the Y axis, start the download.
    */
    export default class LazyLoadImages {

    constructor() {
    const images = document.getElementsByClassName('.js-lazy-image');
    const config = {
    rootMargin: '50px 0px',
    threshold: LazyLoadImages.THRESHOLD
    };

    if(!LazyLoadImages.SUPPORTS_INTERSECTION_OBSERVER) {
    this._loadImagesImmediately(images);
    return;
    }

    this._count = images.length;
    this._onIntersection = this._onIntersection.bind(this);
    this._observer = new IntersectionObserver(this._onIntersection, config);
    [...images].forEach(img => {
    if(img.classList.contains(LazyLoadImages.HANDLED_CLASS)) {
    return;
    }

    this._observer.observe(img);
    });
    }

    // check browser support
    static get SUPPORTS_INTERSECTION_OBSERVER() {
    return 'IntersectionObserver' in window;
    }

    // class with which to select images
    static get HANDLED_CLASS() {
    return 'js-lazy-image--handled';
    }

    // what % of visiblity of the target the observer should trigger
    static get THRESHOLD() {
    return 0.01;
    }

    // initialize new LazyLoadImages loader
    static init() {
    if(this._instance) {
    this._instance._disconnect();
    }

    this._count = 0;
    this._instance = new LazyLoadImages();
    }

    // disconnect LazyLoadImages loader
    // used if new LazyLoadImages() was called
    _disconnect() {
    if(!this._observer) {
    return;
    }

    this._observer.disconnect();
    }

    _onIntersection(entries) {
    entries.forEach(entry => {
    if(entry.intersectionRatio < 0) {
    return;
    }

    this._count--;
    this._observer.unobserve(entry.target);
    this._preloadImage(entry.target);
    });

    if(this._count > 0) {
    return;
    }

    this._observer.disconnect();
    }

    /**
    * Preloading image.
    *
    * @param {String} img - image to preload.
    */
    _preloadImage(img) {
    const src = img.dataset.src;
    if(!src) {
    return;
    }

    return preloadImage(src).then(() => this._applyImage(img, src));
    }

    /**
    * If IntersectionObserver API is not supported, load all images.
    *
    * @param {Array} images - images to add on the page.
    */
    _loadImagesImmediately(images) {
    [...images].forEach(image => this._preloadImage(image));
    }

    /**
    * Adding image on the page.
    *
    * @param {HTMLElement} _img - image to add on the page.
    * @param {String} src - image source path.
    */
    _applyImage(_img, src) {
    const el = _img.querySelector('.js-lazy-image-content');
    if(!el) {
    return;
    }

    // Prevent this from being lazy loaded a second time.
    _img.classList.add(LazyLoadImages.HANDLED_CLASS);
    el.style.backgroundImage = `url(${src})`;
    el.classList.add('fade-in');
    }
    }
    2 changes: 2 additions & 0 deletions main.js
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,2 @@
    import LazyLoadImages from './helpers/lazy-load-images';
    LazyLoadImages.init();
    14 changes: 14 additions & 0 deletions utils.js
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,14 @@
    /**
    * Create and preload image.
    *
    * @param {String} src - image source path.
    * @return {Promise} - created image element.
    */
    export function preloadImage(src) {
    return new Promise((resolve, reject) => {
    const image = document.createElement('img');
    image.src = src;
    image.onload = resolve;
    image.onerror = reject;
    });
    }