aboutsummaryrefslogtreecommitdiffstats
path: root/web/js/loading-attribute-polyfill.js
diff options
context:
space:
mode:
authorMarius Halden <marius.h@lden.org>2021-10-07 13:32:40 +0200
committerMarius Halden <marius.h@lden.org>2021-10-07 13:32:40 +0200
commit09dacfc6b8bf62addeee16c20b1d90c2a256da96 (patch)
tree7caa2bf9e92227ab74448f9b746dd28bbcb81b2a /web/js/loading-attribute-polyfill.js
parent585e57484f9c6332668bf1ac0a6a3b39dbe32223 (diff)
parentcea89fb87a96943708a1db0f646492fbfaaf000f (diff)
Merge tag 'v3.1' into fiksgatami-devfiksgatami-dev
Diffstat (limited to 'web/js/loading-attribute-polyfill.js')
-rw-r--r--web/js/loading-attribute-polyfill.js213
1 files changed, 213 insertions, 0 deletions
diff --git a/web/js/loading-attribute-polyfill.js b/web/js/loading-attribute-polyfill.js
new file mode 100644
index 000000000..f11397985
--- /dev/null
+++ b/web/js/loading-attribute-polyfill.js
@@ -0,0 +1,213 @@
+/*
+ * 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, @Flimm, @TomS- and @vinyfc93 - 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.
+ *
+ * MS - Removed iframe/picture/srcset parts, unneeded at present, and added external API
+ */
+
+(function () {
+ 'use strict';
+
+ var config = {
+ // Start download if the item gets within 256px in the Y axis
+ rootMargin: '256px 0px',
+ threshold: 0.01
+ };
+
+ // Device/browser capabilities object
+ var capabilities = {
+ loading: 'loading' in HTMLImageElement.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();
+ };
+ }
+
+ /**
+ * Put the source 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) {
+ lazyItem.setAttribute('src', lazyItem.getAttribute('data-lazy-src'));
+ lazyItem.removeAttribute('data-lazy-src'); // Not using delete .dataset here for compatibility down to IE9
+ }
+
+ /**
+ * 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('img[loading="lazy"][data-lazy-src]')
+ .forEach(function (lazyItem) {
+ restoreSource(lazyItem);
+ });
+ }
+ });
+ }
+
+ /**
+ * Get and prepare the HTML code depending on feature detection,
+ * 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 getImageWidth = lazyAreaHtml.match(/width=['"](\d+)['"]/) || false;
+ var temporaryImageWidth = getImageWidth[1] || 1;
+ var getImageHeight = lazyAreaHtml.match(/height=['"](\d+)['"]/) || false;
+ var temporaryImageHeight = getImageHeight[1] || 1;
+
+ 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(
+ /(?:\r\n|\r|\n|\t| )src=/g,
+ ' lazyload="1" src='
+ );
+ } else {
+ // Temporarily replace a expensive resource load with a simple one by storing the actual source for later and point src to a temporary replacement (data URI)
+ lazyAreaHtml = lazyAreaHtml
+ .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' &&
+ lazyArea.firstChild.tagName &&
+ lazyArea.firstChild.tagName.toLowerCase() === 'img'
+ ) {
+ // 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
+ }
+
+ /* Add a function we can call externally */
+ fixmystreet.loading_recheck = function() {
+ var lazyLoadAreas = document.querySelectorAll('noscript.loading-lazy');
+ lazyLoadAreas.forEach(prepareElement);
+ };
+
+ /**
+ * Get all the <noscript> tags on the page and setup the printing
+ */
+ function prepareElements() {
+ fixmystreet.loading_recheck();
+
+ // 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 {
+ document.attachEvent('onreadystatechange', function () {
+ if (document.readyState === 'complete') {
+ prepareElements();
+ }
+ });
+ }
+})();