Skip to content

Commit 8adc8a5

Browse files
authored
Merge pull request #11 from maxisam/feat/continue-loading
feat: continue loading
2 parents 9a930eb + 3887e51 commit 8adc8a5

File tree

9 files changed

+2461
-1710
lines changed

9 files changed

+2461
-1710
lines changed

README.md

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ Support Angular >=6.0.0
2525

2626
- Load image directly for spider (SEO friendly) or non-supported browsers
2727

28+
- Aggressive Loading. Able to continue to load even if images haven't got intersected when the concurrently loading count is lower than a certain value. (_after 4.0.0_)
29+
2830
## Install
2931

3032
```bat
@@ -137,7 +139,7 @@ For `ngx-image-placeholder` component, it takes
137139

138140
- placeholderImageSrc
139141

140-
(after 3.0.0, you can set imageRatio and placeholderImageSrc directly on `ngxProgressiveImage` and spare ngx-image-placeholder layer)
142+
(_after 3.0.0_, you can set imageRatio and placeholderImageSrc directly on `ngxProgressiveImage` and spare ngx-image-placeholder layer)
141143

142144
For `ngx-progressive-image-loader` component, it takes
143145

@@ -147,6 +149,10 @@ For `ngx-progressive-image-loader` component, it takes
147149

148150
- filter
149151

152+
- isAggressiveLoading: boolean; default to `true`; Set to `true` to enable **Aggressive Loading** feature. (_after 4.0.0_)
153+
154+
- concurrentLoading: number; default t0 4; Decided at least how many concurrent loading when **Aggressive Loading** is enabled
155+
150156
For `ngxProgressiveImage` directive, (only for image or source elements)
151157

152158
- imageRatio

angular.json

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
"version": 1,
44
"newProjectRoot": "projects",
55
"projects": {
6-
"package-host": {
6+
"library-host": {
77
"root": "",
88
"sourceRoot": "src",
99
"projectType": "application",
@@ -13,7 +13,7 @@
1313
"build": {
1414
"builder": "@angular-devkit/build-angular:browser",
1515
"options": {
16-
"outputPath": "dist/package-host",
16+
"outputPath": "dist/library-host",
1717
"index": "src/index.html",
1818
"main": "src/main.ts",
1919
"polyfills": "src/polyfills.ts",
@@ -45,19 +45,19 @@
4545
"serve": {
4646
"builder": "@angular-devkit/build-angular:dev-server",
4747
"options": {
48-
"browserTarget": "package-host:build",
48+
"browserTarget": "library-host:build",
4949
"port": 5200
5050
},
5151
"configurations": {
5252
"production": {
53-
"browserTarget": "package-host:build:production"
53+
"browserTarget": "library-host:build:production"
5454
}
5555
}
5656
},
5757
"extract-i18n": {
5858
"builder": "@angular-devkit/build-angular:extract-i18n",
5959
"options": {
60-
"browserTarget": "package-host:build"
60+
"browserTarget": "library-host:build"
6161
}
6262
},
6363
"test": {
@@ -81,19 +81,19 @@
8181
}
8282
}
8383
},
84-
"package-host-e2e": {
84+
"library-host-e2e": {
8585
"root": "e2e/",
8686
"projectType": "application",
8787
"architect": {
8888
"e2e": {
8989
"builder": "@angular-devkit/build-angular:protractor",
9090
"options": {
9191
"protractorConfig": "e2e/protractor.conf.js",
92-
"devServerTarget": "package-host:serve"
92+
"devServerTarget": "library-host:serve"
9393
},
9494
"configurations": {
9595
"production": {
96-
"devServerTarget": "package-host:serve:production"
96+
"devServerTarget": "library-host:serve:production"
9797
}
9898
}
9999
},
@@ -140,7 +140,10 @@
140140
}
141141
}
142142
},
143-
"defaultProject": "package-host",
143+
"defaultProject": "library-host",
144+
"cli": {
145+
"packageManager": "yarn"
146+
},
144147
"schematics": {
145148
"@schematics/angular:component": {
146149
"prefix": "app",

package.json

Lines changed: 21 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
11
{
2-
"name": "package-host",
2+
"name": "ngx-progressive-image-loader",
33
"version": "0.0.0",
44
"scripts": {
55
"ng": "ng",
6-
"prettier": "prettier --write \"**/*.{js,json,css,md,ts,html,component.html}\"",
6+
"prettier": "prettier --write \"**/*.{json,md,ts,html,component.html}\"",
77
"start": "ng serve",
88
"build": "npm run build:lib",
99
"build:lib": "ng build ngx-progressive-image-loader && npm run copy-scss && npm run copy-readme",
1010
"build:lib:w": "ng build ngx-progressive-image-loader --watch",
11-
"publish:lib": "npm publish ./dist/ngx-progressive-image-loader",
12-
"publish:lib:next": "npm publish ./dist/ngx-progressive-image-loader --tag next",
11+
"publish:lib": "yarn publish ./dist/ngx-progressive-image-loader",
12+
"publish:lib:next": "yarn publish ./dist/ngx-progressive-image-loader --tag next",
1313
"copy-scss": "cpx \"./projects/ngx-progressive-image-loader/src/lib/*.scss\" \"./dist/ngx-progressive-image-loader/\" --verbose",
1414
"copy-readme": "cpx \"./README.md\" \"./dist/ngx-progressive-image-loader\"",
1515
"test": "ng test ngx-progressive-image-loader",
@@ -27,22 +27,25 @@
2727
"@angular/platform-browser-dynamic": "~7.2.0",
2828
"@angular/router": "~7.2.0",
2929
"core-js": "^2.5.4",
30+
"ngx-logger": "^3.3.13",
31+
"ngx-progressive-image-loader": "^4.0.0",
32+
"ngx-window-token": "^2.0.1",
33+
"node-sass": "^4.12.0",
3034
"rxjs": "~6.3.3",
3135
"tslib": "^1.9.0",
32-
"zone.js": "~0.8.26",
33-
"ngx-progressive-image-loader": "^4.0.0-beta.12",
34-
"ngx-window-token": "^2.0.1"
36+
"zone.js": "~0.8.26"
3537
},
3638
"devDependencies": {
3739
"@angular-devkit/build-angular": "~0.13.0",
3840
"@angular-devkit/build-ng-packagr": "~0.13.0",
3941
"@angular/cli": "~7.3.4",
4042
"@angular/compiler-cli": "~7.2.0",
4143
"@angular/language-service": "~7.2.0",
42-
"@types/node": "~8.9.4",
4344
"@types/jasmine": "~2.8.8",
4445
"@types/jasminewd2": "~2.0.3",
46+
"@types/node": "~8.9.4",
4547
"codelyzer": "~4.5.0",
48+
"cpx": "^1.5.0",
4649
"husky": "1.3.1",
4750
"jasmine-core": "~2.99.1",
4851
"jasmine-spec-reporter": "~4.2.1",
@@ -60,18 +63,17 @@
6063
"tslib": "^1.9.0",
6164
"tslint": "~5.11.0",
6265
"tslint-config-prettier": "1.18.0",
63-
"typescript": "~3.2.2",
64-
"cpx": "^1.5.0"
66+
"typescript": "~3.2.2"
6567
},
6668
"husky": {
67-
"hooks": {
68-
"pre-commit": "lint-staged"
69-
}
70-
},
71-
"lint-staged": {
72-
"*.{js,json,css,md,ts,html,component.html}": [
73-
"prettier --write",
74-
"git add"
75-
]
69+
"hooks": {
70+
"pre-commit": "lint-staged"
7671
}
72+
},
73+
"lint-staged": {
74+
"*.{js,json,css,md,ts,html,component.html}": [
75+
"prettier --write",
76+
"git add"
77+
]
7778
}
79+
}

projects/ngx-progressive-image-loader/package.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "ngx-progressive-image-loader",
3-
"version": "4.0.0-beta.1",
3+
"version": "4.0.0",
44
"author": {
55
"name": "Sam Lin",
66
"email": "[email protected]"
@@ -25,7 +25,7 @@
2525
"ngx-window-token": ">=2.0.0"
2626
},
2727
"peerDependencies": {
28-
"@angular/common": ">=6.0.0 || >=7.0.0 || >=8.0.0",
29-
"@angular/core": ">=6.0.0 || >=7.0. || >=8.0.00"
28+
"@angular/common": ">=6.0.0",
29+
"@angular/core": ">=6.0.0"
3030
}
3131
}

projects/ngx-progressive-image-loader/src/lib/config.ts

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@ import { InjectionToken } from '@angular/core';
33
export interface IImageLoaderOptions extends IntersectionObserverInit {
44
placeholderImageSrc?: string;
55
imageRatio: number;
6-
filter: string;
6+
filter?: string;
7+
isAggressiveLoading?: boolean;
8+
concurrentLoading?: number;
79
}
810
export const IMAGE_LOADER_CONFIG_TOKEN = new InjectionToken<IImageLoaderOptions>(
911
'Image loader Config'
@@ -12,7 +14,9 @@ export const IMAGE_LOADER_CONFIG_TOKEN = new InjectionToken<IImageLoaderOptions>
1214
export const DEFAULT_IMAGE_LOADER_OPTIONS = <IImageLoaderOptions>{
1315
// root?: Element | null;
1416
rootMargin: '10px',
15-
threshold: 0.1,
17+
threshold: [0.1, 0.5, 1],
1618
imageRatio: 16 / 9,
17-
placeholderImageSrc: ''
19+
placeholderImageSrc: '',
20+
isAggressiveLoading: true,
21+
concurrentLoading: 4
1822
};

projects/ngx-progressive-image-loader/src/lib/image-placeholder/image-placeholder.component.ts

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,7 @@ import { ProgressiveImageLoaderComponent } from '../progressive-image-loader/pro
1212
changeDetection: ChangeDetectionStrategy.OnPush
1313
})
1414
export class ImagePlaceholderComponent implements OnInit {
15-
@HostBinding('class')
16-
class = 'ngx-image-placeholder';
15+
@HostBinding('class') class = 'ngx-image-placeholder';
1716
@HostBinding('style')
1817
get placeHolder(): SafeStyle {
1918
return this.sanitizer.bypassSecurityTrustStyle(
@@ -22,11 +21,9 @@ export class ImagePlaceholderComponent implements OnInit {
2221
}
2322

2423
// to create a placeholder before finish loading the real image to avoid reflow
25-
@Input()
26-
imageRatio: number;
24+
@Input() imageRatio: number;
2725
// a loading image showing before the real image is loaded
28-
@Input()
29-
placeholderImageSrc: string;
26+
@Input() placeholderImageSrc: string;
3027

3128
get imageFilter(): SafeStyle {
3229
return this.sanitizer.bypassSecurityTrustStyle(`${this._ProgressiveImageLoader.filter}`);
Lines changed: 74 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
import { isPlatformBrowser } from '@angular/common';
22
import {
3+
ChangeDetectionStrategy,
34
Component,
4-
ElementRef,
55
Inject,
66
Input,
77
OnDestroy,
88
OnInit,
9+
Optional,
910
PLATFORM_ID,
1011
Renderer2
1112
} from '@angular/core';
@@ -16,32 +17,52 @@ import { isSpider, isSupportIntersectionObserver, loadImage } from '../util';
1617

1718
@Component({
1819
selector: 'ngx-progressive-image-loader',
20+
exportAs: 'ngxProgressiveImageLoader',
1921
template: `
2022
<ng-content></ng-content>
21-
`
23+
`,
24+
changeDetection: ChangeDetectionStrategy.OnPush
2225
})
2326
export class ProgressiveImageLoaderComponent implements OnInit, OnDestroy {
2427
// define the placeholder height for all images inside this components
25-
@Input()
26-
imageRatio: number;
27-
28-
@Input()
29-
filter: string;
28+
@Input() imageRatio: number;
29+
// how many image at least should load at the same time
30+
@Input() concurrentLoading: number;
31+
// is going to load images that are being observed but haven't been intersected yet when loading counter < concurrentLoading
32+
@Input() isAggressiveLoading: boolean;
33+
@Input() filter: string;
3034
// the src of loading image
31-
@Input()
32-
placeholderImageSrc: string;
35+
@Input() placeholderImageSrc: string;
36+
3337
intersectionObserver: IntersectionObserver;
38+
// to store observed images
39+
targetMap = new Map();
40+
// to maintain the sequence of observed images
41+
targetQueue = <string[]>[];
42+
// counter of current loading images
43+
loading = 0;
44+
public get isObservable(): boolean {
45+
return !!this.intersectionObserver;
46+
}
3447

3548
constructor(
36-
element: ElementRef,
3749
public _Renderer: Renderer2,
3850
public _ConfigurationService: ConfigurationService,
3951
@Inject(PLATFORM_ID) private platformId: any,
40-
@Inject(WINDOW) private window: any
52+
@Optional()
53+
@Inject(WINDOW)
54+
private window: any
4155
) {}
4256

4357
ngOnInit() {
58+
if (this.isAggressiveLoading === undefined) {
59+
this.isAggressiveLoading = this._ConfigurationService.config.isAggressiveLoading;
60+
}
61+
if (this.concurrentLoading === undefined) {
62+
this.concurrentLoading = this._ConfigurationService.config.concurrentLoading;
63+
}
4464
if (
65+
this.window &&
4566
isSupportIntersectionObserver(this.window) &&
4667
!isSpider(this.window) &&
4768
isPlatformBrowser(this.platformId)
@@ -63,21 +84,54 @@ export class ProgressiveImageLoaderComponent implements OnInit, OnDestroy {
6384
}
6485
}
6586

66-
onIntersectionChanged(entries: IntersectionObserverEntry[], observer: IntersectionObserver) {
67-
entries.forEach(
68-
entry =>
69-
entry.isIntersecting &&
70-
this.onImageAppearsInViewport(entry.target as HTMLImageElement, observer)
71-
);
87+
observe(target: HTMLImageElement) {
88+
// so intersection observer can always detect it correctly, otherwise image elements with 0 in height sometime don't load correctly
89+
target.style.minHeight = '1rem';
90+
this.intersectionObserver.observe(target);
91+
this.targetMap.set(target.dataset.src, target);
92+
this.targetQueue.push(target.dataset.src);
7293
}
7394

74-
onImageAppearsInViewport(image: HTMLImageElement, observer: IntersectionObserver) {
95+
unobserve(target: HTMLImageElement) {
96+
target.style.minHeight = 'initial';
97+
this.targetMap.delete(target.dataset.src);
98+
this.intersectionObserver.unobserve(target);
99+
}
100+
// called after an image loaded
101+
imageLoaded() {
102+
this.loading--;
103+
while (
104+
this.isAggressiveLoading &&
105+
this.targetQueue &&
106+
this.targetQueue.length &&
107+
this.loading <= this.concurrentLoading
108+
) {
109+
const next = this.targetQueue.shift();
110+
this.targetMap.has(next) && this.loadImage(this.targetMap.get(next));
111+
}
112+
}
113+
onIntersectionChanged(entries: IntersectionObserverEntry[], observer: IntersectionObserver) {
114+
entries.forEach(entry => {
115+
if (entry.isIntersecting) {
116+
this.loadImage(entry.target as HTMLImageElement);
117+
}
118+
});
119+
}
120+
// start loading an image
121+
loadImage(image: HTMLImageElement) {
75122
// Stop observing the current target
76-
observer.unobserve(image);
123+
this.unobserve(image);
124+
this.loading++;
77125
loadImage(this._Renderer, image);
78126
}
79127

128+
reset() {
129+
this.targetQueue = [];
130+
this.targetMap = new Map();
131+
this.isObservable && this.intersectionObserver.disconnect();
132+
}
80133
ngOnDestroy(): void {
81-
this.intersectionObserver && this.intersectionObserver.disconnect();
134+
this.isObservable && this.intersectionObserver.disconnect();
135+
this.intersectionObserver = this.targetQueue = this.targetMap = undefined;
82136
}
83137
}

0 commit comments

Comments
 (0)