My Web Loading Strategy.

Michael Westwood
8 min readJun 28, 2016

--

Right, so it’s best to defer the loading of your main stylesheet so it’s not a blocking resource. Unblocking your styles means the user gets to the important stuff (content) faster, without having to wait for the stylesheet to load.

But we don’t want to have a Flash Of Unstyled Content (FOUC), so we should inline the styles necessary to render our above-the-fold content nicely. This is called using critical CSS.

Due to cascading issues, you can sometimes get conflicts after your main styles have loaded by having the same rules both inlined and in an external stylesheet, so you want to remove your critical CSS after the main styles have been applied to avoid this.

Note that I said ‘applied’, not ‘loaded’. This is because there is a slight but very visible delay between the styles being loaded and being applied, which can cause a FOUT if your critical CSS is removed too early.

Nightmare.

So let’s sort it out.

Deferring your CSS

The Filament Group developed a cool little script that defers your CSS for you. It’s called loadCSS and it’s brilliant.

You can include the script any way you like, but I’m a Grunt and Bower man, so I’ve got loadCSS as a dependency in all my projects, and my Grunt Uglify task copies it over to a place where I can include it in my Craft templates (I’m also a Craft man) as if it’s any old partial.

{% include '_partials/inline/loadcss.js' %}

See my Craft Boilerplate’s bower.json, uglify.js, and footer.html if you’re interested in a working example.

Once you’ve got the script included, you just need to loadCSS your main styles instead of using a <style> tag.

loadCSS(“/assets/css/main.css”);

Inlining your critical CSS

Now how you generate your critical CSS is totally up to you, but I use the Filament Group’s grunt-criticalcss. In my Grunt task config I generate one critical CSS file per template that I care about, and a generic critical.css as well.

module.exports = function(grunt) {grunt.config('criticalcss', {
default: {
options: {
url: 'http://du.st',
filename: 'public/assets/css/main.css',
width: 1040,
height: 900,
outputfile: 'craft/templates/_partials/inline/critical.css'
}
},
'editorial': {
options: {
url: 'http://du.st/articles/deichniur-fear/',
filename: 'public/assets/css/main.css',
width: 1040,
height: 900,
outputfile: 'craft/templates/_partials/inline/editorial.css'
}
}
});
grunt.loadNpmTasks('grunt-criticalcss');};

These get churned out into the same partials folder as my loadCSS did, so I can easily include it in the template.

Now in my header, I’ve got a style tag with an ID “js-critical-css”. This is so it can be referenced later when I want to delete it; but you can call this anything you like.

As well as including one of my critical CSS files, I can also specify any other FOUC-avoiding CSS that I need. For example we at Dust used to have a flash of massive inline SVGs while the styles weren’t loaded yet, which was obviously not cool, so I added a pre-load rule for SVGs.

Now we need to include the critical CSS file that matches which template we’re on, if it exists, and if not revert to our standard critical.css. Remember I’m using Craft here, so adapt for your setup.

<head>    ...    <style id="js-critical-css">
svg { opacity: 0; }
{% include ['_partials/inline/' ~ (entry is defined ? entry.section.template|replace('/', '-') : 'critical') ~ '.css', '_partials/inline/critical.css'] ignore missing %} </style> ...</head>

Wicked. So we’ve got deferred main styles, with inlined critical CSS. Now step 3:

Removing your critical CSS

As said before, sometimes you can get cascading conflicts by having inline styles as well as external styles, so let’s remove the redundant critical CSS.

LoadCSS comes with another little script called onloadCSS, which allows you to write a callback for when a stylesheet has been loaded.

So let’s make sure we’re loading in both scripts…

{% include ‘_partials/inline/loadcss.js’ %}
{% include ‘_partials/inline/onloadcss.js’ %}

…save our main styles loadCSS to a variable…

var mainStyles = loadCSS(“/assets/css/main.css”);

