UNPKG

maz-ui

Version:

A standalone components library for Vue.Js 3 & Nuxt.Js 3

175 lines (174 loc) 6.43 kB
const EMPTY_PHOTO = "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7", DEFAULT_OPTIONS = { baseClass: "m-lazy-img", loadedClass: "m-lazy-loaded", loadingClass: "m-lazy-loading", errorClass: "m-lazy-error", fallbackClass: "m-lazy-fallback", observerOnce: !0, loadOnce: !1, observerOptions: { threshold: 0.1 } }; class LazyImg { observers = []; defaultOptions = DEFAULT_OPTIONS; options; onImgLoadedCallback; onImgErrorCallback; hasImgLoaded = !1; constructor(opts = {}) { this.options = this.buildOptions(opts), this.onImgLoadedCallback = this.imageIsLoaded.bind(this), this.onImgErrorCallback = this.imageHasError.bind(this); } async loadErrorPhoto() { const { default: photo } = await import("./no-image.B9FLV0YF.js"); return photo; } buildOptions(opts) { return { ...this.defaultOptions, ...opts, observerOptions: { ...this.defaultOptions.observerOptions, ...opts.observerOptions } }; } removeClass(el, className) { el.classList.remove(className); } addClass(el, className) { el.classList.add(className); } removeAllStateClasses(el) { this.removeClass(el, this.options.loadedClass), this.removeClass(el, this.options.loadingClass), this.removeClass(el, this.options.errorClass), this.removeClass(el, this.options.fallbackClass); } setBaseClass(el) { this.addClass(el, this.options.baseClass); } imageIsLoading(el) { this.addClass(el, this.options.loadingClass), this.options.onLoading?.(el); } imageIsLoaded(el) { this.hasImgLoaded = !0, this.removeClass(el, this.options.loadingClass), this.addClass(el, this.options.loadedClass), this.options.onLoaded?.(el); } imageHasError(el) { this.removeClass(el, this.options.loadingClass), this.addClass(el, this.options.errorClass), this.options.onError?.(el), this.setDefaultPhoto(el); } getSrc(binding) { return typeof binding.value == "object" ? binding.value.src : binding.value; } getImageUrl(el, binding) { const dataSrc = this.getImgElement(el).getAttribute("data-lazy-src"); return dataSrc || this.getSrc(binding); } async setPictureSourceUrls(el) { const sourceElements = el.querySelectorAll("source"); if (sourceElements.length > 0) for await (const source of sourceElements) { const srcSet = source.getAttribute("data-lazy-srcset"); if (srcSet) source.srcset = srcSet; else return this.imageHasError(el); } else this.imageHasError(el); } hasBgImgMode(binding) { return binding.arg === "bg-image"; } isPictureElement(el) { return el instanceof HTMLPictureElement; } getImgElement(el) { return this.isPictureElement(el) ? el.querySelector("img") : el; } async setDefaultPhoto(el) { if (this.options.fallbackSrc === !1) return; const fallbackSrc = this.options.fallbackSrc; typeof fallbackSrc == "string" && this.addClass(el, this.options.fallbackClass); const errorPhoto = fallbackSrc ?? await this.loadErrorPhoto(), sourceElements = el.querySelectorAll("source"); if (sourceElements.length > 0) for await (const source of sourceElements) source.srcset = errorPhoto; else this.setImgSrc(el, errorPhoto); } addEventListenerToImg(el) { const imgElement = this.getImgElement(el); imgElement.addEventListener("load", () => this.onImgLoadedCallback(el), { once: !0 }), imgElement.addEventListener("error", (err) => this.onImgErrorCallback(el, err), { once: !0 }); } async loadImage(el, binding) { if (this.imageIsLoading(el), this.isPictureElement(el)) this.addEventListenerToImg(el), await this.setPictureSourceUrls(el); else { const imageUrl = this.getImageUrl(el, binding); if (!imageUrl) return this.imageHasError(el); this.hasBgImgMode(binding) ? (el.style.backgroundImage = `url('${imageUrl}')`, this.imageIsLoaded(el)) : (this.addEventListenerToImg(el), this.setImgSrc(el, imageUrl)); } } setImgSrc(el, src) { const imgElement = this.getImgElement(el); imgElement.src = src; } handleIntersectionObserver(el, binding, entries, observer) { this.observers.push(observer); for (const entry of entries) if (entry.isIntersecting) { if (this.options.onIntersecting?.(entry.target), this.options.observerOnce && observer.unobserve(el), this.options.loadOnce && this.hasImgLoaded) return; this.loadImage(el, binding); } } createObserver(el, binding) { const observerCallback = (entries, intersectionObserver) => { this.handleIntersectionObserver(el, binding, entries, intersectionObserver); }, observerOptions = this.options.observerOptions; new IntersectionObserver(observerCallback, observerOptions).observe(el); } async imageHandler(el, binding, type) { if (type === "update") for await (const observer of this.observers) observer.unobserve(el); globalThis.IntersectionObserver ? this.createObserver(el, binding) : this.loadImage(el, binding); } async bindUpdateHandler(el, binding, type) { await this.imageHandler(el, binding, type); } async add(el, binding) { if (this.hasBgImgMode(binding) && this.isPictureElement(el)) throw new Error(`[MazLazyImg] You can't use the "bg-image" mode with "<picture />" element`); setTimeout(() => this.setBaseClass(el), 0), el.getAttribute("src") || this.setImgSrc(el, EMPTY_PHOTO), await this.bindUpdateHandler(el, binding, "bind"); } async update(el, binding) { binding.value !== binding.oldValue && (this.hasImgLoaded = !1, this.removeAllStateClasses(el), await this.bindUpdateHandler(el, binding, "update")); } remove(el, binding) { this.hasImgLoaded = !1, this.hasBgImgMode(binding) && (el.style.backgroundImage = ""), this.removeAllStateClasses(el); for (const observer of this.observers) observer.unobserve(el); this.observers = []; } } let instance; const directive = { created(el, binding) { const options = typeof binding.value == "object" ? binding.value : {}; instance = new LazyImg(options), instance.add(el, binding); }, updated(el, binding) { instance.update(el, binding); }, unmounted(el, binding) { instance.remove(el, binding); } }; export { DEFAULT_OPTIONS as D, LazyImg as L, directive as d };