loading-attribute-polyfill
Advanced tools
Comparing version 0.2.0 to 1.0.0
@@ -11,6 +11,22 @@ # Changelog | ||
- Documentation | ||
- Cross browser todos | ||
- Tests | ||
- Ongoing: Further documentation | ||
## [1.0.0] - 2019-08-10 | ||
### Added | ||
- Comment regarding asynchronous loading | ||
- Webdriver.io testing | ||
### Changed | ||
- BREAKING CHANGE: You#ll need to also wrap the `<source>` HTML tags within the `<picture>` tags with `<noscript>` | ||
### Fix | ||
- Documents markup regarding codacy suggestions | ||
- Corrected sample image measurements | ||
- The images didn't load lazily in Safari, but directly, as reported with #GH-3 | ||
- Displaying the images on smaller viewports on the sample page | ||
## [0.2.0] - 2019-05-22 | ||
@@ -17,0 +33,0 @@ |
@@ -20,3 +20,6 @@ /* | ||
lazyImage: 'img[loading="lazy"]', | ||
lazyIframe: 'iframe[loading="lazy"]' | ||
lazyIframe: 'iframe[loading="lazy"]', | ||
loadingSupported: | ||
'loading' in HTMLImageElement.prototype && | ||
'loading' in HTMLIFrameElement.prototype | ||
}; | ||
@@ -47,18 +50,12 @@ | ||
* Temporarily replace a expensive resource load with a simple one | ||
* @param {Object} lazyItem Current item to be transformed for lazy loading. | ||
* @param {string} tempData Temporary data to be inserted as a 'placeholder'. | ||
* @param {String} lazyItem Current item to be transformed for lazy loading. | ||
*/ | ||
function storeSourceForLater(lazyItem, tempData) { | ||
// Store the actual source and srcset for later | ||
lazyItem.dataset.lazySrc = lazyItem.getAttribute('src'); | ||
if (lazyItem.getAttribute('srcset')) { | ||
lazyItem.dataset.lazySrcset = lazyItem.getAttribute('srcset'); | ||
} | ||
// Set the item to point to a temporary replacement (data URI) and remove srcset | ||
lazyItem.setAttribute('src', tempData); | ||
lazyItem.removeAttribute('srcset'); | ||
// Now observe the item so that loading could start when it gets close to the viewport | ||
intersectionObserver.observe(lazyItem); | ||
function rewriteSourceForLater(lazyItem) { | ||
// Store the actual source and srcset for later and point src to a temporary replacement (data URI) | ||
return lazyItem | ||
.replace(/(?:\r\n|\r|\n|\t| )srcset=/g, ' data-lazy-srcset=') | ||
.replace( | ||
/(?:\r\n|\r|\n|\t| )src=/g, | ||
' src="' + temporaryImage + '" data-lazy-src=' | ||
); | ||
} | ||
@@ -68,45 +65,20 @@ | ||
* Temporarily prevent expensive resource loading by inserting a <source> tag pointing to a simple one (data URI) | ||
* @param {Object} lazyItem Current item to be transformed for lazy loading. | ||
* @param {string} tempData Temporary data to be inserted as a 'placeholder'. | ||
* @param {String} lazyItem Current item to be transformed for lazy loading. | ||
*/ | ||
function placeholderSourceLoading(lazyItem, tempData) { | ||
var placeholderSource = document.createElement('source'); | ||
placeholderSource.setAttribute('srcset', tempData); | ||
placeholderSource.dataset.lazyRemove = true; | ||
function placeholderSourceLoading(lazyItem) { | ||
// Adding this <source> tag at the start of the picture tag means the browser will load it first | ||
lazyItem.insertBefore(placeholderSource, lazyItem.firstChild); | ||
var baseImage = lazyItem.querySelector('img'); | ||
if (baseImage) { | ||
// On <picture> tags image needs to get observed (as the picture tag is smaller than the image most likely) | ||
intersectionObserver.observe(baseImage); | ||
} | ||
return ( | ||
'<source srcset="' + | ||
temporaryImage + | ||
'" data-lazy-remove="true"></source>' + | ||
lazyItem | ||
); | ||
} | ||
/** | ||
* Set up the lazy items so that they won't try to load after adding them to the document | ||
* @param {Object} lazyArea Temporary created element for handling the <noscript> content | ||
* @param {Object} noScriptTagParentNode Parent node of <noscript> HTML tag | ||
* Attach abandonned attribute 'lazyload' to the HTML tags on browsers w/o IntersectionObserver being available | ||
* @param {String} lazyItem Current item to be transformed for lazy loading. | ||
*/ | ||
function prepareLazyContents(lazyArea, noScriptTagParentNode) { | ||
var lazyItem = lazyArea.querySelector( | ||
config.lazyImage + ',' + config.lazyIframe | ||
); | ||
// Check for IntersectionObserver support to not delay loading of the items content | ||
if (typeof intersectionObserver === 'undefined') { | ||
// Attach abandonned attribute 'lazyload' to the HTML tags on browsers w/o IntersectionObserver being available | ||
lazyItem.setAttribute('lazyload', 1); | ||
return; | ||
} | ||
storeSourceForLater(lazyItem, temporaryImage); | ||
if (noScriptTagParentNode.tagName.toLowerCase() === 'picture') { | ||
placeholderSourceLoading(noScriptTagParentNode, temporaryImage); | ||
} | ||
function addLazyloadAttribute(lazyItem) { | ||
return lazyItem.replace(/(?:\r\n|\r|\n|\t| )src=/g, ' lazyload="1" src='); | ||
} | ||
@@ -119,12 +91,20 @@ | ||
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); | ||
} | ||
if (lazyItem.dataset.lazySrcset) { | ||
lazyItem.setAttribute('srcset', lazyItem.dataset.lazySrcset); | ||
delete lazyItem.dataset.lazySrcset; | ||
srcsetItems.push(...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); | ||
@@ -177,12 +157,12 @@ delete lazyItem.dataset.lazySrc; | ||
if (mql.matches) { | ||
var lazyItems = document.querySelectorAll( | ||
config.lazyImage + | ||
'[data-lazy-src],' + | ||
config.lazyIframe + | ||
'[data-lazy-src]' | ||
); | ||
lazyItems.forEach(function(lazyItem) { | ||
restoreSource(lazyItem); | ||
}); | ||
document | ||
.querySelectorAll( | ||
config.lazyImage + | ||
'[data-lazy-src],' + | ||
config.lazyIframe + | ||
'[data-lazy-src]' | ||
) | ||
.forEach(function(lazyItem) { | ||
restoreSource(lazyItem); | ||
}); | ||
} | ||
@@ -203,2 +183,16 @@ }); | ||
// Feature detection for both image as well as iframe | ||
if (!config.loadingSupported) { | ||
// Check for IntersectionObserver support | ||
if (typeof intersectionObserver !== 'undefined') { | ||
if (noScriptTag.parentNode.tagName.toLowerCase() === 'picture') { | ||
lazyAreaHtml = placeholderSourceLoading(lazyAreaHtml); | ||
} | ||
lazyAreaHtml = rewriteSourceForLater(lazyAreaHtml); | ||
} else { | ||
lazyAreaHtml = addLazyloadAttribute(lazyAreaHtml); | ||
} | ||
} | ||
// Sticking them in the innerHTML of a new <div> tag to 'load' them | ||
@@ -209,18 +203,19 @@ var lazyArea = document.createElement('div'); | ||
var noScriptTagParentNode = noScriptTag.parentNode; | ||
// move all children out of the element | ||
while (lazyArea.firstChild) { | ||
if ( | ||
!config.loadingSupported && | ||
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); | ||
} | ||
// Feature detection for both image as well as iframe | ||
if ( | ||
!( | ||
'loading' in HTMLImageElement.prototype && | ||
'loading' in HTMLIFrameElement.prototype | ||
) | ||
) { | ||
prepareLazyContents(lazyArea, noScriptTagParentNode); | ||
noScriptTag.parentNode.insertBefore(lazyArea.firstChild, noScriptTag); | ||
} | ||
noScriptTagParentNode.replaceChild( | ||
lazyArea.firstElementChild, | ||
noScriptTag | ||
); | ||
// remove the empty element | ||
noScriptTag.remove(); | ||
}); | ||
@@ -227,0 +222,0 @@ |
@@ -5,2 +5,2 @@ /* | ||
*/ | ||
!function(e,t){"use strict";var a,r,n={rootMargin:"256px 0px",threshold:.01,lazyImage:'img[loading="lazy"]',lazyIframe:'iframe[loading="lazy"]'};"IntersectionObserver"in window&&(a=new IntersectionObserver(function(e,t){e.forEach(function(e){if(0!==e.intersectionRatio){var a=e.target;t.unobserve(a),c(a)}})},n)),r="requestAnimationFrame"in window?window.requestAnimationFrame:function(e){e()};var o="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==";function i(e,t){var r=e.querySelector(n.lazyImage+","+n.lazyIframe);void 0!==a?(function(e,t){e.dataset.lazySrc=e.getAttribute("src"),e.getAttribute("srcset")&&(e.dataset.lazySrcset=e.getAttribute("srcset")),e.setAttribute("src",t),e.removeAttribute("srcset"),a.observe(e)}(r,o),"picture"===t.tagName.toLowerCase()&&function(e,t){var r=document.createElement("source");r.setAttribute("srcset",t),r.dataset.lazyRemove=!0,e.insertBefore(r,e.firstChild);var n=e.querySelector("img");n&&a.observe(n)}(t,o)):r.setAttribute("lazyload",1)}function c(e){var t,a;"picture"===e.parentNode.tagName.toLowerCase()&&(t=e.parentNode,(a=t.querySelector("source[data-lazy-remove]"))&&t.removeChild(a)),e.dataset.lazySrcset&&(e.setAttribute("srcset",e.dataset.lazySrcset),delete e.dataset.lazySrcset),e.setAttribute("src",e.dataset.lazySrc),delete e.dataset.lazySrc}function s(){document.querySelectorAll("noscript."+e).forEach(function(e){var t=e.textContent||e.innerHTML,a=document.createElement("div");a.innerHTML=t;var r=e.parentNode;"loading"in HTMLImageElement.prototype&&"loading"in HTMLIFrameElement.prototype||i(a,r),r.replaceChild(a.firstElementChild,e)}),window.matchMedia("print").addListener(function(e){e.matches&&document.querySelectorAll(n.lazyImage+"[data-lazy-src],"+n.lazyIframe+"[data-lazy-src]").forEach(function(e){c(e)})})}/comp|inter/.test(document.readyState)?r(s):"addEventListener"in document?document.addEventListener("DOMContentLoaded",function(){r(s)}):document.attachEvent("onreadystatechange",function(){"complete"===document.readyState&&s()})}("loading-lazy"); | ||
!function(e,t){"use strict";var r,a,n={rootMargin:"256px 0px",threshold:.01,lazyImage:'img[loading="lazy"]',lazyIframe:'iframe[loading="lazy"]',loadingSupported:"loading"in HTMLImageElement.prototype&&"loading"in HTMLIFrameElement.prototype};"IntersectionObserver"in window&&(r=new IntersectionObserver(function(e,t){e.forEach(function(e){if(0!==e.intersectionRatio){var r=e.target;t.unobserve(r),i(r)}})},n)),a="requestAnimationFrame"in window?window.requestAnimationFrame:function(e){e()};var o="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==";function i(e){var t,r,a=[];"picture"===e.parentNode.tagName.toLowerCase()&&(t=e.parentNode,(r=t.querySelector("source[data-lazy-remove]"))&&t.removeChild(r),a.push(...e.parentNode.querySelectorAll("source"))),a.push(e),a.forEach(function(e){e.dataset.lazySrcset&&(e.setAttribute("srcset",e.dataset.lazySrcset),delete e.dataset.lazySrcset)}),e.setAttribute("src",e.dataset.lazySrc),delete e.dataset.lazySrc}function c(){document.querySelectorAll("noscript."+e).forEach(function(e){var t=e.textContent||e.innerHTML;n.loadingSupported||(void 0!==r?("picture"===e.parentNode.tagName.toLowerCase()&&(t='<source srcset="'+o+'" data-lazy-remove="true"></source>'+t),t=function(e){return e.replace(/(?:\r\n|\r|\n|\t| )srcset=/g," data-lazy-srcset=").replace(/(?:\r\n|\r|\n|\t| )src=/g,' src="'+o+'" data-lazy-src=')}(t)):t=function(e){return e.replace(/(?:\r\n|\r|\n|\t| )src=/g,' lazyload="1" src=')}(t));var a=document.createElement("div");for(a.innerHTML=t;a.firstChild;)n.loadingSupported||!a.firstChild.tagName||"img"!==a.firstChild.tagName.toLowerCase()&&"iframe"!==a.firstChild.tagName.toLowerCase()||r.observe(a.firstChild),e.parentNode.insertBefore(a.firstChild,e);e.remove()}),window.matchMedia("print").addListener(function(e){e.matches&&document.querySelectorAll(n.lazyImage+"[data-lazy-src],"+n.lazyIframe+"[data-lazy-src]").forEach(function(e){i(e)})})}/comp|inter/.test(document.readyState)?a(c):"addEventListener"in document?document.addEventListener("DOMContentLoaded",function(){a(c)}):document.attachEvent("onreadystatechange",function(){"complete"===document.readyState&&c()})}("loading-lazy"); |
{ | ||
"name": "loading-attribute-polyfill", | ||
"version": "0.2.0", | ||
"version": "1.0.0", | ||
"description": "A minimal and dependency-free vanilla JavaScript polyfill for the awesome loading='lazy'-attribute.", | ||
@@ -27,3 +27,9 @@ "main": "loading-attribute-polyfill.min.js", | ||
"devDependencies": { | ||
"@wdio/cli": "^5.8.5", | ||
"@wdio/dot-reporter": "^5.7.8", | ||
"@wdio/local-runner": "^5.8.6", | ||
"@wdio/mocha-framework": "^5.8.1", | ||
"@wdio/sync": "^5.8.6", | ||
"prettier": "1.17.0", | ||
"webdriverio": "^5.8.5", | ||
"xo": "^0.24.0" | ||
@@ -30,0 +36,0 @@ }, |
@@ -15,3 +15,3 @@ # loading="lazy" attribute polyfill | ||
- Supports the standard loading="lazy" attribute on `image` and `iframe` elements | ||
- Supports the standard `loading="lazy"` attribute on `image` and `iframe` elements | ||
- Released under the MIT license | ||
@@ -40,4 +40,6 @@ - Made in Germany | ||
Afterwards you'll need to wrap all of your `<img>` and `<iframe>` HTML tags that you'd like to lazy load (and thatfor added a {{loading="lazy"}} attribute as well) by an `<iframe>` HTML tag: | ||
You could even load the polyfill asynchronously: <https://jsbin.com/yitarajawe/edit?html,css> | ||
Afterwards you'll need to wrap all of your `<img>` and `<iframe>` HTML tags that you'd like to lazy load (and thatfor added a `loading="lazy"` attribute as well) by an `<iframe>` HTML tag: | ||
### Simple image | ||
@@ -121,3 +123,3 @@ | ||
https://www.npmjs.com/package/intersection-observer | ||
<https://www.npmjs.com/package/intersection-observer> | ||
@@ -124,0 +126,0 @@ Nevertheless this polyfill would still work in those browsers without that other polyfill included, but [this small amount of users](<(https://caniuse.com/#feat=intersectionobserver)>) wouldn't totally benefit from the lazy loading functionality - we've at least got you partly covered by using the [Microsoft proprietary lazyloading resource hints](https://caniuse.com/#feat=lazyload). |
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
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
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
Deprecated
MaintenanceThe maintainer of the package marked it as deprecated. This could indicate that a single version should not be used, or that the package is no longer maintained and any new vulnerabilities will not be fixed.
Found 1 instance in 1 package
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
Found 1 instance in 1 package
44017
11
691
0
0
138
8
1