Skip to content

Commit 3479e63

Browse files
committed
feat: refactor
Description TBA, fixes #3
1 parent 9ec7b99 commit 3479e63

File tree

2 files changed

+68
-48
lines changed

2 files changed

+68
-48
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "vue3-lazyload",
3-
"version": "0.3.6",
3+
"version": "0.4.0-refactored",
44
"packageManager": "pnpm@6.32.3",
55
"description": "A Vue3.x image lazyload plugin",
66
"license": "MIT",

src/lazy.ts

Lines changed: 67 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
2-
/* eslint-disable @typescript-eslint/no-empty-function */
31
import type { DirectiveBinding } from 'vue-demi'
42
import type { LazyOptions, Lifecycle, ValueFormatterObject } from './types'
53
import { LifecycleEnum } from './types'
@@ -57,13 +55,7 @@ export default class Lazy {
5755
const { src, loading, error, lifecycle, delay } = this._valueFormatter(typeof binding === 'string' ? binding : binding.value)
5856
this._lifecycle(LifecycleEnum.LOADING, lifecycle, el)
5957
el.setAttribute('src', loading || DEFAULT_LOADING)
60-
if (!hasIntersectionObserver) {
61-
this.loadImages(el, src, error, lifecycle)
62-
this._log(() => {
63-
throw new Error('Not support IntersectionObserver!')
64-
})
65-
}
66-
this._initIntersectionObserver(el, src, error, lifecycle, delay)
58+
this._tryInitIntersectionObserver(el, src, loading, error, lifecycle, delay)
6759
}
6860

