vue3+typescript實(shí)現(xiàn)圖片懶加載插件
github項(xiàng)目地址: github.com/murongg/vue…
求star 與 issues
我文采不好,可能寫的文章不咋樣,有什么問題可以在留言區(qū)評(píng)論,我會(huì)盡力解答
本項(xiàng)目已經(jīng)發(fā)布到npm
安裝:
$ npm i vue3-lazyload # or $ yarn add vue3-lazyload
需求分析
- 支持自定義 loading 圖片,圖片加載狀態(tài)時(shí)使用此圖片
- 支持自定義 error 圖片,圖片加載失敗后使用此圖片
- 支持 lifecycle hooks,類似于 vue 的生命周期,并同時(shí)在
img標(biāo)簽綁定lazy屬性,類似于
<img src="..." lazy="loading"> <img src="..." lazy="loaded"> <img src="..." lazy="error">
并支持:
img[lazy=loading] {
/*your style here*/
}
img[lazy=error] {
/*your style here*/
}
img[lazy=loaded] {
/*your style here*/
}
支持使用 v-lazy 自定義指令,指定可傳入 string/object ,當(dāng)為 string 時(shí),默認(rèn)為需要加載的 url,當(dāng)為 object 時(shí),可傳入
src: 當(dāng)前需要加載的圖片 urlloading: 加載狀態(tài)時(shí)所用到的圖片error: 加載失敗時(shí)所用到的圖片lifecycle: 本次 lazy 的生命周期,替換掉全局生命周期
目錄結(jié)構(gòu)
- src ---- index.ts 入口文件,主要用來注冊(cè)插件 ---- lazy.ts 懶加載主要功能 ---- types.ts 類型文件,包括 interface/type/enum 等等 ---- util.ts 共享工具文件
編寫懶加載類
懶加載主要通過 IntersectionObserver對(duì)象實(shí)現(xiàn),可能有些瀏覽器不支持,暫未做兼容。
確定注冊(cè)插件時(shí)傳入的參數(shù)
export interface LazyOptions {
error?: string; // 加載失敗時(shí)的圖片
loading?: string; // 加載中的圖片
observerOptions?: IntersectionObserverInit; // IntersectionObserver 對(duì)象傳入的第二個(gè)參數(shù)
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,這三個(gè) Hook。
Lazy 類基礎(chǔ)框架代碼,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 方法在 util.ts 文件內(nèi),此文章不在贅述此方法代碼,可在后文 github 倉庫內(nèi)查看此代碼
* 此方法主要功能是合并兩個(gè)對(duì)象
*
* @param {*} [options={}]
* @memberof Lazy
*/
public config(options = {}): void {
assign(this.options, options)
}
public mount(el: HTMLElement, binding: DirectiveBinding<string | ValueFormatterObject>): void {} // 對(duì)應(yīng) directive mount hook
public update() {} // 對(duì)應(yīng) directive update hook
public unmount() {} // 對(duì)應(yīng) directive unmount hook
}
編寫懶加載功能
/**
* 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
}
}
編寫 update hook
/**
* 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)
}
編寫 unmount hook
/**
* unmount
*
* @param {HTMLElement} el
* @memberof Lazy
*/
public unmount(el: HTMLElement): void {
this._observer.unobserve(el)
}
在 index.ts 編寫注冊(cè)插件需要用到的 install 方法
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
// 選項(xiàng)api,可以通過this.$Lazyload獲取到Lazy類的實(shí)例,組合api我還不知道怎么獲取
// 所以通過 provide 來實(shí)現(xiàn)此需求
// 使用方式 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:
<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 = 'http://t8.baidu.com/it/u=3571592872,3353494284&fm=79&app=86&size=h300&n=0&g=4n&f=jpeg?sec=1603764281&t=bedd2d52d62e141cbb08c462183601c7'
}
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>
以上就是vue3+typescript實(shí)現(xiàn)圖片懶加載插件的詳細(xì)內(nèi)容,更多關(guān)于vue3 圖片懶加載的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
vue項(xiàng)目實(shí)現(xiàn)路由跳轉(zhuǎn)到新頁面,返回舊頁面,保留之前的數(shù)據(jù)記錄(操作代碼)
這篇文章主要介紹了vue項(xiàng)目實(shí)現(xiàn)路由跳轉(zhuǎn)到新頁面,返回舊頁面,保留之前的數(shù)據(jù)記錄,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-09-09
vue3+vite使用vite-plugin-svg-icons插件顯示本地svg圖標(biāo)的方法
這篇文章主要介紹了vue3+vite使用vite-plugin-svg-icons插件顯示本地svg圖標(biāo)的方法,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-12-12
新版vue-cli模板下本地開發(fā)環(huán)境使用node服務(wù)器跨域的方法
這篇文章主要介紹了新版vue-cli模板下本地開發(fā)環(huán)境使用node服務(wù)器跨域的方法,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2018-04-04
如何在Vue3和Vite項(xiàng)目中用SQLite數(shù)據(jù)庫進(jìn)行數(shù)據(jù)存儲(chǔ)
SQLite是一種嵌入式關(guān)系型數(shù)據(jù)庫管理系統(tǒng),是一個(gè)零配置、無服務(wù)器的、自給自足的、事務(wù)性的SQL數(shù)據(jù)庫引擎,這篇文章主要給大家介紹了關(guān)于如何在Vue3和Vite項(xiàng)目中用SQLite數(shù)據(jù)庫進(jìn)行數(shù)據(jù)存儲(chǔ)的相關(guān)資料,需要的朋友可以參考下2024-03-03
vue在antDesign框架或elementUI框架組件native事件中觸發(fā)2次問題
這篇文章主要介紹了vue在antDesign框架或elementUI框架組件native事件中觸發(fā)2次問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-04-04
前端通過vue調(diào)用后端接口導(dǎo)出excel表格基本步驟
在Vue前端項(xiàng)目中,可通過axios庫發(fā)送請(qǐng)求至后端獲取Excel下載鏈接,文中通過代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2024-10-10
vue實(shí)現(xiàn)點(diǎn)擊按鈕“查看詳情”彈窗展示詳情列表操作
這篇文章主要介紹了vue實(shí)現(xiàn)點(diǎn)擊按鈕“查看詳情”彈窗展示詳情列表操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2020-09-09
Vue使用輪詢定時(shí)發(fā)送請(qǐng)求代碼
這篇文章主要介紹了Vue使用輪詢定時(shí)發(fā)送請(qǐng)求代碼,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2020-08-08

