forked from mfranzke/loading-attribute-polyfill
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathloading-attribute-polyfill.js
executable file
·257 lines (220 loc) · 8.21 KB
/
loading-attribute-polyfill.js
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
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
/*
* Loading attribute polyfill - https://github.com/mfranzke/loading-attribute-polyfill
* @license Copyright(c) 2019 by Maximilian Franzke
* Credits for the initial kickstarter / script to @Sora2455, and supported by @cbirdsong, @eklingen, @DaPo, @nextgenthemes, @diogoterremoto, @dracos and @Flimm - many thanks for that !
*/
/*
* A minimal and dependency-free vanilla JavaScript loading attribute polyfill.
* Supports standard's functionality and tests for native support upfront.
* Elsewhere the functionality gets emulated with the support of noscript wrapper tags.
* Use an IntersectionObserver polyfill in case of IE11 support necessary.
*/
(function(noscriptClass, rootMargin) {
'use strict';
var config = {
// Start download if the item gets within 256px in the Y axis
rootMargin: rootMargin || '0px 0px 256px 0px',
threshold: 0.01,
lazyImage: 'img[loading="lazy"]',
lazyIframe: 'iframe[loading="lazy"]'
};
// Device/browser capabilities object
var capabilities = {
loading:
'loading' in HTMLImageElement.prototype &&
'loading' in HTMLIFrameElement.prototype,
scrolling: 'onscroll' in window
};
// Nodelist foreach polyfill / source: https://stackoverflow.com/a/46929259
if (
typeof NodeList !== 'undefined' &&
NodeList.prototype &&
!NodeList.prototype.forEach
) {
// Yes, there's really no need for `Object.defineProperty` here
NodeList.prototype.forEach = Array.prototype.forEach;
}
// Define according to browsers support of the IntersectionObserver feature (missing e.g. on IE11 or Safari 11)
var intersectionObserver;
if ('IntersectionObserver' in window) {
intersectionObserver = new IntersectionObserver(onIntersection, config);
}
// On using a browser w/o requestAnimationFrame support (IE9, Opera Mini), just run the passed function
var rAFWrapper;
if ('requestAnimationFrame' in window) {
rAFWrapper = window.requestAnimationFrame;
} else {
rAFWrapper = function(func) {
func();
};
}
var temporaryImage =
'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7';
/**
* Put the source and srcset back where it belongs - now that the elements content is attached to the document, it will load now
* @param {Object} lazyItem Current item to be restored after lazy loading.
*/
function restoreSource(lazyItem) {
var srcsetItems = [];
// Just in case the img is the decendent of a picture element, check for source tags
if (lazyItem.parentNode.tagName.toLowerCase() === 'picture') {
removePlaceholderSource(lazyItem.parentNode);
srcsetItems = Array.prototype.slice.call(
lazyItem.parentNode.querySelectorAll('source')
);
}
srcsetItems.push(lazyItem);
srcsetItems.forEach(function(item) {
if (item.dataset.lazySrcset) {
item.setAttribute('srcset', item.dataset.lazySrcset);
delete item.dataset.lazySrcset;
}
});
lazyItem.setAttribute('src', lazyItem.dataset.lazySrc);
delete lazyItem.dataset.lazySrc;
}
/**
* Remove the source tag preventing the loading of picture assets
* @param {Object} lazyItemPicture Current <picture> item to be restored after lazy loading.
*/
function removePlaceholderSource(lazyItemPicture) {
var placeholderSource = lazyItemPicture.querySelector(
'source[data-lazy-remove]'
);
if (placeholderSource) {
lazyItemPicture.removeChild(placeholderSource); // Preferred .removeChild over .remove here for IE
}
}
/**
* Handle IntersectionObservers callback
* @param {Object} entries Target elements Intersection observed changes
* @param {Object} observer IntersectionObserver instance reference
*/
function onIntersection(entries, observer) {
entries.forEach(function(entry) {
// Mitigation for EDGE lacking support of .isIntersecting until v15, compare to e.g. https://github.com/w3c/IntersectionObserver/issues/211#issuecomment-309144669
if (entry.intersectionRatio === 0) {
return;
}
// If the item is visible now, load it and stop watching it
var lazyItem = entry.target;
observer.unobserve(lazyItem);
restoreSource(lazyItem);
});
}
/**
* Handle printing the page
*/
function onPrinting() {
if (typeof window.matchMedia === 'undefined') {
return;
}
var mediaQueryList = window.matchMedia('print');
mediaQueryList.addListener(function(mql) {
if (mql.matches) {
document
.querySelectorAll(
config.lazyImage +
'[data-lazy-src],' +
config.lazyIframe +
'[data-lazy-src]'
)
.forEach(function(lazyItem) {
restoreSource(lazyItem);
});
}
});
}
/**
* Get and prepare the HTML code depending on feature detection for both image as well as iframe,
* and if not scrolling supported, because it's a Google or Bing Bot
* @param {String} lazyAreaHtml Noscript inner HTML code that src-urls need to get rewritten
*/
function getAndPrepareHTMLCode(noScriptTag) {
// The contents of a <noscript> tag are treated as text to JavaScript
var lazyAreaHtml = noScriptTag.textContent || noScriptTag.innerHTML;
if (
!capabilities.loading &&
capabilities.scrolling &&
typeof document.documentElement.dataset !== 'undefined'
) {
// Check for IntersectionObserver support
if (typeof intersectionObserver === 'undefined') {
// Attach abandonned attribute 'lazyload' to the HTML tags on browsers w/o IntersectionObserver being available
lazyAreaHtml = lazyAreaHtml.replace(
/(?:\r\n|\r|\n|\t| )src=/g,
' lazyload="1" src='
);
} else {
if (noScriptTag.parentNode.tagName.toLowerCase() === 'picture') {
// Temporarily prevent expensive resource loading by inserting a <source> tag pointing to a simple one (data URI)
lazyAreaHtml =
'<source srcset="' +
temporaryImage +
'" data-lazy-remove="true"></source>' +
lazyAreaHtml;
}
// Temporarily replace a expensive resource load with a simple one by storing the actual source and srcset for later and point src to a temporary replacement (data URI)
lazyAreaHtml = lazyAreaHtml
.replace(/(?:\r\n|\r|\n|\t| )srcset=/g, ' data-lazy-srcset=')
.replace(
/(?:\r\n|\r|\n|\t| )src=/g,
' src="' + temporaryImage + '" data-lazy-src='
);
}
}
return lazyAreaHtml;
}
/**
* Retrieve the elements from the 'lazy load' <noscript> tag and prepare them for display
* @param {Object} noScriptTag noscript HTML tag that should get initially transformed
*/
function prepareElement(noScriptTag) {
// Sticking the noscript HTML code in the innerHTML of a new <div> tag to 'load' it after creating that <div>
var lazyArea = document.createElement('div');
lazyArea.innerHTML = getAndPrepareHTMLCode(noScriptTag);
// Move all children out of the element
while (lazyArea.firstChild) {
if (
!capabilities.loading &&
capabilities.scrolling &&
typeof intersectionObserver !== 'undefined' &&
typeof document.documentElement.dataset !== 'undefined' &&
lazyArea.firstChild.tagName &&
(lazyArea.firstChild.tagName.toLowerCase() === 'img' ||
lazyArea.firstChild.tagName.toLowerCase() === 'iframe')
) {
// Observe the item so that loading could start when it gets close to the viewport
intersectionObserver.observe(lazyArea.firstChild);
}
noScriptTag.parentNode.insertBefore(lazyArea.firstChild, noScriptTag);
}
// Remove the empty element - not using .remove() here for IE11 compatibility
noScriptTag.parentNode.removeChild(noScriptTag); // Preferred .removeChild over .remove here for IE
}
/**
* Get all the <noscript> tags on the page and setup the printing
*/
function prepareElements() {
//
var lazyLoadAreas = document.querySelectorAll('noscript.' + noscriptClass);
lazyLoadAreas.forEach(prepareElement);
// Bind for someone printing the page
onPrinting();
}
// If the page has loaded already, run setup - if it hasn't, run as soon as it has.
// Use requestAnimationFrame as this will propably cause repaints
if (/comp|inter/.test(document.readyState)) {
rAFWrapper(prepareElements);
} else if ('addEventListener' in document) {
document.addEventListener('DOMContentLoaded', function() {
rAFWrapper(prepareElements);
});
} else {
document.attachEvent('onreadystatechange', function() {
if (document.readyState === 'complete') {
prepareElements();
}
});
}
}('loading-lazy', '256px 0px'));