Skip to content

Commit ff1daa6

Browse files
committed
perf(virtualScroll): improve UIWebView virtual scroll
Related #6104
1 parent 99fdcc0 commit ff1daa6

File tree

10 files changed

+156
-120
lines changed

10 files changed

+156
-120
lines changed

src/components/img/img.scss

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,18 @@
55

66
ion-img {
77
position: relative;
8-
display: block;
8+
display: flex;
9+
overflow: hidden;
10+
11+
align-items: center;
12+
justify-content: center;
913
}
1014

1115
ion-img img {
12-
display: block;
16+
flex-shrink: 0;
17+
18+
min-width: 100%;
19+
min-height: 100%;
1320
}
1421

1522
ion-img .img-placeholder {

src/components/img/img.ts

Lines changed: 52 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import {Component, Input, ElementRef, ChangeDetectionStrategy, ViewEncapsulation, NgZone} from '@angular/core';
1+
import {Component, Input, HostBinding, ElementRef, ChangeDetectionStrategy, ViewEncapsulation, NgZone} from '@angular/core';
22

33
import {nativeRaf} from '../../util/dom';
44
import {isPresent} from '../../util/util';
@@ -19,6 +19,7 @@ export class Img {
1919
private _w: string;
2020
private _h: string;
2121
private _enabled: boolean = true;
22+
private _init: boolean;
2223

2324
constructor(private _elementRef: ElementRef, private _platform: Platform, private _zone: NgZone) {}
2425

@@ -30,11 +31,18 @@ export class Img {
3031
this._src = isPresent(val) ? val : '';
3132
this._normalizeSrc = tmpImg.src;
3233

34+
if (this._init) {
35+
this._update();
36+
}
37+
}
38+
39+
ngOnInit() {
40+
this._init = true;
3341
this._update();
3442
}
3543

3644
private _update() {
37-
if (this._enabled && this._src !== '' && this.isVisible()) {
45+
if (this._enabled && this._src !== '') {
3846
// actively update the image
3947

4048
for (var i = this._imgs.length - 1; i >= 0; i--) {
@@ -56,8 +64,15 @@ export class Img {
5664
if (!this._imgs.length) {
5765
this._zone.runOutsideAngular(() => {
5866
let img = new Image();
59-
img.style.width = this._w;
60-
img.style.height = this._h;
67+
img.style.width = this._width;
68+
img.style.height = this._height;
69+
70+
if (isPresent(this.alt)) {
71+
img.alt = this.alt;
72+
}
73+
if (isPresent(this.title)) {
74+
img.title = this.title;
75+
}
6176

6277
img.addEventListener('load', () => {
6378
if (img.src === this._normalizeSrc) {
@@ -92,19 +107,45 @@ export class Img {
92107
this._update();
93108
}
94109

95-
isVisible() {
96-
let bounds = this._elementRef.nativeElement.getBoundingClientRect();
97-
return bounds.bottom > 0 && bounds.top < this._platform.height();
98-
}
99-
100110
@Input()
101111
set width(val: string | number) {
102-
this._w = (typeof val === 'number') ? val + 'px' : val;
112+
this._w = getUnitValue(val);
103113
}
104114

105115
@Input()
106116
set height(val: string | number) {
107-
this._h = (typeof val === 'number') ? val + 'px' : val;
117+
this._h = getUnitValue(val);
118+
}
119+
120+
@Input() alt: string;
121+
122+
@Input() title: string;
123+
124+
@HostBinding('style.width')
125+
get _width(): string {
126+
return isPresent(this._w) ? this._w : '';
127+
}
128+
129+
@HostBinding('style.height')
130+
get _height(): string {
131+
return isPresent(this._h) ? this._h : '';
108132
}
109133

110134
}
135+
136+
function getUnitValue(val: any): string {
137+
if (isPresent(val)) {
138+
if (typeof val === 'string') {
139+
if (val.indexOf('%') > -1 || val.indexOf('px') > -1) {
140+
return val;
141+
}
142+
if (val.length) {
143+
return val + 'px';
144+
}
145+
146+
} else if (typeof val === 'number') {
147+
return val + 'px';
148+
}
149+
}
150+
return '';
151+
}

src/components/virtual-scroll/test/basic/index.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,4 +53,6 @@ class E2EApp {
5353
root = E2EPage;
5454
}
5555

56-
ionicBootstrap(E2EApp);
56+
ionicBootstrap(E2EApp, null, {
57+
prodMode: true
58+
});

src/components/virtual-scroll/test/cards/index.ts

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,15 @@ import {ionicBootstrap} from '../../../../../src';
77
encapsulation: ViewEncapsulation.None
88
})
99
class E2EPage {
10-
items = [];
10+
items: any[] = [];
1111

1212
constructor() {
13-
for (var i = 0; i < 500; i++) {
13+
for (var i = 0; i < 1000; i++) {
1414
this.items.push({
15-
imgSrc: `../../img/img/${images[rotateImg]}.jpg?${Math.random()}`,
16-
imgHeight: Math.floor((Math.random() * 50) + 150),
1715
name: i + ' - ' + images[rotateImg],
16+
imgSrc: getImgSrc(),
17+
avatarSrc: getImgSrc(),
18+
imgHeight: Math.floor((Math.random() * 50) + 150),
1819
content: lorem.substring(0, (Math.random() * (lorem.length - 100)) + 100)
1920
});
2021

@@ -33,7 +34,9 @@ class E2EApp {
3334
root = E2EPage;
3435
}
3536

36-
ionicBootstrap(E2EApp);
37+
ionicBootstrap(E2EApp, null, {
38+
prodMode: true
39+
});
3740

3841
const lorem = 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.';
3942

@@ -50,4 +53,11 @@ const images = [
5053
'mirth-mobile',
5154
];
5255

56+
function getImgSrc() {
57+
let src = `../../img/img/${images[rotateImg]}.jpg?${Math.round(Math.random() * 10000000)}`;
58+
rotateImg++;
59+
if (rotateImg === images.length) rotateImg = 0;
60+
return src;
61+
}
62+
5363
let rotateImg = 0;

src/components/virtual-scroll/test/cards/main.html

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,12 @@
77
<ion-card *virtualItem="let item">
88

99
<div>
10-
<ion-img [src]="item.imgSrc" [height]="item.imgHeight"></ion-img>
10+
<ion-img [src]="item.imgSrc" [height]="item.imgHeight" [alt]="item.name"></ion-img>
1111
</div>
1212

1313
<ion-item>
1414
<ion-avatar item-left>
15-
<ion-img [src]="item.imgSrc"></ion-img>
15+
<ion-img [src]="item.avatarSrc" height="40" width="40"></ion-img>
1616
</ion-avatar>
1717
<h2>{{ item.name }}</h2>
1818
</ion-item>

src/components/virtual-scroll/test/image-gallery/index.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,9 @@ class E2EApp {
8080
root = E2EPage;
8181
}
8282

83-
ionicBootstrap(E2EApp);
83+
ionicBootstrap(E2EApp, null, {
84+
prodMode: true
85+
});
8486

8587
var monthNames = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'];
8688

src/components/virtual-scroll/test/variable-size/index.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import {ionicBootstrap} from '../../../../../src';
66
templateUrl: 'main.html'
77
})
88
class E2EPage {
9-
items = [];
9+
items: any[] = [];
1010

1111
constructor() {
1212

@@ -21,7 +21,7 @@ class E2EPage {
2121
}
2222
}
2323

24-
headerFn(record, recordIndex) {
24+
headerFn(record: any, recordIndex: number) {
2525
if (recordIndex > 0 && recordIndex % 100 === 0) {
2626
return recordIndex;
2727
}
@@ -38,4 +38,6 @@ class E2EApp {
3838
root = E2EPage;
3939
}
4040

41-
ionicBootstrap(E2EApp);
41+
ionicBootstrap(E2EApp, null, {
42+
prodMode: true
43+
});

src/components/virtual-scroll/test/virtual-scroll.spec.ts

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -75,11 +75,11 @@ describe('VirtualScroll', () => {
7575
data.hdrWidth = data.viewWidth; // 100%, 1 per row
7676
data.ftrWidth = data.viewWidth; // 100%, 1 per row
7777

78-
headerFn = function(record) {
78+
headerFn = function(record: any) {
7979
return (record === 0) ? 'Header' : null;
8080
};
8181

82-
footerFn = function(record) {
82+
footerFn = function(record: any) {
8383
return (record === 4) ? 'Footer' : null;
8484
};
8585

@@ -159,7 +159,7 @@ describe('VirtualScroll', () => {
159159
data.itmWidth = 90; // 2 per row
160160
data.hdrWidth = data.viewWidth; // 100%, 1 per row
161161

162-
headerFn = function(record) {
162+
headerFn = function(record: any) {
163163
return (record === 0) ? 'Header' : null;
164164
};
165165

@@ -267,10 +267,10 @@ describe('VirtualScroll', () => {
267267
let endCellIndex = 4;
268268

269269
populateNodeData(startCellIndex, endCellIndex, data.viewWidth, true,
270-
cells, records, nodes, viewContainer,
271-
itmTmp, hdrTmp, ftrTmp, true);
270+
cells, records, nodes, viewContainer,
271+
itmTmp, hdrTmp, ftrTmp, true);
272272

273-
expect(nodes.length).toBe(3);
273+
expect(nodes.length).toBe(6);
274274

275275
expect(nodes[0].cell).toBe(2);
276276
expect(nodes[1].cell).toBe(3);
@@ -522,9 +522,9 @@ describe('VirtualScroll', () => {
522522
let headerFn: Function;
523523
let footerFn: Function;
524524
let data: VirtualData;
525-
let itmTmp = null;
526-
let hdrTmp = null;
527-
let ftrTmp = null;
525+
let itmTmp: any = {};
526+
let hdrTmp: any = {};
527+
let ftrTmp: any = {};
528528
let viewContainer: any = {
529529
createEmbeddedView: function() {
530530
return getView();

0 commit comments

Comments
 (0)