Site icon Hip-Hop Website Design and Development

How to delay display of page elements until enqueued script has injected html

I am enqueuing some javascript that injects SVGs into my page’s DOM (called SVGInjector); it works great except for one thing: there is an undesirable "blink"/shift of elements on my screen where the injected elements are placed upon loading. Apparently this blink has a name I found months after asking the question, it is called the Flash of Unstyled Content (or FOUC) and even has its own Wiki.

The reason for this shift seems pretty clear: the page’s other elements are apparently drawn before the js that injects the SVGs runs, and then, once they are inserted, the page changes to accommodate the added SVGs. I mentioned it here in an answer to my own question that I asked yesterday: https://wordpress.stackexchange.com/a/247315/105196

How to prevent element movement due to late injection?

I am hoping someone can tell me how to prevent this blinking/shift. I believe the solution would entail either 1) delaying the drawing of the page’s other elements until after the SVG is injected via the enqueued js or 2) making the js run earlier, but I’m not sure. Even if this is what I need to do, I’m not sure how to do it.

Some clues, perhaps

There is some info here about running js before loading the whole page, https://stackoverflow.com/questions/2920129/can-i-run-javascript-before-the-whole-page-is-loaded, but it is about web-coding, in general, not WordPress specific. I don’t understand enough about WP and enqueuing to figure out how to adapt these solutions.

The Code

How I enqueued the js in functions.php:

function mytheme_enqueue_front_page_scripts() {
    if( is_front_page() )
    {
        wp_enqueue_script( 'injectSVG', get_stylesheet_directory_uri() . '/js/svg-injector.min.js', array( 'jquery' ), null, true );
        wp_enqueue_script( 'custom', get_stylesheet_directory_uri() . '/js/custom.js', array( 'jquery' ), null, true );

    }
}

add_action( 'wp_enqueue_scripts', 'mytheme_enqueue_front_page_scripts' );

The problem may lie in the SVGinjector script itself, it might specifically have instructions to wait until the other elements are drawn. You can take a look at it here if you think it might be the cause:

The SVGInjector js script (enqueued as ‘injectSVG’)

