1
- /* eslint-disable @typescript-eslint/explicit-module-boundary-types */
2
- /* eslint-disable @typescript-eslint/no-empty-function */
3
1
import type { DirectiveBinding } from 'vue-demi'
4
2
import type { LazyOptions , Lifecycle , ValueFormatterObject } from './types'
5
3
import { LifecycleEnum } from './types'
@@ -57,13 +55,7 @@ export default class Lazy {
57
55
const { src, loading, error, lifecycle, delay } = this . _valueFormatter ( typeof binding === 'string' ? binding : binding . value )
58
56
this . _lifecycle ( LifecycleEnum . LOADING , lifecycle , el )
59
57
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 )
67
59
}
68
60
69
61
/**
@@ -76,8 +68,8 @@ export default class Lazy {
76
68
if ( ! el )
77
69
return
78
70
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 )
81
73
}
82
74
83
75
/**
@@ -93,51 +85,79 @@ export default class Lazy {
93
85
this . _images . delete ( el )
94
86
}
95
87
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
+
96
106
/**
97
- * force loading
107
+ * update image with full lifecycles
98
108
*
99
109
* @param {HTMLElement } el
100
110
* @param {string } src
101
111
* @memberof Lazy
102
112
*/
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 )
105
136
}
106
137
107
138
/**
108
- * set img tag src
139
+ * set img src
109
140
*
110
141
* @private
111
142
* @param {HTMLElement } el
112
143
* @param {string } src
113
144
* @memberof Lazy
114
145
*/
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
139
150
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
+ } )
140
159
}
160
+ this . _initIntersectionObserver ( el , src , loading , error , lifecycle , delay )
141
161
}
142
162
143
163
/**
@@ -148,33 +168,33 @@ export default class Lazy {
148
168
* @param {string } src
149
169
* @memberof Lazy
150
170
*/
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 {
152
172
const observerOptions = this . options . observerOptions
153
173
this . _images . set ( el , new IntersectionObserver ( ( entries ) => {
154
174
Array . prototype . forEach . call ( entries , ( entry ) => {
155
175
if ( delay && delay > 0 )
156
- this . _delayedIntersectionCallback ( el , entry , delay , src , error , lifecycle )
176
+ this . _delayedIntersectionCallback ( el , entry , delay , src , loading , error , lifecycle )
157
177
else
158
- this . _intersectionCallback ( el , entry , src , error , lifecycle )
178
+ this . _intersectionCallback ( el , entry , src , loading , error , lifecycle )
159
179
} )
160
180
} , observerOptions ) )
161
181
this . _realObserver ( el ) ?. observe ( el )
162
182
}
163
183
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 {
165
185
if ( entry . isIntersecting ) {
166
186
this . _realObserver ( el ) ?. unobserve ( entry . target )
167
- this . _setImageSrc ( el , src , error , lifecycle )
187
+ this . loadImage ( el , src , loading , error , lifecycle )
168
188
}
169
189
}
170
190
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 {
172
192
if ( entry . isIntersecting ) {
173
193
if ( entry . target . hasAttribute ( TIMEOUT_ID_DATA_ATTR ) )
174
194
return
175
195
176
196
const timeoutId = setTimeout ( ( ) => {
177
- this . _intersectionCallback ( el , entry , src , error , lifecycle )
197
+ this . _intersectionCallback ( el , entry , src , loading , error , lifecycle )
178
198
entry . target . removeAttribute ( TIMEOUT_ID_DATA_ATTR )
179
199
} , delay )
180
200
entry . target . setAttribute ( TIMEOUT_ID_DATA_ATTR , String ( timeoutId ) )
@@ -197,7 +217,7 @@ export default class Lazy {
197
217
* @param {() => void } error
198
218
* @memberof Lazy
199
219
*/
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 ) {
201
221
image . onload = success
202
222
image . onerror = error
203
223
}
0 commit comments