github项目地址: github.com/murongg/vue…
求star 与 issues
我文采不好,可能写的文章不咋样,有什么问题可以在留言区评论,我会尽力解答
本项目已经发布到npm
安装:
$ npm i vue3-lazyload # or $ yarn add vue3-lazyload
需求分析
- 支持自定义 loading 图片,图片加载状态时使用此图片
- 支持自定义 error 图片,图片加载失败后使用此图片
- 支持 lifecycle hooks,类似于 vue 的生命周期,并同时在 img 标签绑定 lazy 属性,类似于
并支持:
img[lazy=loading] {
}
img[lazy=error] {
}
img[lazy=loaded] {
}
支持使用 v-lazy 自定义指令,指定可传入 string/object ,当为 string 时,默认为需要加载的 url,当为 object 时,可传入
- src: 当前需要加载的图片 url
- loading: 加载状态时所用到的图片
- error: 加载失败时所用到的图片
- lifecycle: 本次 lazy 的生命周期,替换掉全局生命周期
目录结构
- src ---- index.ts 入口文件,主要用来注册插件 ---- lazy.ts 懒加载主要功能 ---- types.ts 类型文件,包括 interface/type/enum 等等 ---- util.ts 共享工具文件
编写懒加载类
懒加载主要通过 IntersectionObserver对象实现,可能有些浏览器不支持,暂未做兼容。
确定注册插件时传入的参数
export interface LazyOptions {
error?: string; // 加载失败时的图片
loading?: string; // 加载中的图片
observerOptions?: IntersectionObserverInit; // IntersectionObserver 对象传入的第二个参数
log?: boolean; // 是否需要打印日志
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 in LifecycleEnum]?: () => void;
};
确定类的框架
vue3 的 Custom Directives,支持以下 Hook Functions:beforeMount 、mounted、beforeUpdate、updated、beforeUnmount、unmounted,具体释义可以去 vue3 文档查看,目前仅需要用到mounted、updated、unmounted,这三个 Hook。
Lazy 类基础框架代码,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)
}
public config(options = {}): void {
assign(this.options, options)
}
public mount(el: HTMLElement, binding: DirectiveBinding): void {} // 对应 directive mount hook
public update() {} // 对应 directive update hook
public unmount() {} // 对应 directive unmount hook
}
编写懒加载功能
public mount(el: HTMLElement, binding: DirectiveBinding): 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) } public loadImages(el: HTMLElement, src: string, error?: string, lifecycle?: Lifecycle): void { this._setImageSrc(el, src, error, lifecycle) } 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 + '')' } } 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) } private _listenImageStatus(image: HTMLImageElement, success: ((this: GlobalEventHandlers, ev: Event) => any) | null, error: OnErrorEventHandler) { image.onload = success image.onerror = error } 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 } } public _log(callback: () => void): void { if (!this.options.log) { callback() } } 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 } }
编写 update hook
public update(el: HTMLElement, binding: DirectiveBinding): void { this._observer.unobserve(el) const { src, error, lifecycle } = this._valueFormatter(binding.value) this._initIntersectionObserver(el, src, error, lifecycle) }
编写 unmount hook
public unmount(el: HTMLElement): void {
this._observer.unobserve(el)
}
在 index.ts 编写注册插件需要用到的 install 方法
import Lazy from './lazy'
import { App } from 'vue'
import { LazyOptions } from './types'
export default {
install (Vue: App, options: LazyOptions): void {
const lazy = new Lazy(options)
Vue.config.globalProperties.$Lazyload = lazy
// 留着备用,为了兼容$Lazyload
// 选项api,可以通过this.$Lazyload获取到Lazy类的实例,组合api我还不知道怎么获取
// 所以通过 provide 来实现此需求
// 使用方式 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)
})
}
}
使用插件
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')
App.vue:
.margin { margin-top: 1000px; } .image[lazy=loading] { background: goldenrod; } .image[lazy=error] { background: red; } .image[lazy=loaded] { background: green; }
以上就是vue3+typescript实现图片懒加载插件的详细内容,更多关于vue3 图片懒加载的资料请关注考高分网其它相关文章!