6961
/**
@@ -76,8 +68,8 @@ export default class Lazy {
7668
if (!el)
7769
return
7870
this._realObserver(el)?.unobserve(el)
79-
const { src, error, lifecycle, delay } = this._valueFormatter(typeof binding === 'string' ? binding : binding.value)
80-
this._initIntersectionObserver(el, src, error, lifecycle, delay)
71+
const { src, loading, error, lifecycle, delay } = this._valueFormatter(typeof binding === 'string' ? binding : binding.value)
72+
this._tryInitIntersectionObserver(el, src, loading, error, lifecycle, delay)
8173
}
8274

8375
/**
@@ -93,51 +85,79 @@ export default class Lazy {
9385
this._images.delete(el)
9486
}
9587

88+
private _tryLoadImage(el: HTMLElement, src: string, onSuccess: ((this: GlobalEventHandlers, ev: Event) => any) | null, onError: OnErrorEventHandler) {
89+
const img = new Image()
90+
img.src = src
91+
92+
const _onSuccess = el
93+
? (...p) => {
94+
this._setImageSrc(el, src)
95+
if (onSuccess)
96+
// eslint-disable-next-line prefer-spread
97+
onSuccess.apply(undefined, p)
98+
}
99+
: onSuccess
100+
101+
this._listenImageStatus(img, _onSuccess, onError)
102+
103+
return img
104+
}
105+
96106
/**
97-
* force loading
107+
* update image with full lifecycles
98108
*
99109
* @param {HTMLElement} el
100110
* @param {string} src
101111
* @memberof Lazy
102112
*/
103-
public loadImages(el: HTMLElement, src: string, error?: string, lifecycle?: Lifecycle): void {
104-
this._setImageSrc(el, src, error, lifecycle)
113+
public loadImage(el: HTMLElement, src: string, loading?: string, error?: string, lifecycle?: Lifecycle): void {
114+
const onSuccess = () => {
115+
this._lifecycle(LifecycleEnum.LOADED, lifecycle, el)
116+
}
117+
118+
const onError = () => {
119+
this._listenImageStatus(el, null, null)
120+
this._realObserver(el)?.unobserve(el)
121+
122+
this._lifecycle(LifecycleEnum.ERROR, lifecycle, el)
123+
if (error || DEFAULT_ERROR)
124+
this._setImageSrc(el, error || DEFAULT_ERROR)
125+
126+
this._log(() => {
127+
throw new Error(`Image failed to load! And failed src was: ${src} `)
128+
})
129+
}
130+
131+
// loading state
132+
this._lifecycle(LifecycleEnum.LOADING, lifecycle, el)
133+
this._setImageSrc(el, loading || DEFAULT_LOADING)
134+
135+
this._tryLoadImage(el, src, onSuccess, onError)
105136
}
106137

107138
/**
108-
* set img tag src
139+
* set img src
109140
*
110141
* @private
111142
* @param {HTMLElement} el
112143
* @param {string} src
113144
* @memberof Lazy
114145
*/
115-
private _setImageSrc(el: HTMLElement, src: string, error?: string, lifecycle?: Lifecycle): void {
116-
if (el.tagName.toLowerCase() === 'img') {
117-
if (src) {
118-
const preSrc = el.getAttribute('src')
119-
if (preSrc !== src)
120-
el.setAttribute('src', src)
121-
}
122-
this._listenImageStatus(el as HTMLImageElement, () => {
123-
this._lifecycle(LifecycleEnum.LOADED, lifecycle, el)
124-
}, () => {
125-
// Fix onload trigger twice, clear onload event
126-
// Reload on update
127-
el.onload = null
128-
this._lifecycle(LifecycleEnum.ERROR, lifecycle, el)
129-
this._realObserver(el)?.disconnect()
130-
if (error) {
131-
const newImageSrc = el.getAttribute('src')
132-
if (newImageSrc !== error)
133-
el.setAttribute('src', error)
134-
}
135-
this._log(() => { throw new Error(`Image failed to load!And failed src was: ${src} `) })
136-
})
137-
}
138-
else {
146+
private _setImageSrc(el: HTMLElement, src: string): void {
147+
if (el.tagName.toLowerCase() === 'img')
148+
el.setAttribute('src', src)
149+
else
139150
el.style.backgroundImage = `url('${src}')`
151+
}
152+
153+
private _tryInitIntersectionObserver(el: HTMLElement, src: string, loading?: string, error?: string, lifecycle?: Lifecycle, delay?: number): void {
154+
if (!hasIntersectionObserver) {
155+
this.loadImage(el, src, loading, error, lifecycle)
156+
this._log(() => {
157+
throw new Error('Not support IntersectionObserver!')
158+
})
140159
}
160+
this._initIntersectionObserver(el, src, loading, error, lifecycle, delay)
141161
}
142162

143163
/**
@@ -148,33 +168,33 @@ export default class Lazy {
148168
* @param {string} src
149169
* @memberof Lazy
150170
*/
151-
private _initIntersectionObserver(el: HTMLElement, src: string, error?: string, lifecycle?: Lifecycle, delay?: number): void {
171+
private _initIntersectionObserver(el: HTMLElement, src: string, loading?: string, error?: string, lifecycle?: Lifecycle, delay?: number): void {
152172
const observerOptions = this.options.observerOptions
153173
this._images.set(el, new IntersectionObserver((entries) => {
154174
Array.prototype.forEach.call(entries, (entry) => {
155175
if (delay && delay > 0)
156-
this._delayedIntersectionCallback(el, entry, delay, src, error, lifecycle)
176+
this._delayedIntersectionCallback(el, entry, delay, src, loading, error, lifecycle)
157177
else
158-
this._intersectionCallback(el, entry, src, error, lifecycle)
178+
this._intersectionCallback(el, entry, src, loading, error, lifecycle)
159179
})
160180
}, observerOptions))
161181
this._realObserver(el)?.observe(el)
162182
}
163183

164-
private _intersectionCallback(el: HTMLElement, entry: IntersectionObserverEntry, src: string, error?: string, lifecycle?: Lifecycle): void {
184+
private _intersectionCallback(el: HTMLElement, entry: IntersectionObserverEntry, src: string, loading?: string, error?: string, lifecycle?: Lifecycle): void {
165185
if (entry.isIntersecting) {
166186
this._realObserver(el)?.unobserve(entry.target)
167-
this._setImageSrc(el, src, error, lifecycle)
187+
this.loadImage(el, src, loading, error, lifecycle)
168188
}
169189
}
170190

171-
private _delayedIntersectionCallback(el: HTMLElement, entry: IntersectionObserverEntry, delay: number, src: string, error?: string, lifecycle?: Lifecycle): void {
191+
private _delayedIntersectionCallback(el: HTMLElement, entry: IntersectionObserverEntry, delay: number, src: string, loading?: string, error?: string, lifecycle?: Lifecycle): void {
172192
if (entry.isIntersecting) {
173193
if (entry.target.hasAttribute(TIMEOUT_ID_DATA_ATTR))
174194
return
175195

176196
const timeoutId = setTimeout(() => {
177-
this._intersectionCallback(el, entry, src, error, lifecycle)
197+
this._intersectionCallback(el, entry, src, loading, error, lifecycle)
178198
entry.target.removeAttribute(TIMEOUT_ID_DATA_ATTR)
179199
}, delay)
180200
entry.target.setAttribute(TIMEOUT_ID_DATA_ATTR, String(timeoutId))
@@ -197,7 +217,7 @@ export default class Lazy {
197217
* @param {() => void} error
198218
* @memberof Lazy
199219
*/
200-
private _listenImageStatus(image: HTMLImageElement, success: ((this: GlobalEventHandlers, ev: Event) => any) | null, error: OnErrorEventHandler) {
220+
private _listenImageStatus(image: HTMLImageElement | HTMLElement, success: ((this: GlobalEventHandlers, ev: Event) => any) | null, error: OnErrorEventHandler) {
201221
image.onload = success
202222
image.onerror = error
203223
}

0 commit comments

Comments
 (0)