I’ll build this as a safe, advanced lazy-load utility chunk for RX Theme: images, iframes, videos, backgrounds, scripts, styles, prefetch, idle tasks, and fallback support. It will be standalone so you can place it directly in assets/static-js/static-chunks-js/chunk-008-lazyload.js.
My dear friend, create this file:
rx-theme/assets/static-js/static-chunks-js/chunk-008-lazyload.js
Then paste this full advanced lazy-load code inside it.
/*!
* RX Theme - Advanced Lazy Load Chunk
* File: assets/static-js/static-chunks-js/chunk-008-lazyload.js
* Purpose:
* - Lazy load images, iframes, videos, background images
* - Lazy load scripts and styles
* - Support responsive images: srcset, sizes, picture/source
* - Support native loading="lazy"
* - Support IntersectionObserver
* - Fallback for old browsers
* - Idle loading, priority hints, prefetch/preload helper
* - MutationObserver for Ajax/dynamic content
*
* Author: RX Theme
*/
(function () {
"use strict";
/**
* Prevent duplicate loading if file is included more than once.
*/
if (window.RXThemeLazyLoadInitialized) {
return;
}
window.RXThemeLazyLoadInitialized = true;
/**
* Main global object.
*/
var RXLazy = {
version: "1.0.0",
config: {
root: null,
rootMargin: "300px 0px",
threshold: 0.01,
imageSelector:
"img[data-src], img[data-srcset], img.rx-lazy, img[loading='lazy']",
pictureSourceSelector:
"picture source[data-srcset], source[data-srcset]",
iframeSelector:
"iframe[data-src], iframe.rx-lazy-frame",
videoSelector:
"video[data-src], video.rx-lazy-video, video[data-poster], video source[data-src]",
backgroundSelector:
"[data-bg], [data-bg-image], [data-background], .rx-lazy-bg",
scriptSelector:
"script[type='text/rx-lazy-script'][data-src], script[data-rx-lazy-script]",
styleSelector:
"link[data-rx-lazy-style], link[data-href]",
moduleSelector:
"[data-rx-module]",
lazyClass: "rx-lazy",
loadingClass: "rx-lazy-loading",
loadedClass: "rx-lazy-loaded",
errorClass: "rx-lazy-error",
useNativeLazy: true,
observeDynamicContent: true,
enableIdleLoad: true,
idleTimeout: 2500,
retryCount: 2,
debug: false
},
observers: {
image: null,
iframe: null,
video: null,
background: null,
module: null
},
loadedAssets: {
scripts: {},
styles: {},
images: {}
}
};
/**
* Small helper: debug log.
*/
function debugLog() {
if (!RXLazy.config.debug) {
return;
}
if (window.console && typeof window.console.log === "function") {
window.console.log.apply(window.console, ["[RXLazy]"].concat([].slice.call(arguments)));
}
}
/**
* Check if browser supports IntersectionObserver.
*/
function supportsIntersectionObserver() {
return "IntersectionObserver" in window;
}
/**
* Check if browser supports native image lazy loading.
*/
function supportsNativeLazyLoading() {
return "loading" in HTMLImageElement.prototype;
}
/**
* requestIdleCallback fallback.
*/
function runWhenIdle(callback, timeout) {
if (typeof callback !== "function") {
return;
}
if ("requestIdleCallback" in window) {
window.requestIdleCallback(callback, {
timeout: timeout || RXLazy.config.idleTimeout
});
} else {
window.setTimeout(callback, 1);
}
}
/**
* requestAnimationFrame fallback.
*/
function runNextFrame(callback) {
if (typeof callback !== "function") {
return;
}
if ("requestAnimationFrame" in window) {
window.requestAnimationFrame(callback);
} else {
window.setTimeout(callback, 16);
}
}
/**
* Convert NodeList to Array safely.
*/
function toArray(nodes) {
return Array.prototype.slice.call(nodes || []);
}
/**
* Safe query selector.
*/
function queryAll(selector, context) {
try {
return toArray((context || document).querySelectorAll(selector));
} catch (error) {
debugLog("Invalid selector:", selector, error);
return [];
}
}
/**
* Add class safely.
*/
function addClass(element, className) {
if (!element || !className) {
return;
}
if (element.classList) {
element.classList.add(className);
} else if ((" " + element.className + " ").indexOf(" " + className + " ") === -1) {
element.className += " " + className;
}
}
/**
* Remove class safely.
*/
function removeClass(element, className) {
if (!element || !className) {
return;
}
if (element.classList) {
element.classList.remove(className);
} else {
element.className = (" " + element.className + " ")
.replace(" " + className + " ", " ")
.trim();
}
}
/**
* Mark loading state.
*/
function markLoading(element) {
addClass(element, RXLazy.config.loadingClass);
removeClass(element, RXLazy.config.loadedClass);
removeClass(element, RXLazy.config.errorClass);
}
/**
* Mark loaded state.
*/
function markLoaded(element) {
removeClass(element, RXLazy.config.loadingClass);
removeClass(element, RXLazy.config.errorClass);
addClass(element, RXLazy.config.loadedClass);
element.setAttribute("data-rx-lazy-status", "loaded");
}
/**
* Mark error state.
*/
function markError(element) {
removeClass(element, RXLazy.config.loadingClass);
addClass(element, RXLazy.config.errorClass);
element.setAttribute("data-rx-lazy-status", "error");
}
/**
* Dispatch custom event.
*/
function dispatchLazyEvent(element, eventName, detail) {
if (!element) {
return;
}
var event;
detail = detail || {};
try {
event = new CustomEvent(eventName, {
bubbles: true,
cancelable: true,
detail: detail
});
} catch (error) {
event = document.createEvent("CustomEvent");
event.initCustomEvent(eventName, true, true, detail);
}
element.dispatchEvent(event);
}
/**
* Check whether element was already processed.
*/
function isProcessed(element) {
return element && element.getAttribute("data-rx-lazy-status") === "loaded";
}
/**
* Set fetchpriority safely.
*/
function applyFetchPriority(element) {
if (!element) {
return;
}
var priority = element.getAttribute("data-fetchpriority") || element.getAttribute("data-priority");
if (priority) {
try {
element.setAttribute("fetchpriority", priority);
} catch (error) {
debugLog("fetchpriority not applied", error);
}
}
}
/**
* Load normal image.
*/
function loadImage(img) {
if (!img || isProcessed(img)) {
return;
}
markLoading(img);
applyFetchPriority(img);
var src = img.getAttribute("data-src");
var srcset = img.getAttribute("data-srcset");
var sizes = img.getAttribute("data-sizes");
var decoding = img.getAttribute("data-decoding") || "async";
try {
img.decoding = decoding;
} catch (error) {
debugLog("Image decoding not supported", error);
}
img.onload = function () {
markLoaded(img);
dispatchLazyEvent(img, "rxlazy:image:loaded", {
src: img.currentSrc || img.src
});
};
img.onerror = function () {
retryImage(img);
};
/**
* Load source tags inside picture first.
*/
var picture = img.parentNode && img.parentNode.nodeName
? img.parentNode.nodeName.toLowerCase() === "picture"
? img.parentNode
: null
: null;
if (picture) {
var sources = queryAll("source[data-srcset]", picture);
sources.forEach(function (source) {
var sourceSrcset = source.getAttribute("data-srcset");
var sourceSizes = source.getAttribute("data-sizes");
if (sourceSrcset) {
source.setAttribute("srcset", sourceSrcset);
source.removeAttribute("data-srcset");
}
if (sourceSizes) {
source.setAttribute("sizes", sourceSizes);
source.removeAttribute("data-sizes");
}
});
}
if (sizes) {
img.setAttribute("sizes", sizes);
img.removeAttribute("data-sizes");
}
if (srcset) {
img.setAttribute("srcset", srcset);
img.removeAttribute("data-srcset");
}
if (src) {
img.setAttribute("src", src);
img.removeAttribute("data-src");
}
/**
* If image is already complete from cache.
*/
if (img.complete && img.naturalWidth > 0) {
markLoaded(img);
}
}
/**
* Retry image loading.
*/
function retryImage(img) {
if (!img) {
return;
}
var currentRetry = parseInt(img.getAttribute("data-rx-retry") || "0", 10);
if (currentRetry < RXLazy.config.retryCount) {
img.setAttribute("data-rx-retry", String(currentRetry + 1));
var originalSrc = img.getAttribute("src");
if (originalSrc) {
var separator = originalSrc.indexOf("?") === -1 ? "?" : "&";
img.setAttribute("src", originalSrc + separator + "rx_retry=" + Date.now());
}
return;
}
markError(img);
dispatchLazyEvent(img, "rxlazy:image:error", {
src: img.getAttribute("src") || img.getAttribute("data-src")
});
}
/**
* Load picture source directly when needed.
*/
function loadPictureSource(source) {
if (!source || isProcessed(source)) {
return;
}
var srcset = source.getAttribute("data-srcset");
var sizes = source.getAttribute("data-sizes");
if (srcset) {
source.setAttribute("srcset", srcset);
source.removeAttribute("data-srcset");
}
if (sizes) {
source.setAttribute("sizes", sizes);
source.removeAttribute("data-sizes");
}
markLoaded(source);
}
/**
* Load iframe.
*/
function loadIframe(iframe) {
if (!iframe || isProcessed(iframe)) {
return;
}
var src = iframe.getAttribute("data-src");
if (!src) {
return;
}
markLoading(iframe);
iframe.onload = function () {
markLoaded(iframe);
dispatchLazyEvent(iframe, "rxlazy:iframe:loaded", {
src: iframe.src
});
};
iframe.onerror = function () {
markError(iframe);
dispatchLazyEvent(iframe, "rxlazy:iframe:error", {
src: src
});
};
iframe.setAttribute("src", src);
iframe.removeAttribute("data-src");
}
/**
* Load video.
*/
function loadVideo(video) {
if (!video || isProcessed(video)) {
return;
}
markLoading(video);
var poster = video.getAttribute("data-poster");
var src = video.getAttribute("data-src");
if (poster) {
video.setAttribute("poster", poster);
video.removeAttribute("data-poster");
}
if (src) {
video.setAttribute("src", src);
video.removeAttribute("data-src");
}
var sources = queryAll("source[data-src]", video);
sources.forEach(function (source) {
var sourceSrc = source.getAttribute("data-src");
if (sourceSrc) {
source.setAttribute("src", sourceSrc);
source.removeAttribute("data-src");
}
});
try {
video.load();
} catch (error) {
debugLog("Video load error", error);
}
video.onloadeddata = function () {
markLoaded(video);
dispatchLazyEvent(video, "rxlazy:video:loaded", {});
};
video.onerror = function () {
markError(video);
dispatchLazyEvent(video, "rxlazy:video:error", {});
};
/**
* Some videos may not fire loadeddata quickly.
*/
window.setTimeout(function () {
if (!isProcessed(video)) {
markLoaded(video);
}
}, 2000);
}
/**
* Load background image.
*/
function loadBackground(element) {
if (!element || isProcessed(element)) {
return;
}
var bg =
element.getAttribute("data-bg") ||
element.getAttribute("data-bg-image") ||
element.getAttribute("data-background");
if (!bg) {
return;
}
markLoading(element);
var tempImg = new Image();
tempImg.onload = function () {
element.style.backgroundImage = "url('" + bg.replace(/'/g, "\\'") + "')";
element.removeAttribute("data-bg");
element.removeAttribute("data-bg-image");
element.removeAttribute("data-background");
markLoaded(element);
dispatchLazyEvent(element, "rxlazy:background:loaded", {
src: bg
});
};
tempImg.onerror = function () {
markError(element);
dispatchLazyEvent(element, "rxlazy:background:error", {
src: bg
});
};
tempImg.src = bg;
}
/**
* Load CSS file dynamically.
*/
function loadStyleElement(link) {
if (!link || isProcessed(link)) {
return;
}
var href = link.getAttribute("data-href") || link.getAttribute("data-rx-lazy-style");
if (!href) {
return;
}
if (RXLazy.loadedAssets.styles[href]) {
markLoaded(link);
return;
}
markLoading(link);
link.onload = function () {
RXLazy.loadedAssets.styles[href] = true;
markLoaded(link);
dispatchLazyEvent(link, "rxlazy:style:loaded", {
href: href
});
};
link.onerror = function () {
markError(link);
dispatchLazyEvent(link, "rxlazy:style:error", {
href: href
});
};
link.setAttribute("rel", "stylesheet");
link.setAttribute("href", href);
link.removeAttribute("data-href");
link.removeAttribute("data-rx-lazy-style");
}
/**
* Load external script by URL.
*/
function loadScriptByUrl(src, options) {
options = options || {};
return new Promise(function (resolve, reject) {
if (!src) {
reject(new Error("Missing script src"));
return;
}
if (RXLazy.loadedAssets.scripts[src]) {
resolve(src);
return;
}
var script = document.createElement("script");
script.src = src;
script.async = options.async !== false;
script.defer = options.defer === true;
script.crossOrigin = options.crossorigin || options.crossOrigin || null;
if (options.type) {
script.type = options.type;
}
if (options.integrity) {
script.integrity = options.integrity;
}
if (options.referrerpolicy) {
script.referrerPolicy = options.referrerpolicy;
}
script.onload = function () {
RXLazy.loadedAssets.scripts[src] = true;
resolve(src);
};
script.onerror = function () {
reject(new Error("Failed to load script: " + src));
};
document.head.appendChild(script);
});
}
/**
* Load lazy script tag.
*/
function loadScriptElement(scriptElement) {
if (!scriptElement || isProcessed(scriptElement)) {
return;
}
var src =
scriptElement.getAttribute("data-src") ||
scriptElement.getAttribute("data-rx-lazy-script");
if (!src) {
return;
}
markLoading(scriptElement);
loadScriptByUrl(src, {
async: scriptElement.getAttribute("data-async") !== "false",
defer: scriptElement.getAttribute("data-defer") === "true",
type: scriptElement.getAttribute("data-script-type"),
integrity: scriptElement.getAttribute("data-integrity"),
crossorigin: scriptElement.getAttribute("data-crossorigin"),
referrerpolicy: scriptElement.getAttribute("data-referrerpolicy")
})
.then(function () {
markLoaded(scriptElement);
dispatchLazyEvent(scriptElement, "rxlazy:script:loaded", {
src: src
});
})
.catch(function () {
markError(scriptElement);
dispatchLazyEvent(scriptElement, "rxlazy:script:error", {
src: src
});
});
}
/**
* Load module by data attribute.
* Example:
* <div data-rx-module="/assets/js/comments.js"></div>
*/
function loadModuleElement(element) {
if (!element || isProcessed(element)) {
return;
}
var moduleSrc = element.getAttribute("data-rx-module");
if (!moduleSrc) {
return;
}
markLoading(element);
loadScriptByUrl(moduleSrc, {
async: true,
defer: true,
type: element.getAttribute("data-module-type") || "module"
})
.then(function () {
markLoaded(element);
dispatchLazyEvent(element, "rxlazy:module:loaded", {
src: moduleSrc
});
})
.catch(function () {
markError(element);
dispatchLazyEvent(element, "rxlazy:module:error", {
src: moduleSrc
});
});
}
/**
* Preload an asset.
*/
function preloadAsset(url, asType, options) {
if (!url) {
return;
}
options = options || {};
var selector = "link[rel='preload'][href='" + cssEscape(url) + "']";
if (document.querySelector(selector)) {
return;
}
var link = document.createElement("link");
link.rel = "preload";
link.href = url;
link.as = asType || "script";
if (options.crossorigin) {
link.crossOrigin = options.crossorigin;
}
if (options.type) {
link.type = options.type;
}
if (options.fetchpriority) {
link.setAttribute("fetchpriority", options.fetchpriority);
}
document.head.appendChild(link);
}
/**
* Prefetch an asset.
*/
function prefetchAsset(url, options) {
if (!url) {
return;
}
options = options || {};
var selector = "link[rel='prefetch'][href='" + cssEscape(url) + "']";
if (document.querySelector(selector)) {
return;
}
var link = document.createElement("link");
link.rel = "prefetch";
link.href = url;
if (options.crossorigin) {
link.crossOrigin = options.crossorigin;
}
document.head.appendChild(link);
}
/**
* CSS.escape fallback.
*/
function cssEscape(value) {
if (window.CSS && typeof window.CSS.escape === "function") {
return window.CSS.escape(value);
}
return String(value).replace(/'/g, "\\'");
}
/**
* Observe element with IntersectionObserver.
*/
function observeElement(observer, element, fallbackLoader) {
if (!element) {
return;
}
if (supportsIntersectionObserver() && observer) {
observer.observe(element);
} else if (typeof fallbackLoader === "function") {
fallbackLoader(element);
}
}
/**
* Create observer.
*/
function createObserver(loader) {
if (!supportsIntersectionObserver()) {
return null;
}
return new IntersectionObserver(
function (entries, observer) {
entries.forEach(function (entry) {
if (entry.isIntersecting || entry.intersectionRatio > 0) {
observer.unobserve(entry.target);
loader(entry.target);
}
});
},
{
root: RXLazy.config.root,
rootMargin: RXLazy.config.rootMargin,
threshold: RXLazy.config.threshold
}
);
}
/**
* Native lazy loading enhancement.
*/
function prepareNativeLazyImages(context) {
if (!RXLazy.config.useNativeLazy || !supportsNativeLazyLoading()) {
return;
}
var images = queryAll("img[data-src], img[data-srcset]", context);
images.forEach(function (img) {
if (!img.getAttribute("loading")) {
img.setAttribute("loading", "lazy");
}
if (!img.getAttribute("decoding")) {
img.setAttribute("decoding", "async");
}
});
var iframes = queryAll("iframe[data-src]", context);
iframes.forEach(function (iframe) {
if (!iframe.getAttribute("loading")) {
iframe.setAttribute("loading", "lazy");
}
});
}
/**
* Scan DOM and register lazy elements.
*/
function scan(context) {
context = context || document;
prepareNativeLazyImages(context);
var images = queryAll(RXLazy.config.imageSelector, context);
var sources = queryAll(RXLazy.config.pictureSourceSelector, context);
var iframes = queryAll(RXLazy.config.iframeSelector, context);
var videos = queryAll(RXLazy.config.videoSelector, context);
var backgrounds = queryAll(RXLazy.config.backgroundSelector, context);
var scripts = queryAll(RXLazy.config.scriptSelector, context);
var styles = queryAll(RXLazy.config.styleSelector, context);
var modules = queryAll(RXLazy.config.moduleSelector, context);
images.forEach(function (img) {
observeElement(RXLazy.observers.image, img, loadImage);
});
sources.forEach(function (source) {
observeElement(RXLazy.observers.image, source, loadPictureSource);
});
iframes.forEach(function (iframe) {
observeElement(RXLazy.observers.iframe, iframe, loadIframe);
});
videos.forEach(function (video) {
observeElement(RXLazy.observers.video, video, loadVideo);
});
backgrounds.forEach(function (element) {
observeElement(RXLazy.observers.background, element, loadBackground);
});
scripts.forEach(function (scriptElement) {
if (RXLazy.config.enableIdleLoad) {
runWhenIdle(function () {
loadScriptElement(scriptElement);
});
} else {
loadScriptElement(scriptElement);
}
});
styles.forEach(function (link) {
if (RXLazy.config.enableIdleLoad) {
runWhenIdle(function () {
loadStyleElement(link);
});
} else {
loadStyleElement(link);
}
});
modules.forEach(function (moduleElement) {
observeElement(RXLazy.observers.module, moduleElement, loadModuleElement);
});
}
/**
* Setup MutationObserver for dynamic/Ajax content.
*/
function setupMutationObserver() {
if (!RXLazy.config.observeDynamicContent || !("MutationObserver" in window)) {
return;
}
var mutationTimer = null;
var observer = new MutationObserver(function (mutations) {
var shouldScan = false;
mutations.forEach(function (mutation) {
if (mutation.addedNodes && mutation.addedNodes.length) {
shouldScan = true;
}
});
if (!shouldScan) {
return;
}
window.clearTimeout(mutationTimer);
mutationTimer = window.setTimeout(function () {
scan(document);
}, 120);
});
observer.observe(document.documentElement, {
childList: true,
subtree: true
});
}
/**
* Load assets after user interaction.
*/
function setupInteractionLoader() {
var loaded = false;
var events = [
"mousemove",
"mousedown",
"keydown",
"touchstart",
"scroll"
];
function onFirstInteraction() {
if (loaded) {
return;
}
loaded = true;
events.forEach(function (eventName) {
window.removeEventListener(eventName, onFirstInteraction, passiveEventOptions());
});
dispatchLazyEvent(document, "rxlazy:user:interaction", {});
runWhenIdle(function () {
loadInteractionAssets();
});
}
events.forEach(function (eventName) {
window.addEventListener(eventName, onFirstInteraction, passiveEventOptions());
});
}
/**
* Passive event option helper.
*/
function passiveEventOptions() {
var supportsPassive = false;
try {
var options = Object.defineProperty({}, "passive", {
get: function () {
supportsPassive = true;
return true;
}
});
window.addEventListener("rx-passive-test", null, options);
window.removeEventListener("rx-passive-test", null, options);
} catch (error) {
supportsPassive = false;
}
return supportsPassive ? { passive: true } : false;
}
/**
* Load assets marked for user interaction.
*
* Examples:
* <script type="text/rx-lazy-script" data-src="/chat.js" data-rx-load="interaction"></script>
* <link data-href="/extra.css" data-rx-load="interaction">
*/
function loadInteractionAssets() {
var interactionScripts = queryAll(
"script[type='text/rx-lazy-script'][data-src][data-rx-load='interaction']"
);
var interactionStyles = queryAll(
"link[data-href][data-rx-load='interaction'], link[data-rx-lazy-style][data-rx-load='interaction']"
);
interactionScripts.forEach(loadScriptElement);
interactionStyles.forEach(loadStyleElement);
}
/**
* Load assets after full window load.
*/
function setupWindowLoadAssets() {
window.addEventListener("load", function () {
runWhenIdle(function () {
var scripts = queryAll(
"script[type='text/rx-lazy-script'][data-src][data-rx-load='windowload']"
);
var styles = queryAll(
"link[data-href][data-rx-load='windowload'], link[data-rx-lazy-style][data-rx-load='windowload']"
);
scripts.forEach(loadScriptElement);
styles.forEach(loadStyleElement);
});
});
}
/**
* Preload assets marked by data-rx-preload.
*
* Example:
* <a href="/next-page/" data-rx-preload="hover">Next</a>
*/
function setupHoverPreload() {
document.addEventListener(
"mouseover",
function (event) {
var target = event.target;
while (target && target !== document) {
if (target.getAttribute && target.getAttribute("data-rx-preload") === "hover") {
var href = target.getAttribute("href") || target.getAttribute("data-href");
if (href) {
prefetchAsset(href);
}
break;
}
target = target.parentNode;
}
},
passiveEventOptions()
);
}
/**
* Lazy load CSS background from responsive data attributes.
*
* Example:
* data-bg-mobile=""
* data-bg-tablet=""
* data-bg-desktop=""
*/
function applyResponsiveBackgrounds(context) {
var elements = queryAll("[data-bg-mobile], [data-bg-tablet], [data-bg-desktop]", context);
elements.forEach(function (element) {
var width = window.innerWidth || document.documentElement.clientWidth;
var bg = "";
if (width <= 767) {
bg = element.getAttribute("data-bg-mobile");
} else if (width <= 1024) {
bg = element.getAttribute("data-bg-tablet");
} else {
bg = element.getAttribute("data-bg-desktop");
}
if (bg) {
element.setAttribute("data-bg", bg);
}
});
}
/**
* Resize handler for responsive backgrounds.
*/
function setupResponsiveBackgroundResize() {
var resizeTimer = null;
window.addEventListener(
"resize",
function () {
window.clearTimeout(resizeTimer);
resizeTimer = window.setTimeout(function () {
applyResponsiveBackgrounds(document);
scan(document);
}, 250);
},
passiveEventOptions()
);
}
/**
* Lazy load Google Maps iframe only when clicked.
*
* Example:
* <div class="rx-map-placeholder" data-rx-map-src="https://www.google.com/maps/embed?...">
* Click to load map
* </div>
*/
function setupClickToLoadMaps() {
document.addEventListener("click", function (event) {
var target = event.target;
while (target && target !== document) {
if (target.getAttribute && target.getAttribute("data-rx-map-src")) {
var src = target.getAttribute("data-rx-map-src");
if (!src) {
return;
}
var iframe = document.createElement("iframe");
iframe.src = src;
iframe.loading = "lazy";
iframe.width = target.getAttribute("data-width") || "100%";
iframe.height = target.getAttribute("data-height") || "400";
iframe.style.border = "0";
iframe.setAttribute("allowfullscreen", "");
iframe.setAttribute("referrerpolicy", "no-referrer-when-downgrade");
target.innerHTML = "";
target.appendChild(iframe);
target.removeAttribute("data-rx-map-src");
addClass(target, RXLazy.config.loadedClass);
dispatchLazyEvent(target, "rxlazy:map:loaded", {
src: src
});
break;
}
target = target.parentNode;
}
});
}
/**
* Lazy load YouTube/Vimeo iframe on click.
*
* Example:
* <div class="rx-video-placeholder" data-rx-embed-src="https://www.youtube.com/embed/VIDEO_ID">
* Play
* </div>
*/
function setupClickToLoadEmbeds() {
document.addEventListener("click", function (event) {
var target = event.target;
while (target && target !== document) {
if (target.getAttribute && target.getAttribute("data-rx-embed-src")) {
var src = target.getAttribute("data-rx-embed-src");
if (!src) {
return;
}
var iframe = document.createElement("iframe");
iframe.src = src;
iframe.loading = "lazy";
iframe.allow =
"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share";
iframe.allowFullscreen = true;
iframe.width = target.getAttribute("data-width") || "100%";
iframe.height = target.getAttribute("data-height") || "400";
iframe.style.border = "0";
target.innerHTML = "";
target.appendChild(iframe);
target.removeAttribute("data-rx-embed-src");
addClass(target, RXLazy.config.loadedClass);
dispatchLazyEvent(target, "rxlazy:embed:loaded", {
src: src
});
break;
}
target = target.parentNode;
}
});
}
/**
* Lazy load comments area.
*
* Example:
* <div id="comments" data-rx-comments-module="/assets/js/comments.js"></div>
*/
function setupCommentsLazyLoad() {
var comments = queryAll("[data-rx-comments-module]");
if (!comments.length) {
return;
}
var commentsObserver = createObserver(function (element) {
var src = element.getAttribute("data-rx-comments-module");
if (!src) {
return;
}
loadScriptByUrl(src, {
async: true,
defer: true
})
.then(function () {
markLoaded(element);
dispatchLazyEvent(element, "rxlazy:comments:loaded", {
src: src
});
})
.catch(function () {
markError(element);
});
});
comments.forEach(function (element) {
observeElement(commentsObserver, element, function () {
var src = element.getAttribute("data-rx-comments-module");
if (src) {
loadScriptByUrl(src);
}
});
});
}
/**
* Lazy load ads placeholder.
*
* Example:
* <div data-rx-ad-module="/assets/js/ads.js"></div>
*/
function setupAdsLazyLoad() {
var ads = queryAll("[data-rx-ad-module]");
if (!ads.length) {
return;
}
var adObserver = createObserver(function (element) {
var src = element.getAttribute("data-rx-ad-module");
if (!src) {
return;
}
loadScriptByUrl(src, {
async: true,
defer: true
})
.then(function () {
markLoaded(element);
dispatchLazyEvent(element, "rxlazy:ad:loaded", {
src: src
});
})
.catch(function () {
markError(element);
});
});
ads.forEach(function (element) {
observeElement(adObserver, element, function () {
var src = element.getAttribute("data-rx-ad-module");
if (src) {
loadScriptByUrl(src);
}
});
});
}
/**
* Lazy hydrate components.
*
* Example:
* <div data-rx-hydrate="/assets/js/component.js"></div>
*/
function setupLazyHydration() {
var components = queryAll("[data-rx-hydrate]");
if (!components.length) {
return;
}
var hydrationObserver = createObserver(function (element) {
var src = element.getAttribute("data-rx-hydrate");
if (!src) {
return;
}
loadScriptByUrl(src, {
async: true,
defer: true,
type: element.getAttribute("data-hydrate-type") || null
})
.then(function () {
markLoaded(element);
dispatchLazyEvent(element, "rxlazy:hydrate:loaded", {
src: src
});
})
.catch(function () {
markError(element);
});
});
components.forEach(function (element) {
observeElement(hydrationObserver, element, function () {
var src = element.getAttribute("data-rx-hydrate");
if (src) {
loadScriptByUrl(src);
}
});
});
}
/**
* Data API:
* <div data-rx-prefetch="/page-2/"></div>
* <div data-rx-preload="/assets/app.js" data-as="script"></div>
*/
function setupDataAssetHints(context) {
var preloads = queryAll("[data-rx-preload-url]", context);
var prefetches = queryAll("[data-rx-prefetch-url]", context);
preloads.forEach(function (element) {
var url = element.getAttribute("data-rx-preload-url");
var asType = element.getAttribute("data-as") || "script";
preloadAsset(url, asType, {
crossorigin: element.getAttribute("data-crossorigin"),
type: element.getAttribute("data-type"),
fetchpriority: element.getAttribute("data-fetchpriority")
});
});
prefetches.forEach(function (element) {
var url = element.getAttribute("data-rx-prefetch-url");
prefetchAsset(url, {
crossorigin: element.getAttribute("data-crossorigin")
});
});
}
/**
* Public API.
*/
RXLazy.scan = scan;
RXLazy.loadImage = loadImage;
RXLazy.loadIframe = loadIframe;
RXLazy.loadVideo = loadVideo;
RXLazy.loadBackground = loadBackground;
RXLazy.loadScriptByUrl = loadScriptByUrl;
RXLazy.preloadAsset = preloadAsset;
RXLazy.prefetchAsset = prefetchAsset;
RXLazy.runWhenIdle = runWhenIdle;
/**
* Initialize observers.
*/
function setupObservers() {
RXLazy.observers.image = createObserver(function (element) {
if (element.nodeName && element.nodeName.toLowerCase() === "source") {
loadPictureSource(element);
} else {
loadImage(element);
}
});
RXLazy.observers.iframe = createObserver(loadIframe);
RXLazy.observers.video = createObserver(loadVideo);
RXLazy.observers.background = createObserver(loadBackground);
RXLazy.observers.module = createObserver(loadModuleElement);
}
/**
* Initialize everything.
*/
function init() {
setupObservers();
applyResponsiveBackgrounds(document);
setupDataAssetHints(document);
scan(document);
setupMutationObserver();
setupInteractionLoader();
setupWindowLoadAssets();
setupHoverPreload();
setupResponsiveBackgroundResize();
setupClickToLoadMaps();
setupClickToLoadEmbeds();
setupCommentsLazyLoad();
setupAdsLazyLoad();
setupLazyHydration();
dispatchLazyEvent(document, "rxlazy:ready", {
version: RXLazy.version
});
debugLog("RX LazyLoad initialized");
}
/**
* Start safely.
*/
if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", init);
} else {
runNextFrame(init);
}
/**
* Expose globally.
*/
window.RXLazy = RXLazy;
})();
Example HTML usage
Lazy image:
<img
class="rx-lazy"
src="small-placeholder.jpg"
data-src="real-image.jpg"
data-srcset="real-image-480.jpg 480w, real-image-800.jpg 800w"
data-sizes="(max-width: 768px) 100vw, 800px"
alt="RX Theme image"
loading="lazy"
decoding="async">
Lazy background:
<div class="rx-lazy-bg" data-bg="hero-image.jpg"></div>
Responsive lazy background:
<div
class="rx-lazy-bg"
data-bg-mobile="hero-mobile.jpg"
data-bg-tablet="hero-tablet.jpg"
data-bg-desktop="hero-desktop.jpg">
</div>
Lazy iframe:
<iframe
class="rx-lazy-frame"
data-src="https://www.youtube.com/embed/example"
loading="lazy"
width="100%"
height="400">
</iframe>
Click-to-load video embed:
<div
class="rx-video-placeholder"
data-rx-embed-src="https://www.youtube.com/embed/example"
data-width="100%"
data-height="400">
Click to load video
</div>
Lazy script:
<script
type="text/rx-lazy-script"
data-src="/wp-content/themes/rx-theme/assets/static-js/app.js"
data-rx-load="windowload">
</script>
Lazy CSS:
<link
data-href="/wp-content/themes/rx-theme/assets/static-css/extra.css"
data-rx-load="windowload">
Lazy module:
<div data-rx-module="/wp-content/themes/rx-theme/assets/static-js/comments.js"></div>
Best enqueue name for WordPress
In your functions.php or enqueue file, use:
wp_enqueue_script(
'rx-theme-lazyload',
get_template_directory_uri() . '/assets/static-js/static-chunks-js/chunk-008-lazyload.js',
array(),
'1.0.0',
true
);
For performance, this file should load in the footer with true.