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.length0;for(r=0;nr;++r)r in thist.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;re;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),nr(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||n0===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(DOMParserDOMParser 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.overrideMimeTypei.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);ai.setAttribute(id,a);var p=e.getAttribute(title);pi.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);vi.setAttribute(style,v);var h=[].filter.call(e.attributes,function(t){return/^data-\w[\w\-]*$/.test(t.name)});o.call(h,function(t){t.namet.valuei.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;re;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;re;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;TG;G++)S=k[G].getAttribute(type),Sapplication/ecmascript!==Sapplication/javascript!==S||(x=k[G].innerText||k[G].textContent,j.push(x),i.removeChild(k[G]));if(j.length0(always===n||once===n!c[f])){for(var M=0,V=j.length;VM;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){afunction==typeof aa(e),rt.length===++lr(l)})})}else t?h(t,n,i,function(e){afunction==typeof aa(e),rr(1),t=null}):rr(0)};object==typeof moduleobject==typeof module.exports?module.exports=exports=g:function==typeof definedefine.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!

Topic svg wp-enqueue-script html Wordpress javascript

Category Web


The real solution is to not use the library, but insert the SVG into the content with a shortcode, or with a widget.

SVGInjector is AJAX based, therefor it doesn't matter where and how you place its core JS it will need another round trip to the server to fetch the SVG itself, which means that probably always the drawing of the SVG will happen after some part of the page had been already drawn and a reflow is needed.

One way to mitigate the impact of the reflow (or in other words, avoid it being noticeable by humans) is to pre-allocate enough space on the page for the element that will be the target of insertion. For example if you know that the SVG is going to result in an 40x40 image, just have a 40x40 div in your HTML in the place were you want the insertion to happen, and insert the SVG there. This strategy works well with images, but SVG is a much more complex beast so YMMV.


Have you considered using lazy loading/ajax methods of loading elements? You could use that in conjunction with css and JS to produce the desired results. EG you load the elements hidden, and then use the onload event (or whatever other bind). Yet another method would be to use a simple pre-loading script to preload the images(make the Dom request) before actually loading them in. Something like this:

$(image_object_or_element_here).each(function(){ //preloader

      $('<img/>')[0].src = image_source_here;

    });

That said, blocking the entire DOM load while you wait for a script to load is typically bad practice/poor performance and a bad UI. Also, think of scenarios where you end up with script errors that prevent your script from running. Users will be shown nothing, with no indication as to why they arent getting content.


So the problem with injection is going to be that the javascript is only going to run after everything has loaded, which means that the, and it needs to make the all the image requests to the server again. Unfortunately, you probably can't make the requests any earlier than they're already being made, since they're front side requests instead of back-end on-load requests...

So you have a few options, either attempt to fade them in, or just make sure that there's enough space where the images were to begin with, or potentially both.

I wasn't sure if you wanted me to elaborate more, which is why I didn't make this an answer.

About

Geeks Mental is a community that publishes articles and tutorials about Web, Android, Data Science, new techniques and Linux security.