…and then register a function to the onloadCSS callback…

onloadCSS(mainStyles, function() {    ...});

Cool, so now when the main styles have been loaded, that function will fire.

However, as mentioned before, there is a slight delay between the styles being loaded and the styles being applied, so we need a way to check if the styles have been applied before removing the critical CSS.

What we’re going to do is create an invisible element with background colour of pink (specified in our main, non-critical styles), then keep checking if it’s pink or not, and when it is we can safely remove the critical CSS.

So in your footer, create an invisible element.

<span class="visuallyhidden loadcss-check js-loadcss-check">main.css loaded.</span>

The visuallyhidden class is an accessibility friendly way of hiding content, which I also include on every project.

Now style up that element to be pink.

.loadcss-check { background-color: rgb(255, 192, 203); }

I did originally just write ‘pink’, but the way the checker checks it returns the RGB, so I felt this was safer.

Now in our loadCSS callback, create an interval and keep checking if .js-loadcss-check has a pink background colour or not, and remove the critical CSS when it does.

onloadCSS(mainStyles, function() {  var elem = document.getElementsByClassName(‘js-loadcss-check’)[0];
var loadcsstimer = setInterval(function() {
if ( window.getComputedStyle(elem)[‘backgroundColor’] == ‘rgb(255, 192, 203)’ ) { var element = document.getElementById(“js-critical-css”);
element.parentNode.removeChild(element);
window.clearInterval(loadcsstimer);
} }, 100);});

There we have it. Deferred main styles, inlined critical styles, no conflicts. Happy days.

Fonts

It’s also best to unblock the loading of your fonts, but this can cause a Flash Of Unstyled Text (FOUT). This is basically going to happen unless you want a Flash Of Invisible Text (FOIT) instead, but we can do cool stuff to make it as seamless and pretty as possible.

We can do this by specifying different properties for our fallback fonts, so they look at close as possible to the real webfonts until they load. We’re talking things like adjusting letter spacing and font weight. Small alterations in these properties can prevent a massive janky reflow when your webfonts are loaded.

Right, for loading fonts we’ll use a mix of the loadCSS used above and Bram Stein’s Font Face Observer. As before, I’m including fontfaceobserver as a Bower dependency, and uglifying it to my partials folder, then including it in my template

{% include '_partials/inline/fontfaceobserver.js' %}

Using loadCSS like we did for our main styles, we can defer the loading of our font file.

var fontStyles = loadCSS("/assets/css/fonts.css");

Here, fonts.css contains all the @font-face declarations you need. You could also use a fonts.com or a Google fonts url or whatever you want.

Now we need to write our onloadCSS callback we want to trigger when fonts.css has loaded. In that callback we’re using the Font Face Observer to trigger another callback when all the fonts have loaded.

When all of our fonts have loaded, we want to add a class of ‘webfonts-loaded’ to the html element, which we’ll use to style up our fallbacks fonts.

onloadCSS(fontStyles, function() {    var observer1 = new FontFaceObserver(“font-one”);
var observer2 = new FontFaceObserver(“font-two”);
var observer3 = new FontFaceObserver(“font-three”);
var observer4 = new FontFaceObserver(“font-four”);
Promise.all([observer1.check(), observer2.check(), observer3.check(), observer4.check()]).then(function () { document.documentElement.className = document.documentElement.className.replace( /(?:^|\s)no-webfonts(?!\S)/g , ‘’ );
document.documentElement.className += “ webfonts-loaded”;
});
});

Cool, so now we’ve got a parent class which we can style up our proper webfonts and our fallback fonts with.

How you want to go about styling your .no-webfonts and .webfonts-loaded declarations is up you, whether you’re using CSS or SASS it totally depends on your setup.

Images

Loading all of your images while your user is first trying to access your page is a bit of a problem. That’s a lot of kb all at once. So we want to defer the loading of images until they’re needed.

For this I recommend Alexander Farkas’ Lazy Sizes script. You basically just add the ‘lazyload’ class to an image and it’ll only load when it’s near the viewport.

Together with using appropriately sizes responsive images, as detailed out in Using Responsive Images with Craft, we’ve got a pretty decent loading setup for images right there.

However, when the images do load there can be a janky reflow as we’ve gone from a box with no height to an actual image. This is uncool.

Making a placeholder box

To avoid a reflow we can just specify the width and height of the image container and we’re good, right?

Wrong.

When the browser squidges and stretches depending on screen size, those images are going to have different widths and heights. However, assuming they’re going to maintain the same aspect ratio we can use percentages to give us a placeholder box.

Taking inspiration from the <=IE11 SVG Height Padding Hack, we can give the placeholder box a height based on the width of the box and the ratio of the image. We’ll do this as top padding.

padding-top: ( image.height / image.width * 100%)

We don’t need no brackets; remember BODMAS?

Now if you absolutely position your image within this relatively positioned container you’re laughing. I also put an awful little drop shadow on for fun.

Cool, so now your images are also loaded as and when they’re needed, and it doesn’t cause a jarring reflow when they do show up.

Scripts

Finally, Javascript.

The thing about deferring the load of your JS is that there’s no consistent way to do it while also preserving the order the scripts are loaded. You don’t necessarily need jQuery to render your initial content, but you also definitely don’t want something jQuery dependant to load before jQuery has turned up.

Taking heavy inspiration and just plain old copying from this HTML5 Rocks article, we’ve got this snippet:

<script>
!function(e,t,r){function n(){for(;d[0]&&"loaded"==d[0][f];)c=d.shift(),c[o]=!i.parentNode.insertBefore(c,i)}for(var s,a,c,d=[],i=e.scripts[0],o="onreadystatechange",f="readyState";s=r.shift();)a=e.createElement(t),"async"in i?(a.async=!1,e.head.appendChild(a)):i[f]?(d.push(a),a[o]=n):e.write("<"+t+' src="'+s+'" defer></'+t+">"),a.src=s}(document,"script",["/assets/js/first.js", "/assets/js/second.js"])
</script>

Read the article for details, but just trust me that this is an order preserving, non-render blocking way to load Javascript files. So in the above, first.js will load first, then second.js will come after.

Now, you might have everything in on javascript file which would mean order doesn’t really matter, but you might also have some pages which need some specific script that you don’t want on every page, but depend on the main scripts having already loaded. Hence the above.

The way I’ve done this in my Craft templates is I add to an array called pageJsFiles as and when they’re needed, then as that snippet is in the footer, it can at that point loop through the array and load them in order.

  <script>
!function(e,t,r){function n(){for(;d[0]&&"loaded"==d[0][f];)c=d.shift(),c[o]=!i.parentNode.insertBefore(c,i)}for(var s,a,c,d=[],i=e.scripts[0],o="onreadystatechange",f="readyState";s=r.shift();)a=e.createElement(t),"async"in i?(a.async=!1,e.head.appendChild(a)):i[f]?(d.push(a),a[o]=n):e.write("<"+t+' src="'+s+'" defer></'+t+">"),a.src=s}(document,"script",[
"/assets/js/main.js"{% spaceless %}
{% if (pageJsFiles is defined) and (pageJsFiles is not empty) %},
{% for file in pageJsFiles %}
"/assets/js/{{ file }}"{% if not loop.last %}, {% endif %}
{% endfor %}
{% endif %}
{% endspaceless %}])
</script>

Boom.

So yeah.

Styles are deferred, with criticalcss in place until it’s deleted after the main styles have rendered.

Fonts are deferred with minimal janky reflow due to our ability to style on .webfonts-loaded and .no-webfonts.

Images are lazyloaded, avoiding reflow using the padding-top technique.

Scripts are deferred and order preserved in a browser-consistent way.

--

--