perf(img): do not reuse img elements

Safari UIWebView has troubles dropping image requests when the src
changes, and when there are hundreds going in and out this causes
issues. Instead, do not reuse img elements. Closes #6112
This commit is contained in:
Adam Bradley
2016-04-11 12:21:56 -05:00
parent dad2155ecd
commit b744275936
2 changed files with 62 additions and 45 deletions

View File

@ -7,6 +7,7 @@ export * from './components/button/button'
export * from './components/checkbox/checkbox' export * from './components/checkbox/checkbox'
export * from './components/content/content' export * from './components/content/content'
export * from './components/icon/icon' export * from './components/icon/icon'
export * from './components/img/img'
export * from './components/infinite-scroll/infinite-scroll' export * from './components/infinite-scroll/infinite-scroll'
export * from './components/infinite-scroll/infinite-scroll-content' export * from './components/infinite-scroll/infinite-scroll-content'
export * from './components/input/input' export * from './components/input/input'

View File

@ -1,5 +1,6 @@
import {Component, Input, HostBinding, ViewChild, ElementRef, ViewEncapsulation} from 'angular2/core'; import {Component, Input, ElementRef, ChangeDetectionStrategy, ViewEncapsulation, NgZone} from 'angular2/core';
import {raf} from '../../util/dom';
import {isPresent} from '../../util/util'; import {isPresent} from '../../util/util';
import {Platform} from '../../platform/platform'; import {Platform} from '../../platform/platform';
@ -7,48 +8,83 @@ import {Platform} from '../../platform/platform';
@Component({ @Component({
selector: 'ion-img', selector: 'ion-img',
template: template:
'<div *ngIf="_useA" class="img-placeholder" [style.height]="_h" [style.width]="_w"></div>' + '<div class="img-placeholder" [style.height]="_h" [style.width]="_w"></div>',
'<img #imgA *ngIf="_useA" (load)="_onLoad()" [src]="_srcA" [style.height]="_h" [style.width]="_w">' + changeDetection: ChangeDetectionStrategy.OnPush,
'<div *ngIf="!_useA" class="img-placeholder" [style.height]="_h" [style.width]="_w"></div>' +
'<img #imgB *ngIf="!_useA" (load)="_onLoad()" [src]="_srcB" [style.height]="_h" [style.width]="_w">',
encapsulation: ViewEncapsulation.None, encapsulation: ViewEncapsulation.None,
}) })
export class Img { export class Img {
private _src: string = ''; private _src: string = '';
private _srcA: string = ''; private _normalizeSrc: string = '';
private _srcB: string = ''; private _imgs: HTMLImageElement[] = [];
private _useA: boolean = true;
private _w: string; private _w: string;
private _h: string; private _h: string;
private _enabled: boolean = true; private _enabled: boolean = true;
constructor(private _elementRef: ElementRef, private _platform: Platform) {} constructor(private _elementRef: ElementRef, private _platform: Platform, private _zone: NgZone) {}
@ViewChild('imgA') private _imgA: ElementRef;
@ViewChild('imgB') private _imgB: ElementRef;
@Input() @Input()
set src(val: string) { set src(val: string) {
val = (isPresent(val) ? val : ''); let tmpImg = new Image();
tmpImg.src = isPresent(val) ? val : '';
this._src = isPresent(val) ? val : '';
this._normalizeSrc = tmpImg.src;
if (this._src !== val) {
this._src = val;
this._loaded = false;
this._srcA = this._srcB = '';
this._useA = !this._useA;
this._update(); this._update();
} }
}
private _update() { private _update() {
if (this._enabled && this.isVisible()) { if (this._enabled && this._src !== '' && this.isVisible()) {
if (this._useA) { // actively update the image
this._srcA = this._src;
for (var i = this._imgs.length - 1; i >= 0; i--) {
if (this._imgs[i].src === this._normalizeSrc) {
// this is the active image
if (this._imgs[i].complete) {
this._loaded(true);
}
} else { } else {
this._srcB = this._src; // no longer the active image
if (this._imgs[i].parentElement) {
this._imgs[i].parentElement.removeChild(this._imgs[i]);
}
this._imgs.splice(i, 1);
} }
} }
if (!this._imgs.length) {
this._zone.runOutsideAngular(() => {
let img = new Image();
img.style.width = this._w;
img.style.height = this._h;
img.addEventListener('load', () => {
if (img.src === this._normalizeSrc) {
this._elementRef.nativeElement.appendChild(img);
raf(() => {
this._update();
});
}
});
img.src = this._src;
this._imgs.push(img);
this._loaded(false);
});
}
} else {
// do not actively update the image
if (!this._imgs.some(img => img.src === this._normalizeSrc)) {
this._loaded(false);
}
}
}
private _loaded(isLoaded: boolean) {
this._elementRef.nativeElement.classList[isLoaded ? 'add': 'remove']('img-loaded');
} }
enable(shouldEnable: boolean) { enable(shouldEnable: boolean) {
@ -61,26 +97,6 @@ export class Img {
return bounds.bottom > 0 && bounds.top < this._platform.height(); return bounds.bottom > 0 && bounds.top < this._platform.height();
} }
@HostBinding('class.img-loaded')
private _loaded: boolean = false;
private _onLoad() {
this._loaded = this.isLoaded();
}
isLoaded() {
let imgEle: HTMLImageElement;
if (this._useA && this._imgA) {
imgEle = this._imgA.nativeElement;
} else if (this._imgB) {
imgEle = this._imgB.nativeElement;
}
return (imgEle && imgEle.src !== '' && imgEle.complete);
}
@Input() @Input()
set width(val: string | number) { set width(val: string | number) {
this._w = (typeof val === 'number') ? val + 'px' : val; this._w = (typeof val === 'number') ? val + 'px' : val;