UNPKG

@likun./lazy-img

Version:

a custom element of image-lazy-loading based on web-components

130 lines (111 loc) 3.65 kB
import { getValWithUnit } from '@/utils' class LazyImg extends HTMLElement { private shadow: ShadowRoot; private img: HTMLImageElement; private done: boolean = false; private observer: IntersectionObserver; constructor() { super() this.shadow = this.attachShadow({ mode: 'open' }) this.shadow.innerHTML = ` <style> :host{ display: inline-flex; background: #F5F7FA; } img{ width: 100%; height: 100%; } </style> ` this.img = document.createElement('img') this.img.setAttribute('alt', ' ') // remove default border this.shadow.appendChild(this.img) } static observedAttributes = ['src', 'alt', 'presrc', 'width', 'height'] attributeChangedCallback(name, oldVal, newVal) { if(oldVal !== newVal) { if(['width', 'height'].includes(name)) { this.style.setProperty(name, getValWithUnit(newVal)) } else if(name === 'src') { this.done && this.img.setAttribute(name, newVal) } else if(name === 'presrc') { !this.done && this.img.setAttribute(name, newVal) } else { this.img.setAttribute(name, newVal) } } } connectedCallback() { this.init() } disconnectedCallback() { this.img.onload = null this.img.onerror = null } private init() { if(!this.hasAttribute('width') && !this.hasAttribute('height')) { this.style.setProperty('width', '300px') this.style.setProperty('height', '200px') } this.img.onload = this.handleLoad this.img.onerror = this.handleError this.createIntersectionObserver() Promise.resolve().then(() => { if(!this.done && this.hasAttribute('presrc')) { this.img.setAttribute('src', this.getAttribute('presrc')) } }) } // whether element is in viewport? get isInViewport() { const vWidth = window.innerWidth || document.documentElement.clientWidth const vHeight = window.innerHeight || document.documentElement.clientHeight const { top, bottom, left, right } = this.getBoundingClientRect() return !( top > vHeight || bottom < 0 || left > vWidth || right < 0 ) } // whether browser support IntersectionObserver & IntersectionObserverEntry API? get isSupportIntersectionObserver() { return [IntersectionObserver, IntersectionObserverEntry] .map(fn => typeof fn).every(type => type === 'function') } createIntersectionObserver = () => { if(!this.isSupportIntersectionObserver) { throw new Error('the current environment does not support IntersectionObserver API.') } this.observer && this.observer.disconnect() const handleObserver = ([entry]: IntersectionObserverEntry[]) => { if(!this.done && entry && entry.isIntersecting) { this.img.setAttribute('src', this.getAttribute('src')) // this.shadow.appendChild(this.img) this.done = true this.observer && this.observer.disconnect() } } const options = { root: null, rootMargin: '0px', threshold: 0 // threshold can be also array type, such as [0, 0.2, 0.4, 0.6, 0.8, 1] } this.observer = new IntersectionObserver(handleObserver, options) this.observer.observe(this) } handleEmit = (eventName: string) => { this.dispatchEvent(new CustomEvent(eventName, { bubbles: true, composed: true, detail: { target: this, src: this.getAttribute('src') } })) } handleLoad = () => { this.handleEmit('lazyload') } handleError = () => { this.handleEmit('lazyerror') } } export default LazyImg