forked from tjoskar/ng-lazyload-image
-
Notifications
You must be signed in to change notification settings - Fork 0
/
lazyload-image.ts
143 lines (129 loc) · 5.22 KB
/
lazyload-image.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
import 'rxjs/add/operator/filter';
import 'rxjs/add/operator/do';
import 'rxjs/add/operator/take';
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/mergeMap';
import 'rxjs/add/operator/catch';
import 'rxjs/add/observable/of';
import { Observable } from 'rxjs/Observable';
import { getScrollListener } from './scroll-listener';
import { Rect } from './rect';
export function isVisible(element: HTMLElement, threshold = 0, _window: Window, scrollContainer?: HTMLElement) {
const elementBounds = Rect.fromElement(element);
const windowBounds = Rect.fromWindow(_window);
elementBounds.inflate(threshold);
if (scrollContainer) {
const scrollContainerBounds = Rect.fromElement(scrollContainer);
if (scrollContainerBounds.intersectsWith(windowBounds)) {
const intersection = scrollContainerBounds.getIntersectionWith(windowBounds);
return elementBounds.intersectsWith(intersection);
} else {
return false;
}
} else {
return elementBounds.intersectsWith(windowBounds);
}
}
export function isChildOfPicture(element: HTMLImageElement | HTMLDivElement): boolean {
return Boolean(element.parentElement && element.parentElement.nodeName.toLowerCase() === 'picture');
}
export function isImageElement(element: HTMLImageElement | HTMLDivElement): element is HTMLImageElement {
return element.nodeName.toLowerCase() === 'img';
}
function loadImage(element: HTMLImageElement | HTMLDivElement, imagePath: string, useSrcset: boolean): Observable<string> {
let img: HTMLImageElement;
if (isImageElement(element) && isChildOfPicture(element)) {
const parentClone = element.parentNode.cloneNode(true) as HTMLPictureElement;
img = parentClone.getElementsByTagName('img')[0];
setSourcesToLazy(img);
setImage(img, imagePath, useSrcset);
} else {
img = new Image();
if (isImageElement(element) && element.sizes) {
img.sizes = element.sizes;
}
if (useSrcset) {
img.srcset = imagePath;
} else {
img.src = imagePath;
}
}
return Observable
.create(observer => {
img.onload = () => {
observer.next(imagePath);
observer.complete();
};
img.onerror = err => {
observer.error(null);
};
});
}
function setImage(element: HTMLImageElement | HTMLDivElement, imagePath: string, useSrcset: boolean) {
if (isImageElement(element)) {
if (useSrcset) {
element.srcset = imagePath;
} else {
element.src = imagePath;
}
} else {
element.style.backgroundImage = `url('${imagePath}')`;
}
return element;
}
function setSources(attrName: string) {
return (image: HTMLImageElement) => {
const sources = image.parentElement.getElementsByTagName('source');
for (let i = 0; i < sources.length; i++) {
const attrValue = sources[i].getAttribute(attrName);
if (attrValue) {
sources[i].srcset = attrValue;
}
}
}
}
const setSourcesToDefault = setSources('defaultImage');
const setSourcesToLazy = setSources('lazyLoad');
const setSourcesToError = setSources('errorImage');
function setImageAndSources(setSourcesFn: (image: HTMLImageElement) => void) {
return (element: HTMLImageElement | HTMLDivElement, imagePath: string, useSrcset: boolean) => {
if (isImageElement(element) && isChildOfPicture(element)) {
setSourcesFn(element);
}
if (imagePath) {
setImage(element, imagePath, useSrcset);
}
}
}
const setImageAndSourcesToDefault = setImageAndSources(setSourcesToDefault);
const setImageAndSourcesToLazy = setImageAndSources(setSourcesToLazy);
const setImageAndSourcesToError = setImageAndSources(setSourcesToError);
function setLoadedStyle(element: HTMLImageElement | HTMLDivElement) {
const styles = element.className
.split(' ')
.filter(s => !!s)
.filter(s => s !== 'ng-lazyloading');
styles.push('ng-lazyloaded');
element.className = styles.join(' ');
return element;
}
export function lazyLoadImage(element: HTMLImageElement | HTMLDivElement, imagePath: string, defaultImagePath: string, errorImgPath: string, offset: number, useSrcset: boolean = false, scrollContainer?: HTMLElement) {
setImageAndSourcesToDefault(element, defaultImagePath, useSrcset);
if (element.className && element.className.includes('ng-lazyloaded')) {
element.className = element.className.replace('ng-lazyloaded', '');
}
return (scrollObservable: Observable<Event>) => {
return scrollObservable
.filter(() => isVisible(element, offset, window, scrollContainer))
.take(1)
.mergeMap(() => loadImage(element, imagePath, useSrcset))
.do(() => setImageAndSourcesToLazy(element, imagePath, useSrcset))
.map(() => true)
.catch(() => {
setImageAndSourcesToError(element, errorImgPath, useSrcset);
element.className += ' ng-failed-lazyloaded';
return Observable.of(false);
})
.do(() => setLoadedStyle(element));
};
}