maz-ui
Version:
A standalone components library for Vue.Js 3 & Nuxt.Js 3
175 lines (174 loc) • 6.43 kB
JavaScript
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
};