Github project address: github.com/murongg/vue…
O star and issues
I am not a good writer and may not write good articles. If you have any questions, please comment in the comment section and I will try my best to answer them
This project has been published to NPM
Installation:
$ npm i vue3-lazyload
# or
$ yarn add vue3-lazyload
Copy the code
Demand analysis
-
You can customize loading images to be used when loading images
-
Support to customize the error picture, the picture load failed to use this picture
-
Support for Lifecycle hooks, similar to vue’s lifecycle, and bind lazy attributes to the IMG tag, similar to
<img src="..." lazy="loading"> <img src="..." lazy="loaded"> <img src="..." lazy="error"> Copy the code
And support:
img[lazy=loading] { /*your style here*/ } img[lazy=error] { /*your style here*/ } img[lazy=loaded] { /*your style here*/ } Copy the code
-
Supports the v-lazy custom directive, which specifies that string/object can be passed in. If it is string, it is the URL to be loaded by default. If it is Object, it can be passed in
src:
The url of the image that needs to be loadedloading:
The image used to load the stateerror:
The image used when loading failedlifecycle:
The lazy life cycle replaces the global life cycle
The directory structure
- SRC ---- index.ts entry file, which is used to register plug-ins ---- lazy.ts lazy loading main function ---- types. Ts type files, including ---- utilCopy the code
Write lazy loading classes
Lazy loading is mainly implemented through IntersectionObserver object, which may not be supported by some browsers and thus not compatible with IntersectionObserver object.
Determines the parameters passed in when registering the plug-in
Types. Ts:
export interfaceLazyOptions { error? :string; // Failed to load the imageloading? :string; // Load the imageobserverOptions? : IntersectionObserverInit;// IntersectionObserver object passes in the second parameterlog? :boolean; // Whether to print logslifecycle? : Lifecycle;// Lifecycle hooks
}
export interface ValueFormatterObject {
src: string, error? :string, loading? :string, lifecycle? : Lifecycle; }export enum LifecycleEnum {
LOADING = 'loading',
LOADED = 'loaded',
ERROR = 'error'
}
export type Lifecycle = {
[x inLifecycleEnum]? :() = > void;
};
Copy the code
Determine the framework of the class
Vue3 Custom Directives support the following Hook Functions: BeforeMount, mounted, beforeUpdate, updated, beforeUnmount, unmounted. You can view the definitions in vue3. Currently, only mounted, updated, and unmounted are required. These three hooks.
Lazy class infrastructure code, lazy.ts:
export default class Lazy {
public options: LazyOptions = {
loading: DEFAULT_LOADING,
error: DEFAULT_ERROR,
observerOptions: DEFAULT_OBSERVER_OPTIONS,
log: true.lifecycle: {}};constructor(options? : LazyOptions) {
this.config(options)
}
/** * Merge config * assgin (); /** * merge config * assgin ()@param {*} [options={}]
* @memberof Lazy* /
public config(options = {}): void {
assign(this.options, options)
}
public mount(el: HTMLElement, binding: DirectiveBinding<string | ValueFormatterObject>): void {} // Corresponding to directive mount hook
public update() {} // Corresponding to directive update hook
public unmount() {} // Corresponding to directive unmount hook
}
Copy the code
Write lazy loading features
/**
* mount
*
* @param {HTMLElement} el
* @param {DirectiveBinding<string>} binding
* @memberof Lazy* /
public mount(el: HTMLElement, binding: DirectiveBinding<string | ValueFormatterObject>): void {
this._image = el
const { src, loading, error, lifecycle } = this._valueFormatter(binding.value)
this._lifecycle(LifecycleEnum.LOADING, lifecycle)
this._image.setAttribute('src', loading || DEFAULT_LOADING)
if(! hasIntersectionObserver) {this.loadImages(el, src, error, lifecycle)
this._log(() = > {
throw new Error('Not support IntersectionObserver! ')})}this._initIntersectionObserver(el, src, error, lifecycle)
}
/**
* force loading
*
* @param {HTMLElement} el
* @param {string} src
* @memberof Lazy* /
public loadImages(el: HTMLElement, src: string, error? :string, lifecycle? : Lifecycle):void {
this._setImageSrc(el, src, error, lifecycle)
}
/**
* set img tag src
*
* @private
* @param {HTMLElement} el
* @param {string} src
* @memberof Lazy* /
private _setImageSrc(el: HTMLElement, src: string, error? :string, lifecycle? : Lifecycle):void {
const srcset = el.getAttribute('srcset')
if ('img' === el.tagName.toLowerCase()) {
if (src) el.setAttribute('src', src)
if (srcset) el.setAttribute('srcset', srcset)
this._listenImageStatus(el as HTMLImageElement, () = > {
this._log(() = > {
console.log('Image loaded successfully! ')})this._lifecycle(LifecycleEnum.LOADED, lifecycle)
}, () = > {
// Fix onload trigger twice, clear onload event
// Reload on update
el.onload = null
this._lifecycle(LifecycleEnum.ERROR, lifecycle)
this._observer.disconnect()
if (error) el.setAttribute('src', error)
this._log(() = > { throw new Error('Image failed to load! ')})})}else {
el.style.backgroundImage = 'url(\'' + src + '\') '}}/**
* init IntersectionObserver
*
* @private
* @param {HTMLElement} el
* @param {string} src
* @memberof Lazy* /
private _initIntersectionObserver(el: HTMLElement, src: string, error? :string, lifecycle? : Lifecycle):void {
const observerOptions = this.options.observerOptions
this._observer = new IntersectionObserver((entries) = > {
Array.prototype.forEach.call(entries, (entry) = > {
if (entry.isIntersecting) {
this._observer.unobserve(entry.target)
this._setImageSrc(el, src, error, lifecycle)
}
})
}, observerOptions)
this._observer.observe(this._image)
}
/**
* only listen to image status
*
* @private
* @param {string} src
* @param {(string | null)} cors
* @param {() => void} success
* @param {() => void} error
* @memberof Lazy* /
private _listenImageStatus(image: HTMLImageElement, success: ((this: GlobalEventHandlers, ev: Event) => any) | null, error: OnErrorEventHandler) {
image.onload = success
image.onerror = error
}
/**
* to do it differently for object and string
*
* @public
* @param {(ValueFormatterObject | string)} value
* @returns {*}
* @memberof Lazy* /
public _valueFormatter(value: ValueFormatterObject | string): ValueFormatterObject {
let src = value as string
let loading = this.options.loading
let error = this.options.error
let lifecycle = this.options.lifecycle
if (isObject(value)) {
src = (value as ValueFormatterObject).src
loading = (value as ValueFormatterObject).loading || this.options.loading
error = (value as ValueFormatterObject).error || this.options.error
lifecycle = ((value as ValueFormatterObject).lifecycle || this.options.lifecycle)
}
return {
src,
loading,
error,
lifecycle
}
}
/**
* log
*
* @param {() => void} callback
* @memberof Lazy* /
public _log(callback: () = > void) :void {
if (!this.options.log) {
callback()
}
}
/**
* lifecycle easy
*
* @private
* @param {LifecycleEnum} life
* @param {Lifecycle} [lifecycle]
* @memberof Lazy* /
private_lifecycle(life: LifecycleEnum, lifecycle? : Lifecycle):void {
switch (life) {
case LifecycleEnum.LOADING:
this._image.setAttribute('lazy', LifecycleEnum.LOADING)
if(lifecycle? .loading) { lifecycle.loading() }break
case LifecycleEnum.LOADED:
this._image.setAttribute('lazy', LifecycleEnum.LOADED)
if(lifecycle? .loaded) { lifecycle.loaded() }break
case LifecycleEnum.ERROR:
this._image.setAttribute('lazy', LifecycleEnum.ERROR)
if(lifecycle? .error) { lifecycle.error() }break
default:
break}}Copy the code
Write the update hooks
/**
* update
*
* @param {HTMLElement} el
* @memberof Lazy* /
public update(el: HTMLElement, binding: DirectiveBinding<string | ValueFormatterObject>): void {
this._observer.unobserve(el)
const { src, error, lifecycle } = this._valueFormatter(binding.value)
this._initIntersectionObserver(el, src, error, lifecycle)
}
Copy the code
Write unmount to hook
/**
* unmount
*
* @param {HTMLElement} el
* @memberof Lazy* /
public unmount(el: HTMLElement): void {
this._observer.unobserve(el)
}
Copy the code
Write the install method in index.ts to register the plug-in
import Lazy from './lazy'
import { App } from 'vue'
import { LazyOptions } from './types'
export default {
/**
* install plugin
*
* @param {App} Vue
* @param {LazyOptions} options* /
install (Vue: App, options: LazyOptions): void {
const lazy = new Lazy(options)
Vue.config.globalProperties.$Lazyload = lazy
$Lazyload = $Lazyload
$this.$Lazyload = this.$Lazyload = this.$Lazyload = this
// So implement this requirement by providing
Const useLazylaod = inject('Lazyload')
Vue.provide('Lazyload', lazy)
Vue.directive('lazy', {
mounted: lazy.mount.bind(lazy),
updated: lazy.update.bind(lazy),
unmounted: lazy.unmount.bind(lazy)
})
}
}
Copy the code
The use of plug-in
Main.js:
import { createApp } from 'vue'
import App from './App.vue'
import VueLazyLoad from '.. /src/index'
const app = createApp(App)
app.use(VueLazyLoad, {
log: true.lifecycle: {
loading: () = > {
console.log('loading')},error: () = > {
console.log('error')},loaded: () = > {
console.log('loaded')
}
}
})
app.mount('#app')
Copy the code
App.vue:
<template> <div class="margin" /> <img v-lazy="'/example/assets/logo.png'" alt="Vue logo" width="100"> <img v-lazy="{src: errorlazy.src, lifecycle: errorlazy.lifecycle}" alt="Vue logo" class="image" width="100"> <button @click="change"> change </button> </template> <script> import { reactive } from 'vue' export default { name: 'App', setup() { const errorlazy = reactive({ src: '/example/assets/log1o.png', lifecycle: { loading: () => { console.log('image loading') }, error: () => { console.log('image error') }, loaded: () => { console.log('image loaded') } } }) const change = () => { errorlazy.src = 'FM = http://t8.baidu.com/it/u=3571592872, 3353494284 & 79 & app = 86 & size = h300 & & g = 4 n & n = 0 f = jpeg? = 1603764281 & t = bedd2d52d62e141c the SEC bb08c462183601c7' } return { errorlazy, change } } } </script> <style> .margin { margin-top: 1000px; } .image[lazy=loading] { background: goldenrod; } .image[lazy=error] { background: red; } .image[lazy=loaded] { background: green; } </style>Copy the code
The main functions of this plug-in refer to: vue-Lazyload