WPSurfer.com

How to Delay The Execution of JavaScript without a plugin

Published on December 8, 2022 | Updated on July 7, 2024

Delaying the execution of a scripts means that it will not run immediately when the program is executed, but will be scheduled to run at a later time.

The first time I heard about delaying execution of JavaScript files was when I was playing with Flying Scripts.

Then WPRocket, Perfmatters, and other plugins added similar features to their plugins.

If you don’t want to use a plugin to delay the execution of JavaScript, you can use the following code snippets.



Flying Scripts Way to Delay Scripts

This is the code that you have to insert into your website footer or header to delay internal and external scripts.

This is the minified version of the script used by Flying Scripts:

<script type="text/javascript" id="flying-scripts">const loadScriptsTimer=setTimeout(loadScripts,2*1000);const userInteractionEvents=["mouseover","keydown","touchstart","touchmove","wheel"];userInteractionEvents.forEach(function(event){window.addEventListener(event,triggerScriptLoader,{passive:!0})});function triggerScriptLoader(){loadScripts();clearTimeout(loadScriptsTimer);userInteractionEvents.forEach(function(event){window.removeEventListener(event,triggerScriptLoader,{passive:!0})})}
function loadScripts(){document.querySelectorAll("script[data-type='lazy']").forEach(function(elem){elem.setAttribute("src",elem.getAttribute("data-src"))})}</script>

These are some examples that show how to wrap the scripts manually:

If you use Optad360, this is how you delay both of those scripts:

<script async data-type="lazy" data-src="//get.optad360.io/sf/66de3669-901a-4936-acaf-aca945c17acb/plugin.min.js"></script>

<script async data-type="lazy" data-src="//cmp.optad360.io/items/c554b8cd-d417-4f47-bb44-858e45a171a7.min.js"></script></head>

If you want to delay a theme script, you have to dequeue the script and add it back using a header inserter plugin or a simple code snippet.

<script data-type="lazy" data-src="https://wpsurfer.com/wp-content/themes/generatepress/assets/js/menu.min.js?ver=3.2.4"></script>

I will try to figure out a way to add the attributes automatically.


Perfmatter’s Script to Delay all External Scripts

Delay Scripts without a Plugin

I think Perfmatters is a plugin that every site must have.

When I have time off, I spend time figuring out how to optimize my site without it.

The options to delay the execution of all scripts was kinda hard to replace using code snippets so I took some time to explore the source code of a site using Perfmatters.

This is the inline minified JavaScript that Perfmatters adds to the footer to delay all external scripts:

