@@ -2,8 +2,8 @@ import { ChangeDetectionStrategy, Component, ElementRef, Input, NgZone, OnDestro
2
2
3
3
import { Content } from '../content/content' ;
4
4
import { DomController } from '../../util/dom-controller' ;
5
- import { ImgLoader , ImgLoadCallback } from './img-loader' ;
6
5
import { isPresent , isTrueProperty } from '../../util/util' ;
6
+ import { listenEvent , eventOptions } from '../../util/ui-event-manager' ;
7
7
import { Platform } from '../../platform/platform' ;
8
8
9
9
@@ -80,40 +80,12 @@ import { Platform } from '../../platform/platform';
80
80
* Its concrete object size is resolved as a cover constraint against the
81
81
* element’s used width and height.
82
82
*
83
+ * ### Future Optimizations
83
84
*
84
- * ### Web Worker and XHR Requests
85
- *
86
- * Another big cause of scroll jank is kicking off a new HTTP request,
87
- * which is exactly what images do. Normally, this isn't a problem for
88
- * something like a blog since all image HTTP requests are started immediately
89
- * as HTML parses. However, Ionic has the ability to include hundreds, or even
90
- * thousands of images within one page, but its not actually loading all of
91
- * the images at the same time.
92
- *
93
- * Imagine an app where users can scroll slowly, or very quickly, through
94
- * thousands of images. If they're scrolling extremely fast, ideally the app
95
- * wouldn't want to start all of those image requests, but if they're scrolling
96
- * slowly they would. Additionally, most browsers can only have six requests at
97
- * one time for the same domain, so it's extemely important that we're managing
98
- * exacctly which images we should downloading. Basically we want to ensure
99
- * that the app is requesting the most important images, and aborting
100
- * unnecessary requests, which is another benefit of using `ion-img`.
101
- *
102
- * Next, by running the image request within a web worker, we're able to pass
103
- * off the heavy lifting to another thread. Not only are able to take the load
104
- * of the main thread, but we're also able to accurately control exactly which
105
- * images should be downloading, along with the ability to abort unnecessary
106
- * requests. Aborting requets is just as important so that Ionic can free up
107
- * connections for the most important images which are visible.
108
- *
109
- * One restriction however, is that all image requests must work with
110
- * [cross-origin HTTP requests (CORS)](https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS).
111
- * Traditionally, the `img` element does not have this issue, but because
112
- * `ion-img` uses `XMLHttpRequest` within a web worker, then requests for
113
- * images must be served from the same domain, or the image server's response
114
- * must set the `Access-Control-Allow-Origin` HTTP header. Again, if your app
115
- * does not have the same problems which `ion-img` is solving, then it's
116
- * recommended to just use the standard `img` HTML element instead.
85
+ * Future goals are to place image requests within web workers, and cache
86
+ * images in-memory as datauris. This method has proven to be effective,
87
+ * however there are some current limitations with Cordova which we are
88
+ * currently working on.
117
89
*
118
90
*/
119
91
@Component ( {
@@ -130,12 +102,10 @@ export class Img implements OnDestroy {
130
102
/** @internal */
131
103
_renderedSrc : string ;
132
104
/** @internal */
133
- _tmpDataUri : string ;
105
+ _hasLoaded : boolean ;
134
106
/** @internal */
135
107
_cache : boolean = true ;
136
108
/** @internal */
137
- _cb : ImgLoadCallback ;
138
- /** @internal */
139
109
_bounds : any ;
140
110
/** @internal */
141
111
_rect : any ;
@@ -147,6 +117,10 @@ export class Img implements OnDestroy {
147
117
_wQ : string = '' ;
148
118
/** @internal */
149
119
_hQ : string = '' ;
120
+ /** @internal */
121
+ _img : HTMLImageElement ;
122
+ /** @internal */
123
+ _unreg : Function ;
150
124
151
125
/** @private */
152
126
canRequest : boolean ;
@@ -155,7 +129,6 @@ export class Img implements OnDestroy {
155
129
156
130
157
131
constructor (
158
- private _ldr : ImgLoader ,
159
132
private _elementRef : ElementRef ,
160
133
private _renderer : Renderer ,
161
134
private _platform : Platform ,
@@ -191,11 +164,11 @@ export class Img implements OnDestroy {
191
164
192
165
if ( newSrc . indexOf ( 'data:' ) === 0 ) {
193
166
// they're using an actual datauri already
194
- this . _tmpDataUri = newSrc ;
167
+ this . _hasLoaded = true ;
195
168
196
169
} else {
197
170
// reset any existing datauri we might be holding onto
198
- this . _tmpDataUri = null ;
171
+ this . _hasLoaded = false ;
199
172
}
200
173
201
174
// run update to kick off requests or render if everything is good
@@ -210,7 +183,7 @@ export class Img implements OnDestroy {
210
183
if ( this . _requestingSrc ) {
211
184
// abort any active requests
212
185
console . debug ( `abortRequest ${ this . _requestingSrc } ${ Date . now ( ) } ` ) ;
213
- this . _ldr . abort ( this . _requestingSrc ) ;
186
+ this . _srcAttr ( '' ) ;
214
187
this . _requestingSrc = null ;
215
188
}
216
189
if ( this . _renderedSrc ) {
@@ -228,61 +201,34 @@ export class Img implements OnDestroy {
228
201
// only attempt an update if there is an active src
229
202
// and the content containing the image considers it updatable
230
203
if ( this . _src && this . _content . isImgsUpdatable ( ) ) {
231
- if ( this . canRequest && ( this . _src !== this . _renderedSrc && this . _src !== this . _requestingSrc ) && ! this . _tmpDataUri ) {
204
+ if ( this . canRequest && ( this . _src !== this . _renderedSrc && this . _src !== this . _requestingSrc ) && ! this . _hasLoaded ) {
232
205
// only begin the request if we "can" request
233
206
// begin the image request if the src is different from the rendered src
234
207
// and if we don't already has a tmpDataUri
235
208
console . debug ( `request ${ this . _src } ${ Date . now ( ) } ` ) ;
236
209
this . _requestingSrc = this . _src ;
237
210
238
- this . _cb = ( status , msg , datauri ) => {
239
- this . _loadResponse ( status , msg , datauri ) ;
240
- this . _cb = null ;
241
- } ;
242
-
243
- // post the message to the web worker
244
- this . _ldr . load ( this . _src , this . _cache , this . _cb ) ;
211
+ this . _isLoaded ( false ) ;
212
+ this . _srcAttr ( this . _src ) ;
245
213
246
214
// set the dimensions of the image if we do have different data
247
215
this . _setDims ( ) ;
248
216
}
249
217
250
- if ( this . canRender && this . _tmpDataUri && this . _src !== this . _renderedSrc ) {
218
+ if ( this . canRender && this . _hasLoaded && this . _src !== this . _renderedSrc ) {
251
219
// we can render and we have a datauri to render
252
220
this . _renderedSrc = this . _src ;
253
221
this . _setDims ( ) ;
254
222
this . _dom . write ( ( ) => {
255
- if ( this . _tmpDataUri ) {
223
+ if ( this . _hasLoaded ) {
256
224
console . debug ( `render ${ this . _src } ${ Date . now ( ) } ` ) ;
257
225
this . _isLoaded ( true ) ;
258
- this . _srcAttr ( this . _tmpDataUri ) ;
259
- this . _tmpDataUri = null ;
260
226
}
261
227
} ) ;
262
228
}
263
229
}
264
230
}
265
231
266
- private _loadResponse ( status : number , msg : string , datauri : string ) {
267
- this . _requestingSrc = null ;
268
-
269
- if ( status === 200 ) {
270
- // success :)
271
- this . _tmpDataUri = datauri ;
272
- this . update ( ) ;
273
-
274
- } else {
275
- // error :(
276
- if ( status ) {
277
- console . error ( `img, status: ${ status } ${ msg } ` ) ;
278
- }
279
- this . _renderedSrc = this . _tmpDataUri = null ;
280
- this . _dom . write ( ( ) => {
281
- this . _isLoaded ( false ) ;
282
- } ) ;
283
- }
284
- }
285
-
286
232
/**
287
233
* @internal
288
234
*/
@@ -297,11 +243,10 @@ export class Img implements OnDestroy {
297
243
* @internal
298
244
*/
299
245
_srcAttr ( srcAttr : string ) {
300
- const imgEle = this . _elementRef . nativeElement . firstChild ;
301
246
const renderer = this . _renderer ;
302
247
303
- renderer . setElementAttribute ( imgEle , 'src' , srcAttr ) ;
304
- renderer . setElementAttribute ( imgEle , 'alt' , this . alt ) ;
248
+ renderer . setElementAttribute ( this . _img , 'src' , srcAttr ) ;
249
+ renderer . setElementAttribute ( this . _img , 'alt' , this . alt ) ;
305
250
}
306
251
307
252
/**
@@ -409,11 +354,25 @@ export class Img implements OnDestroy {
409
354
*/
410
355
@Input ( ) alt : string = '' ;
411
356
357
+ /**
358
+ * @private
359
+ */
360
+ ngAfterContentInit ( ) {
361
+ this . _img = this . _elementRef . nativeElement . firstChild ;
362
+
363
+ this . _unreg && this . _unreg ( ) ;
364
+ const opts = eventOptions ( false , true ) ;
365
+ this . _unreg = listenEvent ( this . _img , 'load' , false , opts , ( ) => {
366
+ this . _hasLoaded = true ;
367
+ this . update ( ) ;
368
+ } ) ;
369
+ }
370
+
412
371
/**
413
372
* @private
414
373
*/
415
374
ngOnDestroy ( ) {
416
- this . _cb = null ;
375
+ this . _unreg && this . _unreg ( ) ;
417
376
this . _content && this . _content . removeImg ( this ) ;
418
377
}
419
378
0 commit comments