loading-attribute-polyfill
Advanced tools
Comparing version 2.0.0-beta.0 to 2.0.0-beta.1
@@ -36,3 +36,2 @@ { | ||
".huskyrc", | ||
"config.codekit3", | ||
"commitlint.config.js", | ||
@@ -39,0 +38,0 @@ "wdio.conf-crossbrowsertesting.js" |
@@ -9,2 +9,8 @@ # Changelog | ||
## [2.0.0-beta.1] - 2021-03-04 | ||
### Changed | ||
- BREAKING CHANGE: Even also generate JS modules from now on supported by [microbundle](https://npmjs.com/microbundle), added the relevant property entries within the `package.json` directing to those files, that are now stored within the `dist/` folder (see [migration guide](MIGRATION.md) [#19](https://github.com/mfranzke/loading-attribute-polyfill/issues/19), [#87](https://github.com/mfranzke/loading-attribute-polyfill/pull/87), [#39](https://github.com/mfranzke/loading-attribute-polyfill/pull/39)) | ||
## [2.0.0-beta.0] - 2021-03-04 | ||
@@ -11,0 +17,0 @@ |
@@ -13,252 +13,254 @@ /* | ||
(function (noscriptClass, rootMargin) { | ||
'use strict'; | ||
'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"]', | ||
}; | ||
var config = { | ||
// Start download if the item gets within 256px in the Y axis | ||
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, | ||
}; | ||
// Device/browser capabilities object | ||
var capabilities = { | ||
loading: | ||
'loading' in HTMLImageElement.prototype && | ||
'loading' in HTMLIFrameElement.prototype, | ||
scrolling: 'onscroll' in window, | ||
}; | ||
// Nodelist foreach polyfill / source: https://developer.mozilla.org/en-US/docs/Web/API/NodeList/forEach#polyfill | ||
if (window.NodeList && !NodeList.prototype.forEach) { | ||
NodeList.prototype.forEach = Array.prototype.forEach; | ||
} | ||
// Nodelist foreach polyfill / source: https://developer.mozilla.org/en-US/docs/Web/API/NodeList/forEach#polyfill | ||
if (window.NodeList && !NodeList.prototype.forEach) { | ||
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; | ||
// 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); | ||
} | ||
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 = | ||
'requestAnimationFrame' in window | ||
? window.requestAnimationFrame | ||
: function (func) { | ||
func(); | ||
}; | ||
// On using a browser w/o requestAnimationFrame support (IE9, Opera Mini), just run the passed function | ||
var rAFWrapper = | ||
'requestAnimationFrame' in window | ||
? window.requestAnimationFrame | ||
: function (func) { | ||
func(); | ||
}; | ||
/** | ||
* 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 = []; | ||
/** | ||
* 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); | ||
// 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 = Array.prototype.slice.call( | ||
lazyItem.parentNode.querySelectorAll('source') | ||
); | ||
} | ||
srcsetItems.push(lazyItem); | ||
// Not using .dataset within those upfollowing lines of code for polyfill independent compatibility down to IE9 | ||
srcsetItems.forEach(function (item) { | ||
if (item.hasAttribute('data-lazy-srcset')) { | ||
item.setAttribute('srcset', item.getAttribute('data-lazy-srcset')); | ||
item.removeAttribute('data-lazy-srcset'); // Not using delete .dataset here for compatibility down to IE9 | ||
} | ||
}); | ||
srcsetItems.push(lazyItem); | ||
lazyItem.setAttribute('src', lazyItem.getAttribute('data-lazy-src')); | ||
lazyItem.removeAttribute('data-lazy-src'); // Not using delete .dataset here for compatibility down to IE9 | ||
} | ||
// Not using .dataset within those upfollowing lines of code for polyfill independent compatibility down to IE9 | ||
srcsetItems.forEach(function (item) { | ||
if (item.hasAttribute('data-lazy-srcset')) { | ||
item.setAttribute('srcset', item.getAttribute('data-lazy-srcset')); | ||
item.removeAttribute('data-lazy-srcset'); // Not using delete .dataset here for compatibility down to IE9 | ||
} | ||
}); | ||
/** | ||
* 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]' | ||
); | ||
lazyItem.setAttribute('src', lazyItem.getAttribute('data-lazy-src')); | ||
lazyItem.removeAttribute('data-lazy-src'); // Not using delete .dataset here for compatibility down to IE9 | ||
if (placeholderSource) { | ||
// Preferred .removeChild over .remove here for IE | ||
lazyItemPicture.removeChild(placeholderSource); | ||
} | ||
} | ||
/** | ||
* 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; | ||
} | ||
} | ||
/** | ||
* 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; | ||
// If the item is visible now, load it and stop watching it | ||
var lazyItem = entry.target; | ||
observer.unobserve(lazyItem); | ||
observer.unobserve(lazyItem); | ||
restoreSource(lazyItem); | ||
}); | ||
} | ||
restoreSource(lazyItem); | ||
}); | ||
/** | ||
* Handle printing the page | ||
*/ | ||
function onPrinting() { | ||
if (typeof window.matchMedia === 'undefined') { | ||
return; | ||
} | ||
/** | ||
* 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); | ||
}); | ||
} | ||
}); | ||
} | ||
var mediaQueryList = window.matchMedia('print'); | ||
/** | ||
* 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; | ||
mediaQueryList.addListener(function (mql) { | ||
if (mql.matches) { | ||
document | ||
.querySelectorAll( | ||
config.lazyImage + | ||
'[data-lazy-src],' + | ||
config.lazyIframe + | ||
'[data-lazy-src]' | ||
) | ||
.forEach(function (lazyItem) { | ||
restoreSource(lazyItem); | ||
}); | ||
} | ||
}); | ||
} | ||
var getImageWidth = lazyAreaHtml.match(/width=['"](\d+)['"]/) || false; | ||
var temporaryImageWidth = getImageWidth[1] || 1; | ||
var getImageHeight = lazyAreaHtml.match(/height=['"](\d+)['"]/) || false; | ||
var temporaryImageHeight = getImageHeight[1] || 1; | ||
/** | ||
* 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; | ||
var temporaryImage = | ||
'data:image/svg+xml,%3Csvg xmlns=%27http://www.w3.org/2000/svg%27 viewBox=%270 0 ' + | ||
temporaryImageWidth + | ||
' ' + | ||
temporaryImageHeight + | ||
'%27%3E%3C/svg%3E'; | ||
var getImageWidth = lazyAreaHtml.match(/width=['"](\d+)['"]/) || false; | ||
var temporaryImageWidth = getImageWidth[1] || 1; | ||
var getImageHeight = lazyAreaHtml.match(/height=['"](\d+)['"]/) || false; | ||
var temporaryImageHeight = getImageHeight[1] || 1; | ||
if (!capabilities.loading && capabilities.scrolling) { | ||
// 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 { | ||
// Temporarily prevent expensive resource loading by inserting a <source> tag pointing to a simple one (data URI) | ||
lazyAreaHtml = lazyAreaHtml.replace( | ||
'<source', | ||
'<source srcset="' + | ||
temporaryImage + | ||
'" data-lazy-remove="true"></source>\n<source' | ||
); | ||
var temporaryImage = | ||
'data:image/svg+xml,%3Csvg xmlns=%27http://www.w3.org/2000/svg%27 viewBox=%270 0 ' + | ||
temporaryImageWidth + | ||
' ' + | ||
temporaryImageHeight + | ||
'%27%3E%3C/svg%3E'; | ||
if (!capabilities.loading && capabilities.scrolling) { | ||
// 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( | ||
// 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, | ||
' lazyload="1" src=' | ||
' src="' + temporaryImage + '" data-lazy-src=' | ||
); | ||
} else { | ||
// Temporarily prevent expensive resource loading by inserting a <source> tag pointing to a simple one (data URI) | ||
lazyAreaHtml = lazyAreaHtml.replace( | ||
'<source', | ||
'<source srcset="' + | ||
temporaryImage + | ||
'" data-lazy-remove="true"></source>\n<source' | ||
); | ||
// 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'); | ||
return lazyAreaHtml; | ||
} | ||
lazyArea.innerHTML = getAndPrepareHTMLCode(noScriptTag); | ||
/** | ||
* 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'); | ||
// Move all children out of the element | ||
while (lazyArea.firstChild) { | ||
var actualChild = lazyArea.firstChild; | ||
lazyArea.innerHTML = getAndPrepareHTMLCode(noScriptTag); | ||
if ( | ||
!capabilities.loading && | ||
capabilities.scrolling && | ||
typeof intersectionObserver !== 'undefined' && | ||
actualChild.tagName && | ||
(actualChild.tagName.toLowerCase() === 'img' || | ||
actualChild.tagName.toLowerCase() === 'picture' || | ||
actualChild.tagName.toLowerCase() === 'iframe') | ||
) { | ||
var observedElement = | ||
actualChild.tagName.toLowerCase() === 'picture' | ||
? lazyArea.querySelector('img') | ||
: actualChild; | ||
// Observe the item so that loading could start when it gets close to the viewport | ||
intersectionObserver.observe(observedElement); | ||
} | ||
// Move all children out of the element | ||
while (lazyArea.firstChild) { | ||
var actualChild = lazyArea.firstChild; | ||
noScriptTag.parentNode.insertBefore(actualChild, noScriptTag); | ||
if ( | ||
!capabilities.loading && | ||
capabilities.scrolling && | ||
typeof intersectionObserver !== 'undefined' && | ||
actualChild.tagName && | ||
(actualChild.tagName.toLowerCase() === 'img' || | ||
actualChild.tagName.toLowerCase() === 'picture' || | ||
actualChild.tagName.toLowerCase() === 'iframe') | ||
) { | ||
var observedElement = | ||
actualChild.tagName.toLowerCase() === 'picture' | ||
? lazyArea.querySelector('img') | ||
: actualChild; | ||
// Observe the item so that loading could start when it gets close to the viewport | ||
intersectionObserver.observe(observedElement); | ||
} | ||
// Remove the empty element - not using .remove() here for IE11 compatibility | ||
noScriptTag.parentNode.removeChild(noScriptTag); // Preferred .removeChild over .remove here for IE | ||
noScriptTag.parentNode.insertBefore(actualChild, noScriptTag); | ||
} | ||
/** | ||
* Get all the <noscript> tags on the page and setup the printing | ||
*/ | ||
function prepareElements() { | ||
// | ||
var lazyLoadAreas = document.querySelectorAll('noscript.' + noscriptClass); | ||
// Remove the empty element - not using .remove() here for IE11 compatibility | ||
noScriptTag.parentNode.removeChild(noScriptTag); | ||
} | ||
lazyLoadAreas.forEach(function (element) { | ||
prepareElement(element); | ||
}); | ||
/** | ||
* Get all the <noscript> tags on the page and setup the printing | ||
*/ | ||
let prepareElements = () => { | ||
var lazyLoadAreas = document.querySelectorAll('noscript.loading-lazy'); | ||
// Bind for someone printing the page | ||
onPrinting(); | ||
} | ||
lazyLoadAreas.forEach((element) => prepareElement(element)); | ||
// 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 | ||
// document.readyState values: https://www.w3schools.com/jsref/prop_doc_readystate.asp | ||
if (/comp|inter/.test(document.readyState)) { | ||
// 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 | ||
// document.readyState values: https://www.w3schools.com/jsref/prop_doc_readystate.asp | ||
if (/comp|inter/.test(document.readyState)) { | ||
rAFWrapper(prepareElements); | ||
} else if ('addEventListener' in document) { | ||
document.addEventListener('DOMContentLoaded', function () { | ||
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'); | ||
}); | ||
} else { | ||
document.attachEvent('onreadystatechange', function () { | ||
if (document.readyState === 'complete') { | ||
prepareElements(); | ||
} | ||
}); | ||
} | ||
const loadingAttributePolyfill = { | ||
prepareElement: prepareElement, | ||
}; | ||
export default loadingAttributePolyfill; |
# Migration guidelines | ||
## 2.0.0 migration guide | ||
## 2.0.0-beta.1 migration guide | ||
We've switched from previously only providing the source and a minified version of the JS, to additionally provide the different relevant JavaScript formats especially regarding modules supported by [microbundle](https://npmjs.com/microbundle). Thatfor we even also changed the location of the generated files as well as pointed the relevant property entries within the `package.json` to those files. Please find all the relevant generated files in the `dist/` folder from now on. | ||
## 2.0.0-beta.0 migration guide | ||
You'll need to wrap the `<picture>` tag instead of the included HTML tags with `<noscript>`. | ||
@@ -6,0 +10,0 @@ |
{ | ||
"name": "loading-attribute-polyfill", | ||
"version": "2.0.0-beta.0", | ||
"version": "2.0.0-beta.1", | ||
"description": "Fast and lightweight dependency-free vanilla JavaScript polyfill for native lazy loading / the awesome loading='lazy'-attribute.", | ||
"main": "loading-attribute-polyfill.min.js", | ||
"source": "./loading-attribute-polyfill.js", | ||
"main": "./dist/loading-attribute-polyfill.js", | ||
"esmodule": "./dist/loading-attribute-polyfill.modern.js", | ||
"module": "./dist/loading-attribute-polyfill.module.js", | ||
"umd:main": "./dist/loading-attribute-polyfill.umd.js", | ||
"repository": { | ||
@@ -38,6 +42,7 @@ "type": "git", | ||
"husky": "^5.1.2", | ||
"microbundle": "^0.13.0", | ||
"npm-run-all": "^4.1.5", | ||
"prettier": "2.2.1", | ||
"pretty-quick": "^3.1.0", | ||
"webdriverio": "^6.1.12", | ||
"webdriverio": "^6.12.1", | ||
"xo": "^0.38.2" | ||
@@ -49,2 +54,4 @@ }, | ||
"validate:html": "html-validate demo/index.html", | ||
"build": "microbundle", | ||
"dev": "microbundle watch", | ||
"release:minor": "npm version minor -m 'build(release): new minor version' && git push && git push --tags && npm publish", | ||
@@ -51,0 +58,0 @@ "release:patch": "npm version patch -m 'build(release): new patch version' && git push && git push --tags && npm publish", |
@@ -141,4 +141,8 @@ # loading="lazy" attribute polyfill | ||
Nothing really, just integrate it as shown within the "installation" section, and it ~~will~~ should work out of the box. | ||
In case that you're dynamically adding HTML elements within the browser, you could call the following method with an included [HTMLElement](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement) object, like e.g.: | ||
```JavaScript | ||
loadingAttributePolyfill.prepareElement(document.querySelector('main noscript.loading-lazy')); | ||
``` | ||
## Demo | ||
@@ -145,0 +149,0 @@ |
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
92929
22
330
205
15