<script id="perfmatters-delayed-scripts-js">const pmDelayClick=true;const pmUserInteractions=["keydown","mousedown","mousemove","wheel","touchmove","touchstart","touchend"],pmDelayedScripts={normal:[],defer:[],async:[]},jQueriesArray=[],pmInterceptedClicks=[];var pmDOMLoaded=!1,pmClickTarget="";function pmTriggerDOMListener(){"undefined"!=typeof pmDelayTimer&&clearTimeout(pmDelayTimer),pmUserInteractions.forEach(function(e){window.removeEventListener(e,pmTriggerDOMListener,{passive:!0})}),document.removeEventListener("visibilitychange",pmTriggerDOMListener),"loading"===document.readyState?document.addEventListener("DOMContentLoaded",pmTriggerDelayedScripts):pmTriggerDelayedScripts()}async function pmTriggerDelayedScripts(){pmDelayEventListeners(),pmDelayJQueryReady(),pmProcessDocumentWrite(),pmSortDelayedScripts(),pmPreloadDelayedScripts(),await pmLoadDelayedScripts(pmDelayedScripts.normal),await pmLoadDelayedScripts(pmDelayedScripts.defer),await pmLoadDelayedScripts(pmDelayedScripts.async),await pmTriggerEventListeners(),document.querySelectorAll("link[data-pmdelayedstyle]").forEach(function(e){e.setAttribute("href",e.getAttribute("data-pmdelayedstyle"))}),window.dispatchEvent(new Event("perfmatters-allScriptsLoaded")),pmReplayClicks()}function pmDelayEventListeners(){let e={};function t(t,r){function n(r){return e[t].delayedEvents.indexOf(r)>=0?"perfmatters-"+r:r}e[t]||(e[t]={originalFunctions:{add:t.addEventListener,remove:t.removeEventListener},delayedEvents:[]},t.addEventListener=function(){arguments[0]=n(arguments[0]),e[t].originalFunctions.add.apply(t,arguments)},t.removeEventListener=function(){arguments[0]=n(arguments[0]),e[t].originalFunctions.remove.apply(t,arguments)}),e[t].delayedEvents.push(r)}function r(e,t){let r=e[t];Object.defineProperty(e,t,{get:r||function(){},set:function(r){e["perfmatters"+t]=r}})}t(document,"DOMContentLoaded"),t(window,"DOMContentLoaded"),t(window,"load"),t(window,"pageshow"),t(document,"readystatechange"),r(document,"onreadystatechange"),r(window,"onload"),r(window,"onpageshow")}function pmDelayJQueryReady(){let e=window.jQuery;Object.defineProperty(window,"jQuery",{get:()=>e,set(t){if(t&&t.fn&&!jQueriesArray.includes(t)){t.fn.ready=t.fn.init.prototype.ready=function(e){pmDOMLoaded?e.bind(document)(t):document.addEventListener("perfmatters-DOMContentLoaded",function(){e.bind(document)(t)})};let r=t.fn.on;t.fn.on=t.fn.init.prototype.on=function(){if(this[0]===window){function e(e){return e=(e=(e=e.split(" ")).map(function(e){return"load"===e||0===e.indexOf("load.")?"perfmatters-jquery-load":e})).join(" ")}"string"==typeof arguments[0]||arguments[0]instanceof String?arguments[0]=e(arguments[0]):"object"==typeof arguments[0]&&Object.keys(arguments[0]).forEach(function(t){delete Object.assign(arguments[0],{[e(t)]:arguments[0][t]})[t]})}return r.apply(this,arguments),this},jQueriesArray.push(t)}e=t}})}function pmProcessDocumentWrite(){let e=new Map;document.write=document.writeln=function(t){var r=document.currentScript,n=document.createRange();let a=e.get(r);void 0===a&&(a=r.nextSibling,e.set(r,a));var i=document.createDocumentFragment();n.setStart(i,0),i.appendChild(n.createContextualFragment(t)),r.parentElement.insertBefore(i,a)}}function pmSortDelayedScripts(){document.querySelectorAll("script[type=pmdelayedscript]").forEach(function(e){e.hasAttribute("src")?e.hasAttribute("defer")&&!1!==e.defer?pmDelayedScripts.defer.push(e):e.hasAttribute("async")&&!1!==e.async?pmDelayedScripts.async.push(e):pmDelayedScripts.normal.push(e):pmDelayedScripts.normal.push(e)})}function pmPreloadDelayedScripts(){var e=document.createDocumentFragment();[...pmDelayedScripts.normal,...pmDelayedScripts.defer,...pmDelayedScripts.async].forEach(function(t){var r=t.getAttribute("src");if(r){var n=document.createElement("link");n.href=r,n.rel="preload",n.as="script",e.appendChild(n)}}),document.head.appendChild(e)}async function pmLoadDelayedScripts(e){var t=e.shift();return t?(await pmReplaceScript(t),pmLoadDelayedScripts(e)):Promise.resolve()}async function pmReplaceScript(e){return await pmNextFrame(),new Promise(function(t){let r=document.createElement("script");[...e.attributes].forEach(function(e){let t=e.nodeName;"type"!==t&&("data-type"===t&&(t="type"),r.setAttribute(t,e.nodeValue))}),e.hasAttribute("src")?(r.addEventListener("load",t),r.addEventListener("error",t)):(r.text=e.text,t()),e.parentNode.replaceChild(r,e)})}async function pmTriggerEventListeners(){pmDOMLoaded=!0,await pmNextFrame(),document.dispatchEvent(new Event("perfmatters-DOMContentLoaded")),await pmNextFrame(),window.dispatchEvent(new Event("perfmatters-DOMContentLoaded")),await pmNextFrame(),document.dispatchEvent(new Event("perfmatters-readystatechange")),await pmNextFrame(),document.perfmattersonreadystatechange&&document.perfmattersonreadystatechange(),await pmNextFrame(),window.dispatchEvent(new Event("perfmatters-load")),await pmNextFrame(),window.perfmattersonload&&window.perfmattersonload(),await pmNextFrame(),jQueriesArray.forEach(function(e){e(window).trigger("perfmatters-jquery-load")});let e=new Event("perfmatters-pageshow");e.persisted=window.pmPersisted,window.dispatchEvent(e),await pmNextFrame(),window.perfmattersonpageshow&&window.perfmattersonpageshow({persisted:window.pmPersisted})}async function pmNextFrame(){return new Promise(function(e){requestAnimationFrame(e)})}function pmClickHandler(e){e.target.removeEventListener("click",pmClickHandler),pmRenameDOMAttribute(e.target,"pm-onclick","onclick"),pmInterceptedClicks.push(e),e.preventDefault(),e.stopPropagation(),e.stopImmediatePropagation()}function pmReplayClicks(){window.removeEventListener("touchstart",pmTouchStartHandler,{passive:!0}),window.removeEventListener("mousedown",pmTouchStartHandler),pmInterceptedClicks.forEach(e=>{e.target.outerHTML===pmClickTarget&&e.target.dispatchEvent(new MouseEvent("click",{view:e.view,bubbles:!0,cancelable:!0}))})}function pmTouchStartHandler(e){"HTML"!==e.target.tagName&&(pmClickTarget||(pmClickTarget=e.target.outerHTML),window.addEventListener("touchend",pmTouchEndHandler),window.addEventListener("mouseup",pmTouchEndHandler),window.addEventListener("touchmove",pmTouchMoveHandler,{passive:!0}),window.addEventListener("mousemove",pmTouchMoveHandler),e.target.addEventListener("click",pmClickHandler),pmRenameDOMAttribute(e.target,"onclick","pm-onclick"))}function pmTouchMoveHandler(e){window.removeEventListener("touchend",pmTouchEndHandler),window.removeEventListener("mouseup",pmTouchEndHandler),window.removeEventListener("touchmove",pmTouchMoveHandler,{passive:!0}),window.removeEventListener("mousemove",pmTouchMoveHandler),e.target.removeEventListener("click",pmClickHandler),pmRenameDOMAttribute(e.target,"pm-onclick","onclick")}function pmTouchEndHandler(e){window.removeEventListener("touchend",pmTouchEndHandler),window.removeEventListener("mouseup",pmTouchEndHandler),window.removeEventListener("touchmove",pmTouchMoveHandler,{passive:!0}),window.removeEventListener("mousemove",pmTouchMoveHandler)}function pmRenameDOMAttribute(e,t,r){e.hasAttribute&&e.hasAttribute(t)&&(event.target.setAttribute(r,event.target.getAttribute(t)),event.target.removeAttribute(t))}window.addEventListener("pageshow",e=>{window.pmPersisted=e.persisted}),pmUserInteractions.forEach(function(e){window.addEventListener(e,pmTriggerDOMListener,{passive:!0})}),pmDelayClick&&(window.addEventListener("touchstart",pmTouchStartHandler,{passive:!0}),window.addEventListener("mousedown",pmTouchStartHandler)),document.addEventListener("visibilitychange",pmTriggerDOMListener);</script>

You can add the script using several methods:

  • Many plugins have the option to add scripts and styles to the header and footer. You can copy the code and add it using one of those plugins.
  • You can use a function to inject scripts to the footer.
  • You can use a mu-plugin for that.

In case you want to take the PHP snippet solution, you can use this mu-plugin:

<?php
/*
Plugin Name: Custom Footer Inline Script
Description: Adds an inline script to the footer.
Author: TicoLibre
Version: 1.0
*/

function insert_inline_script_to_footer() {
    echo "<script>
        // Your custom inline JavaScript code here
    </script>";
}
add_action('wp_footer', 'insert_inline_script_to_footer');

In case you are wondering why you should add that freaking long script to the footer.

I think that the developers behind Perfmatters didn’t want to create one more requests and affect your speed.

In case you want an external script,

  1. Remove the script tags at the beginning and at the end of the code.
  2. Create a file and name it “whatever you want.js”, I named mine “delay.js”.
  3. Use a file manager and go to your wordpress installation.
  4. Create a folder called “files” or “js” inside the “uploads folder”
  5. Copy your file into that folder.
  6. Use the following code to output the js into the head of your pages and posts.
<?php
/*
Plugin Name: Header Scripts
Description: Add  Scripts to the headers
Version: 1.0
Author: TicoLibre
*/


