Skip to content

Instantly share code, notes, and snippets.

@dreamyguy
Last active July 31, 2017 08:01
Show Gist options
  • Select an option

  • Save dreamyguy/cc0d86e034a072605878d408b186e75b to your computer and use it in GitHub Desktop.

Select an option

Save dreamyguy/cc0d86e034a072605878d408b186e75b to your computer and use it in GitHub Desktop.
Only lazy-load 3rd party script tag once the placeholder that interacts with it is visible on viewport
/* ===================================
* Only lazy-load 3rd party script tag once the placeholder that interacts with it is visible on viewport.
*
* Copyright (c) 2017, Wallace Sidhrée
* MIT License
====================================== */
var lazyScript = function(elId, scriptUrl, threshold) {
// polyfill requestAnimationFrame for ie9
window.requestAnimationFrame = window.requestAnimationFrame || function(f) { return setTimeout(f, 1000/60); };
// polyfill for custom events
(function () {
function CustomEvent (event, params) {
params = params || { bubbles: false, cancelable: false, detail: undefined };
var evt = document.createEvent( 'CustomEvent' );
evt.initCustomEvent(event, params.bubbles, params.cancelable, params.detail);
return evt;
}
CustomEvent.prototype = window.Event.prototype;
window.CustomEvent = CustomEvent;
})();
// show console messages only when supported and if debug mode is set as true
var logMessage = function(message){
if(typeof parent.window.console === 'object') {
parent.window.console.log(message);
}
};
// get element
var getElement = function(id) {
return document.getElementById(id);
};
// get dimensions
var windowHeight = function() {
return window.innerHeight || document.documentElement.clientHeight;
};
var windowWidth = function() {
return window.innerWidth || document.documentElement.clientWidth;
};
var notCalled = true;
var checkViewport;
var scrollingHandler;
// fire when ready
var fireWhenReady = function(el) {
var event = new CustomEvent('inViewPort');
checkViewport = function() {
var rect = el.getBoundingClientRect();
if (
rect.top >= 0 &&
rect.left >= 0 &&
rect.bottom <= windowHeight() &&
rect.right <= windowWidth()
) {
el.dispatchEvent(event);
notCalled = false;
}
};
scrollingHandler = function() {
return notCalled && requestAnimationFrame(checkViewport);
};
window.addEventListener('scroll', scrollingHandler);
};
// inject scripts through script tags
var injectJS = function(url, callback) {
// inject url
var injs = document.createElement('script');
injs.src = url;
injs.type = 'text/javascript';
injs.async = true;
// output message on load callback
var success = logMessage('[injectJS] ' + url + ' has loaded as a script!');
if (injs.addEventListener) {
injs.addEventListener('load', success, false);
}
document.body.appendChild(injs);
var doCallback = function() {
return callback;
}();
doCallback();
};
var containerScript = getElement(elId);
// set event and what it will execute
if (containerScript) {
containerScript.addEventListener('inViewPort', function () {
injectJS(scriptUrl, function () {
logMessage('[lazyScript] ' + elId + ' was lazy-loaded!');
window.removeEventListener('scroll', scrollingHandler);
});
});
// trigger event when it's ready and visible on viewport
// 'threshold' in 'ms' for the lazy-loading's agressiveness
fireWhenReady(containerScript, threshold);
}
};
// if you prefer to call `lazyScript` straight away by having it on the same file,
// you'll need some form of reliable 'on doc ready' check (jQuery is used below)
$(function() {
lazyScript('strossle', '//widgets.sprinklecontent.com/v2/sprinkle.js', 100);
});

Use scenario

You need to lazy-load a script tag that modifies the DOM through a placeholder <div>. By using this script, the request is made and the content rendered inside the placeholder only when the placeholder becomes visible in the viewport.

Useful for 3rd party scripts that have two elements:

  • A div that receives content through a script.
  • An async script tag that request the script necessary to render content on the placeholder.

Examples:

<div data-spklw-widget="widget-7777777777777"></div>
<script type="text/javascript" async src="//widgets.sprinklecontent.com/v2/sprinkle.js"></script>

The snippet above becomes:

Syncronous

<div id="strossle" data-spklw-widget="widget-7777777777777"></div>
<script>
  lazyScript('strossle', '//widgets.sprinklecontent.com/v2/sprinkle.js', 100);
</script>

No need for 'on doc ready' check as script is loaded right after the element it depends on, but this assumes that lazyScript was included synchronously and is therefore defined.

Asyncronous

<div id="strossle" data-spklw-widget="widget-7777777777777"></div>
<script>
  (function () {
    var intervalCounterStrossle = 0;
    if (window.lazyScript) {
      window.lazyScript('strossle', '//widgets.sprinklecontent.com/v2/sprinkle.js', 100);
    } else {
      console.log('[lazyScript]: "lazyScript" is undefined at this point, so we cannot load Strossle. Attempting to load it again...');
      var loadStrossleInterval = window.setInterval(function () {
        intervalCounterStrossle++;
        if (window.lazyScript) {
          window.clearInterval(loadStrossleInterval);
          window.lazyScript('strossle', '//widgets.sprinklecontent.com/v2/sprinkle.js', 100);
        } else if (intervalCounterStrossle > 50) {
          window.clearInterval(loadStrossleInterval);
          console.log('[lazyScript]: Strossle timed-out after attempting to load it through "lazyScript", which is still undefined');
        }
      }, 100);
    }
  })();
</script>

No need for 'on doc ready' check as script is loaded right after the element it depends on.

If lazyScript was included asynchronously and isn't defined by the time it's called, we will continue to attempt loading it for another 5 secs (in this case). It will eventually fail after the timeout, and a console error will explain what just happened.

Though unrelated to the challenge at hand, the principle applied above could be used to determine 'on element ready'. However an inline script tag right after the element (like it was done in the examples above) enables us to skip checking for the element's existence, since it's guaranteed to be there.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment