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