//Insert Styles and Scripts in the Header
function insert_custom_header_files() {
    $domain = home_url();

    // Enqueue script in the header
   wp_enqueue_script( 'delay-js', $domain . '/wp-content/uploads/files/delay.js', array(), false, false );
}
add_action( 'wp_enqueue_scripts', 'insert_custom_header_files' );

Using this second method will reduce the size of the HTML but it will add a request to your site.


How to Add the Attribute Manually

Adding the previous code to your footer is not enough to lazy load all scripts, so you need to add the type=”pmdelayedscript” to your scripts so they are lazy loaded

You can do it manually if you don’t have that many scripts.

This is how you delay the execution of the Google Analytics Scripts:

<script async src="https://www.googletagmanager.com/gtag/js?id=G-XXXXXXXXXX" type="pmdelayedscript"></script>
<script>
  window.dataLayer = window.dataLayer || [];
  function gtag(){dataLayer.push(arguments);}
  gtag('js', new Date());

  gtag('config', 'G-XXXXXXXXXX');
</script>

Mu-Plugin to Add the Attribute to External Links

I bet that you don’t want to add type=”pmdelayedscript” to all scripts manually.

This PHP snippet will add type=”pmdelayedscript to all external scripts

<?php
/*
Plugin Name: Script Delayer
Description: Add Attribute to External Scripts
Version: 1.0
Author: TicoLibre
*/


// This will add the pmdelayedscript to all external scripts

function add_delayed_script_type($tag, $handle, $src) {
    // Check if the current request is not in the admin area
    if ( ! is_admin() ) {
        // Define an array of keywords to exclude
        $exclude_keywords = array( 'menu.min.js', 'delay.js' );

        // Check if the script source contains any of the excluded keywords
        foreach ($exclude_keywords as $keyword) {
            if (strpos($src, $keyword) !== false) {
                // If the source contains an excluded keyword, return the original script tag
                return $tag;
            }
        }

        // If the script is not excluded, add the type attribute with the value pmdelayedscript
        $tag = str_replace('<script', '<script type="pmdelayedscript"', $tag);
    }
    return $tag;
}
add_filter('script_loader_tag', 'add_delayed_script_type', 10, 3);

The code won’t add the type=”pmdelayedscript to:

#1admin scripts
#2inline scripts
#4scripts you exclude

So you won’t break anything on your WordPress dashboard and you won’t add the attribute to inline scripts.


What Script to Use?

I am not a coder so I don’t really know why Flying Scripts used those few lines of code and Perfmatters approach requires so many lines of code to accomplish the same thing.

I have used both ways to delay scripts and the one by Perfmatters helped me delay H5P scripts without breaking the plugin.

Try the one by Flying Scripts First and it delays what you wanted to delay. Don’t bother using the one by Perfmatters.

If you don’t want to complicate your lives dealing with this stuff, consider using Flying Scripts, FlyingPress or Perfmatters to speed up your site


Manuel Campos

Manuel Campos

I am José Manuel. I am writing about things I know and things that I am learning about WordPress. I hope you find the content of this blog useful.