Skip to content

Commit

Permalink
fix(img): use img tag due to cordova limitations
Browse files Browse the repository at this point in the history
  • Loading branch information
adamdbradley committed Dec 14, 2016
1 parent 1f83cde commit 4f61ea5
Show file tree
Hide file tree
Showing 4 changed files with 37 additions and 322 deletions.
175 changes: 0 additions & 175 deletions src/components/img/img-loader.ts

This file was deleted.

113 changes: 36 additions & 77 deletions src/components/img/img.ts
Expand Up @@ -2,8 +2,8 @@ import { ChangeDetectionStrategy, Component, ElementRef, Input, NgZone, OnDestro

import { Content } from '../content/content';
import { DomController } from '../../util/dom-controller';
import { ImgLoader, ImgLoadCallback } from './img-loader';
import { isPresent, isTrueProperty } from '../../util/util';
import { listenEvent, eventOptions } from '../../util/ui-event-manager';
import { Platform } from '../../platform/platform';


Expand Down Expand Up @@ -80,40 +80,12 @@ import { Platform } from '../../platform/platform';
* Its concrete object size is resolved as a cover constraint against the
* element’s used width and height.
*
* ### Future Optimizations
*
* ### Web Worker and XHR Requests
*
* Another big cause of scroll jank is kicking off a new HTTP request,
* which is exactly what images do. Normally, this isn't a problem for
* something like a blog since all image HTTP requests are started immediately
* as HTML parses. However, Ionic has the ability to include hundreds, or even
* thousands of images within one page, but its not actually loading all of
* the images at the same time.
*
* Imagine an app where users can scroll slowly, or very quickly, through
* thousands of images. If they're scrolling extremely fast, ideally the app
* wouldn't want to start all of those image requests, but if they're scrolling
* slowly they would. Additionally, most browsers can only have six requests at
* one time for the same domain, so it's extemely important that we're managing
* exacctly which images we should downloading. Basically we want to ensure
* that the app is requesting the most important images, and aborting
* unnecessary requests, which is another benefit of using `ion-img`.
*
* Next, by running the image request within a web worker, we're able to pass
* off the heavy lifting to another thread. Not only are able to take the load
* of the main thread, but we're also able to accurately control exactly which
* images should be downloading, along with the ability to abort unnecessary
* requests. Aborting requets is just as important so that Ionic can free up
* connections for the most important images which are visible.
*
* One restriction however, is that all image requests must work with
* [cross-origin HTTP requests (CORS)](https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS).
* Traditionally, the `img` element does not have this issue, but because
* `ion-img` uses `XMLHttpRequest` within a web worker, then requests for
* images must be served from the same domain, or the image server's response
* must set the `Access-Control-Allow-Origin` HTTP header. Again, if your app
* does not have the same problems which `ion-img` is solving, then it's
* recommended to just use the standard `img` HTML element instead.
* Future goals are to place image requests within web workers, and cache
* images in-memory as datauris. This method has proven to be effective,
* however there are some current limitations with Cordova which we are
* currently working on.
*
*/
@Component({
Expand All @@ -130,12 +102,10 @@ export class Img implements OnDestroy {
/** @internal */
_renderedSrc: string;
/** @internal */
_tmpDataUri: string;
_hasLoaded: boolean;
/** @internal */
_cache: boolean = true;
/** @internal */
_cb: ImgLoadCallback;
/** @internal */
_bounds: any;
/** @internal */
_rect: any;
Expand All @@ -147,6 +117,10 @@ export class Img implements OnDestroy {
_wQ: string = '';
/** @internal */
_hQ: string = '';
/** @internal */
_img: HTMLImageElement;
/** @internal */
_unreg: Function;

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


constructor(
private _ldr: ImgLoader,
private _elementRef: ElementRef,
private _renderer: Renderer,
private _platform: Platform,
Expand Down Expand Up @@ -191,11 +164,11 @@ export class Img implements OnDestroy {

if (newSrc.indexOf('data:') === 0) {
// they're using an actual datauri already
this._tmpDataUri = newSrc;
this._hasLoaded = true;

} else {
// reset any existing datauri we might be holding onto
this._tmpDataUri = null;
this._hasLoaded = false;
}

// run update to kick off requests or render if everything is good
Expand All @@ -210,7 +183,7 @@ export class Img implements OnDestroy {
if (this._requestingSrc) {
// abort any active requests
console.debug(`abortRequest ${this._requestingSrc} ${Date.now()}`);
this._ldr.abort(this._requestingSrc);
this._srcAttr('');
this._requestingSrc = null;
}
if (this._renderedSrc) {
Expand All @@ -228,61 +201,34 @@ export class Img implements OnDestroy {
// only attempt an update if there is an active src
// and the content containing the image considers it updatable
if (this._src && this._content.isImgsUpdatable()) {
if (this.canRequest && (this._src !== this._renderedSrc && this._src !== this._requestingSrc) && !this._tmpDataUri) {
if (this.canRequest && (this._src !== this._renderedSrc && this._src !== this._requestingSrc) && !this._hasLoaded) {
// only begin the request if we "can" request
// begin the image request if the src is different from the rendered src
// and if we don't already has a tmpDataUri
console.debug(`request ${this._src} ${Date.now()}`);
this._requestingSrc = this._src;

this._cb = (status, msg, datauri) => {
this._loadResponse(status, msg, datauri);
this._cb = null;
};

// post the message to the web worker
this._ldr.load(this._src, this._cache, this._cb);
this._isLoaded(false);
this._srcAttr(this._src);

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

if (this.canRender && this._tmpDataUri && this._src !== this._renderedSrc) {
if (this.canRender && this._hasLoaded && this._src !== this._renderedSrc) {
// we can render and we have a datauri to render
this._renderedSrc = this._src;
this._setDims();
this._dom.write(() => {
if (this._tmpDataUri) {
if (this._hasLoaded) {
console.debug(`render ${this._src} ${Date.now()}`);
this._isLoaded(true);
this._srcAttr(this._tmpDataUri);
this._tmpDataUri = null;
}
});
}
}
}

private _loadResponse(status: number, msg: string, datauri: string) {
this._requestingSrc = null;

if (status === 200) {
// success :)
this._tmpDataUri = datauri;
this.update();

} else {
// error :(
if (status) {
console.error(`img, status: ${status} ${msg}`);
}
this._renderedSrc = this._tmpDataUri = null;
this._dom.write(() => {
this._isLoaded(false);
});
}
}

/**
* @internal
*/
Expand All @@ -297,11 +243,10 @@ export class Img implements OnDestroy {
* @internal
*/
_srcAttr(srcAttr: string) {
const imgEle = this._elementRef.nativeElement.firstChild;
const renderer = this._renderer;

renderer.setElementAttribute(imgEle, 'src', srcAttr);
renderer.setElementAttribute(imgEle, 'alt', this.alt);
renderer.setElementAttribute(this._img, 'src', srcAttr);
renderer.setElementAttribute(this._img, 'alt', this.alt);
}

/**
Expand Down Expand Up @@ -409,11 +354,25 @@ export class Img implements OnDestroy {
*/
@Input() alt: string = '';

/**
* @private
*/
ngAfterContentInit() {
this._img = this._elementRef.nativeElement.firstChild;

this._unreg && this._unreg();
const opts = eventOptions(false, true);
this._unreg = listenEvent(this._img, 'load', false, opts, () => {
this._hasLoaded = true;
this.update();
});
}

/**
* @private
*/
ngOnDestroy() {
this._cb = null;
this._unreg && this._unreg();
this._content && this._content.removeImg(this);
}

Expand Down

0 comments on commit 4f61ea5

Please sign in to comment.