Skip to content

Commit 4f61ea5

Browse files
committed
fix(img): use img tag due to cordova limitations
1 parent 1f83cde commit 4f61ea5

File tree

4 files changed

+37
-322
lines changed

4 files changed

+37
-322
lines changed

src/components/img/img-loader.ts

Lines changed: 0 additions & 175 deletions
This file was deleted.

src/components/img/img.ts

Lines changed: 36 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@ import { ChangeDetectionStrategy, Component, ElementRef, Input, NgZone, OnDestro
22

33
import { Content } from '../content/content';
44
import { DomController } from '../../util/dom-controller';
5-
import { ImgLoader, ImgLoadCallback } from './img-loader';
65
import { isPresent, isTrueProperty } from '../../util/util';
6+
import { listenEvent, eventOptions } from '../../util/ui-event-manager';
77
import { Platform } from '../../platform/platform';
88

99

@@ -80,40 +80,12 @@ import { Platform } from '../../platform/platform';
8080
* Its concrete object size is resolved as a cover constraint against the
8181
* element’s used width and height.
8282
*
83+
* ### Future Optimizations
8384
*
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.
11789
*
11890
*/
11991
@Component({
@@ -130,12 +102,10 @@ export class Img implements OnDestroy {
130102
/** @internal */
131103
_renderedSrc: string;
132104
/** @internal */
133-
_tmpDataUri: string;
105+
_hasLoaded: boolean;
134106
/** @internal */
135107
_cache: boolean = true;
136108
/** @internal */
137-
_cb: ImgLoadCallback;
138-
/** @internal */
139109
_bounds: any;
140110
/** @internal */
141111
_rect: any;
@@ -147,6 +117,10 @@ export class Img implements OnDestroy {
147117
_wQ: string = '';
148118
/** @internal */
149119
_hQ: string = '';
120+
/** @internal */
121+
_img: HTMLImageElement;
122+
/** @internal */
123+
_unreg: Function;
150124

151125
/** @private */
152126
canRequest: boolean;
@@ -155,7 +129,6 @@ export class Img implements OnDestroy {
155129

156130

157131
constructor(
158-
private _ldr: ImgLoader,
159132
private _elementRef: ElementRef,
160133
private _renderer: Renderer,
161134
private _platform: Platform,
@@ -191,11 +164,11 @@ export class Img implements OnDestroy {
191164

192165
if (newSrc.indexOf('data:') === 0) {
193166
// they're using an actual datauri already
194-
this._tmpDataUri = newSrc;
167+
this._hasLoaded = true;
195168

196169
} else {
197170
// reset any existing datauri we might be holding onto
198-
this._tmpDataUri = null;
171+
this._hasLoaded = false;
199172
}
200173

201174
// run update to kick off requests or render if everything is good
@@ -210,7 +183,7 @@ export class Img implements OnDestroy {
210183
if (this._requestingSrc) {
211184
// abort any active requests
212185
console.debug(`abortRequest ${this._requestingSrc} ${Date.now()}`);
213-
this._ldr.abort(this._requestingSrc);
186+
this._srcAttr('');
214187
this._requestingSrc = null;
215188
}
216189
if (this._renderedSrc) {
@@ -228,61 +201,34 @@ export class Img implements OnDestroy {
228201
// only attempt an update if there is an active src
229202
// and the content containing the image considers it updatable
230203
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) {
232205
// only begin the request if we "can" request
233206
// begin the image request if the src is different from the rendered src
234207
// and if we don't already has a tmpDataUri
235208
console.debug(`request ${this._src} ${Date.now()}`);
236209
this._requestingSrc = this._src;
237210

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);
245213

246214
// set the dimensions of the image if we do have different data
247215
this._setDims();
248216
}
249217

250-
if (this.canRender && this._tmpDataUri && this._src !== this._renderedSrc) {
218+
if (this.canRender && this._hasLoaded && this._src !== this._renderedSrc) {
251219
// we can render and we have a datauri to render
252220
this._renderedSrc = this._src;
253221
this._setDims();
254222
this._dom.write(() => {
255-
if (this._tmpDataUri) {
223+
if (this._hasLoaded) {
256224
console.debug(`render ${this._src} ${Date.now()}`);
257225
this._isLoaded(true);
258-
this._srcAttr(this._tmpDataUri);
259-
this._tmpDataUri = null;
260226
}
261227
});
262228
}
263229
}
264230
}
265231

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-
286232
/**
287233
* @internal
288234
*/
@@ -297,11 +243,10 @@ export class Img implements OnDestroy {
297243
* @internal
298244
*/
299245
_srcAttr(srcAttr: string) {
300-
const imgEle = this._elementRef.nativeElement.firstChild;
301246
const renderer = this._renderer;
302247

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);
305250
}
306251

307252
/**
@@ -409,11 +354,25 @@ export class Img implements OnDestroy {
409354
*/
410355
@Input() alt: string = '';
411356

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+
412371
/**
413372
* @private
414373
*/
415374
ngOnDestroy() {
416-
this._cb = null;
375+
this._unreg && this._unreg();
417376
this._content && this._content.removeImg(this);
418377
}
419378

0 commit comments

Comments
 (0)