All files lazyload.ts

23.21% Statements 13/56
10.53% Branches 2/19
5.56% Functions 1/18
23.53% Lines 12/51

Press n or j to go to the next uncovered block, b, p or k for the previous block.

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 88 89 90 91 92 93 94 95 96 97 98    1x           1x 1x 1x   2x 1x 1x                                                       1x               1x                 1x           1x                 1x                                              
import { ObserverOptions, IntersectionObserverEntryType, Props } from './types'
 
const defaultObserverOptions: ObserverOptions = {
  root: undefined,
  rootMargin: undefined,
  threshold: undefined
}
 
const observerKeys: ObserverOptions[] = []
const observers = new WeakMap<ObserverOptions, IntersectionObserver>()
const images = new WeakMap<Element, { observer: IntersectionObserver; options: Props }>()
 
export const call = fn => fn && fn()
export const isNull = <T>(obj: T | null): obj is null => obj === null
const createIntersectionObserver = (options: ObserverOptions) =>
  new IntersectionObserver(loadingCallback, options)
 
function loadingCallback(entrys: IntersectionObserverEntryType[]) {
  entrys.filter(entry => entry.isIntersecting).forEach(entry => {
    const target = entry.target as HTMLImageElement | HTMLDivElement
    const metaData = images.get(target)
    if (!metaData) {
      console.warn('Could not find meta data for image')
      return
    }
    metaData.observer.unobserve(target)
    loadImage(metaData.options.image)
      .catch(() => {
        if (metaData.options.errorImage) {
          return loadImage(metaData.options.errorImage)
        }
        return Promise.resolve(metaData.options.defaultImage)
      })
      .catch(() => metaData.options.defaultImage)
      .then((imagePath: string) => {
        setImage(target as HTMLImageElement, imagePath)
        addCssClassName(target, 'lazy-loaded')
        call(metaData.options.onLoaded)
      })
  })
}
 
export function setImage(element: HTMLImageElement | HTMLDivElement, imagePath: string) {
  if (isImageElement(element)) {
    element.src = imagePath
  } else {
    element.style.backgroundImage = `url('${imagePath}')`
  }
}
 
export function loadImage(imagePath: string): Promise<string> {
  return new Promise((resolve, reject) => {
    const img = new Image()
    img.src = imagePath
    img.onload = () => resolve(imagePath)
    img.onerror = err => reject(err)
  })
}
 
export function isImageElement(
  element: HTMLImageElement | HTMLDivElement
): element is HTMLImageElement {
  return element.nodeName.toLowerCase() === 'img'
}
 
export function addCssClassName(
  element: HTMLImageElement | HTMLDivElement,
  cssClassName: string
) {
  if (!element.className.includes(cssClassName)) {
    element.className += ` ${cssClassName}`
  }
}
 
export function registerImageToLazyLoad(element: Element, metadata: Props) {
  const options = metadata.options || defaultObserverOptions
  let observerKey = observerKeys.find(
    oKey =>
      oKey.root === options.root &&
      oKey.rootMargin === options.rootMargin &&
      oKey.threshold === options.threshold
  )
  if (!observerKey) {
    observerKey = options
    observerKeys.push(observerKey)
  }
  let observer = observers.get(observerKey)
  if (!observer) {
    observer = createIntersectionObserver(observerKey)
    observers.set(observerKey, observer)
  }
  images.set(element, {
    observer,
    options: metadata
  })
  observer.observe(element)
}