0

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!

  • What does your actual injectSVG JS code look like? – socki03 Nov 25 '16 at 16:28
  • The function I had pasted was it, but I also just updated the question and added my call of the function, I hope that helps. Please let me know if you need any other clarification! – CoderScissorhands Nov 25 '16 at 16:48
0

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.

0

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.

  • Thanks for giving this a shot. Since the SVG often occurs within an element with a fixed height, I might be able to do this. Can you tell me what is the easiest way to lazyLoad elements in WP? I can find lazyLoaders for media and images, but none that seem to clearly allow you to specify elements via selectors. Also could you please explain the pre-loading script a bit more...is it jQuery? Where would i put it, in the template or would I enqueue it? – CoderScissorhands Nov 25 '16 at 18:39
  • Ready made solutions might not be applicable to your situation. i know there are heaps of lazy loaders out there, but in this case you might be best rolling your own script. It's a simple matter of writing a bit of jquery/js to make an ajax call. Bind an action to the wp_ajax hooks and go from there. – Hybrid Web Dev Nov 25 '16 at 18:44
  • Thank you, I asked this question because I am new to this. Can you provide more clarification about a)writing the jquery (i have very basic knowledge) 2) making the ajax call and c) binding the action? :) – CoderScissorhands Nov 25 '16 at 18:46
  • For an ajax call you want to look at 2 hooks: wp_ajax_{your_action_here} // this one is for cases where uses is logged in and wp_ajax_nopriv_{your_action_here} // this one is for cases where users are NOT logged in. Then in your jquery, you'd reference your_action_here in your ajax call. Dont forget, when utilizing WP's ajax, make sure you use an exit; statement at the end of the call, otherwise you'll end up with a 0 at the end of your content. I suggest taking a look at WP's codex for more info – Hybrid Web Dev Nov 25 '16 at 22:29
  • I'm sorry I don't understand how to implement your suggestion. Could you please provide a full example, and preferably in your answer, with the relevant code included. These references are probably great for someone who is experienced in WP I'm a beginner. I wish I were more proficient, but if I were I wouldn't have asked this question. – CoderScissorhands Nov 25 '16 at 22:35
0

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.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.