!function(t,e){"use strict";function r(t){t=t.split(" ");for(var e={},r=t.length,n=[];r--;)e.hasOwnProperty(t[r])||(e[t[r]]=1,n.unshift(t[r]));return n.join(" ")}var n="file:"===t.location.protocol,i=e.implementation.hasFeature("http://www.w3.org/TR/SVG11/feature#BasicStructure","1.1"),o=Array.prototype.forEach||function(t,e){if(void 0===this||null===this||"function"!=typeof t)throw new TypeError;var r,n=this.length>>>0;for(r=0;n>r;++r)r in this&&t.call(e,this[r],r,this)},a={},l=0,s=[],u=[],c={},f=function(t){return t.cloneNode(!0)},p=function(t,e){u[t]=u[t]||[],u[t].push(e)},d=function(t){for(var e=0,r=u[t].length;r>e;e++)!function(e){setTimeout(function(){u[t][e](f(a[t]))},0)}(e)},v=function(e,r){if(void 0!==a[e])a[e]instanceof SVGSVGElement?r(f(a[e])):p(e,r);else{if(!t.XMLHttpRequest)return r("Browser does not support XMLHttpRequest"),!1;a[e]={},p(e,r);var i=new XMLHttpRequest;i.onreadystatechange=function(){if(4===i.readyState){if(404===i.status||null===i.responseXML)return r("Unable to load SVG file: "+e),n&&r("Note: SVG injection ajax calls do not work locally without adjusting security setting in your browser. Or consider using a local webserver."),r(),!1;if(!(200===i.status||n&&0===i.status))return r("There was a problem injecting the SVG: "+i.status+" "+i.statusText),!1;if(i.responseXML instanceof Document)a[e]=i.responseXML.documentElement;else if(DOMParser&&DOMParser instanceof Function){var t;try{var o=new DOMParser;t=o.parseFromString(i.responseText,"text/xml")}catch(l){t=void 0}if(!t||t.getElementsByTagName("parsererror").length)return r("Unable to parse SVG file: "+e),!1;a[e]=t.documentElement}d(e)}},i.open("GET",e),i.overrideMimeType&&i.overrideMimeType("text/xml"),i.send()}},h=function(e,n,a,u){var f=e.getAttribute("data-src")||e.getAttribute("src");if(!/.svg/i.test(f))return void u("Attempted to inject a file with a non-svg extension: "+f);if(!i){var p=e.getAttribute("data-fallback")||e.getAttribute("data-png");return void(p?(e.setAttribute("src",p),u(null)):a?(e.setAttribute("src",a+"/"+f.split("/").pop().replace(".svg",".png")),u(null)):u("This browser does not support SVG and no PNG fallback was defined."))}-1===s.indexOf(e)&&(s.push(e),e.setAttribute("src",""),v(f,function(i){if("undefined"==typeof i||"string"==typeof i)return u(i),!1;var a=e.getAttribute("id");a&&i.setAttribute("id",a);var p=e.getAttribute("title");p&&i.setAttribute("title",p);var d=[].concat(i.getAttribute("class")||[],"injected-svg",e.getAttribute("class")||[]).join(" ");i.setAttribute("class",r(d));var v=e.getAttribute("style");v&&i.setAttribute("style",v);var h=[].filter.call(e.attributes,function(t){return/^data-w[w-]*$/.test(t.name)});o.call(h,function(t){t.name&&t.value&&i.setAttribute(t.name,t.value)});var g,m,b,y,A,w={clipPath:["clip-path"],"color-profile":["color-profile"],cursor:["cursor"],filter:["filter"],linearGradient:["fill","stroke"],marker:["marker","marker-start","marker-mid","marker-end"],mask:["mask"],pattern:["fill","stroke"],radialGradient:["fill","stroke"]};Object.keys(w).forEach(function(t){g=t,b=w[t],m=i.querySelectorAll("defs "+g+"[id]");for(var e=0,r=m.length;r>e;e++){y=m[e].id,A=y+"-"+l;var n;o.call(b,function(t){n=i.querySelectorAll("["+t+'*="'+y+'"]');for(var e=0,r=n.length;r>e;e++)n[e].setAttribute(t,"url(#"+A+")")}),m[e].id=A}}),i.removeAttribute("xmlns:a");for(var x,S,k=i.querySelectorAll("script"),j=[],G=0,T=k.length;T>G;G++)S=k[G].getAttribute("type"),S&&"application/ecmascript"!==S&&"application/javascript"!==S||(x=k[G].innerText||k[G].textContent,j.push(x),i.removeChild(k[G]));if(j.length>0&&("always"===n||"once"===n&&!c[f])){for(var M=0,V=j.length;V>M;M++)new Function(j[M])(t);c[f]=!0}var E=i.querySelectorAll("style");o.call(E,function(t){t.textContent+=""}),e.parentNode.replaceChild(i,e),delete s[s.indexOf(e)],e=null,l++,u(i)}))},g=function(t,e,r){e=e||{};var n=e.evalScripts||"always",i=e.pngFallback||!1,a=e.each;if(void 0!==t.length){var l=0;o.call(t,function(e){h(e,n,i,function(e){a&&"function"==typeof a&&a(e),r&&t.length===++l&&r(l)})})}else t?h(t,n,i,function(e){a&&"function"==typeof a&&a(e),r&&r(1),t=null}):r&&r(0)};"object"==typeof module&&"object"==typeof module.exports?module.exports=exports=g:"function"==typeof define&&define.amd?define(function(){return g}):"object"==typeof t&&(t.SVGInjector=g)}(window,document);

This is the code I have in my custom.js file that calls the script. I have tried placing it directly in the file and within document ready, and in both instances the blinking/shifting still happened:

The SVGInjector js call (inside the enqueued ‘custom’ js file)

// For testing in IE8
if (!window.console){ console = {log: function() {}}; };

// Elements to inject
var mySVGsToInject = document.querySelectorAll('img.inject-me');

// Options
var injectorOptions = {
evalScripts: 'once',
pngFallback: 'assets/png',
each: function (svg) {
    // Callback after each SVG is injected
    if (svg) console.log('SVG injected: ' + svg.getAttribute('id'));
  }
};

 // Trigger the injection
  SVGInjector(mySVGsToInject, injectorOptions, function (totalSVGsInjected) {
 // Callback after all SVGs are injected
 console.log('We injected ' + totalSVGsInjected + ' SVG(s)!');
});

Thanks!