summaryrefslogtreecommitdiff
path: root/app/assets/javascripts/lazy_loader.js
blob: 9bba341e3a394fef213c73a7e94e22467d9adb93 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
import _ from 'underscore';

export const placeholderImage =
  'data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==';
const SCROLL_THRESHOLD = 300;

export default class LazyLoader {
  constructor(options = {}) {
    this.lazyImages = [];
    this.observerNode = options.observerNode || '#content-body';

    const throttledScrollCheck = _.throttle(() => this.scrollCheck(), 300);
    const debouncedElementsInView = _.debounce(() => this.checkElementsInView(), 300);

    window.addEventListener('scroll', throttledScrollCheck);
    window.addEventListener('resize', debouncedElementsInView);

    const scrollContainer = options.scrollContainer || window;
    scrollContainer.addEventListener('load', () => this.loadCheck());
  }
  searchLazyImages() {
    this.lazyImages = [].slice.call(document.querySelectorAll('.lazy'));

    if (this.lazyImages.length) {
      this.checkElementsInView();
    }
  }
  startContentObserver() {
    const contentNode = document.querySelector(this.observerNode) || document.querySelector('body');

    if (contentNode) {
      const observer = new MutationObserver(() => this.searchLazyImages());

      observer.observe(contentNode, {
        childList: true,
        subtree: true,
      });
    }
  }
  loadCheck() {
    this.searchLazyImages();
    this.startContentObserver();
  }
  scrollCheck() {
    requestAnimationFrame(() => this.checkElementsInView());
  }
  checkElementsInView() {
    const scrollTop = window.pageYOffset;
    const visHeight = scrollTop + window.innerHeight + SCROLL_THRESHOLD;

    // Loading Images which are in the current viewport or close to them
    this.lazyImages = this.lazyImages.filter(selectedImage => {
      if (selectedImage.getAttribute('data-src')) {
        const imgBoundRect = selectedImage.getBoundingClientRect();
        const imgTop = scrollTop + imgBoundRect.top;
        const imgBound = imgTop + imgBoundRect.height;

        if (scrollTop < imgBound && visHeight > imgTop) {
          LazyLoader.loadImage(selectedImage);
          return false;
        }

        return true;
      }
      return false;
    });
  }
  static loadImage(img) {
    if (img.getAttribute('data-src')) {
      let imgUrl = img.getAttribute('data-src');
      // Only adding width + height for avatars for now
      if (imgUrl.indexOf('/avatar/') > -1 && imgUrl.indexOf('?') === -1) {
        let targetWidth = null;
        if (img.getAttribute('width')) {
          targetWidth = img.getAttribute('width');
        } else {
          targetWidth = img.width;
        }
        if (targetWidth) imgUrl += `?width=${targetWidth}`;
      }
      img.setAttribute('src', imgUrl);
      img.removeAttribute('data-src');
      img.classList.remove('lazy');
      img.classList.add('js-lazy-loaded');
    }
  }
}