How to load css async the proper way? - javascript

As a test to learn more about optimizing websites, I've been trying to get my site to have the perfect score on PageSpeed Insights. Everything is going great so far except for the CSS delivery.
I did manage to get a perfect result by using the preload tag, but for some reason that didn't load for Firefox. So I tried using other solutions.
I switched then over to this:
<link rel="stylesheet" href="~/css/site.min.css" media="none" onload="if(media !== 'all')media='all';">
<noscript><link rel="stylesheet" href="~/css/site.min.css"></noscript>
Which seemed very effective but pagespeed doesn't pick it up so it only gives me a 85 score rating.
Same happened when I used <link rel="stylesheet" href="~/css/site.min.css" media="none"/> in head and <link rel="stylesheet" href="~/css/site.min.css"> at the end of my body.
Then I tried loading my css with Javascript like this:
<noscript id="deferred-styles">
<link rel="stylesheet" href="~/css/site.min.css"/>
</noscript>
<script>
var loadDeferredStyles = function() {
var addStylesNode = document.getElementById("deferred-styles");
var replacement = document.createElement("div");
replacement.innerHTML = addStylesNode.textContent;
document.body.appendChild(replacement);
addStylesNode.parentElement.removeChild(addStylesNode);
};
var raf = requestAnimationFrame ||
mozRequestAnimationFrame ||
webkitRequestAnimationFrame ||
msRequestAnimationFrame;
if (raf) raf(function() { window.setTimeout(loadDeferredStyles, 0); });
else window.addEventListener('load', loadDeferredStyles);
</script>
But this also had the same effect! Pagespeed didn't pick it up and gave me a bad rating. Any reason why for this? Because the above code can be found on their site!

according to my experience with google pagespeed, for optimizing css delivery you have to write inline css code of first fold of your webpage.So that it can be painted fast, and rest of the css you can write in the external file. Minimize and concat css files used in the page. Refer this link for more info optimize css delivery

This is what I use just before the </body> and works fine.
<script type="text/javascript">
function downloadAtOnload() {
// Dynamically load CSS
var ls = document.createElement("link");
ls.rel="stylesheet";
ls.href= "css/my-css-file.css";
document.getElementsByTagName("head")[0].appendChild(ls);
}
if (window.addEventListener) window.addEventListener("load", downloadAtOnload, false);
else if (window.attachEvent) window.attachEvent("onload", downloadAtOnload);
else window.onload = downloadAtOnload;
</script>

Related

How to load CSS Asynchronously without Inline Scripts (comply with CSP)

I want to implement Asynchronously loading CSS files for faster performance. However I want security too, so I want my site to have CSP.
<link rel="stylesheet" media="print" class="AOcssLoad" .... onload="this.onload=null;this.media='all';" />
Without going into details it wants me to avoid things like onload and many other JS that are part of elements.
I want it to look like this
<link rel="stylesheet" media="print" class="AOcssLoad" href="" />
Please suggest a way to achieve Asynchronous CSS files without inline JS as used above.
We can use inline <script> tags or seperate JS files.
I tried the below code as an inline JS.. Below is the HTML for the JS,
<script nonce="" type="text/javascript" data-exclude="true">
var Script = document.getElementsByClassName("AOcssLoad");
for (var i = 0 ; i < Script.length; i++) {
this.className += " Loading";
Script[i].addEventListener("load", function({
this.onload=null;this.media="all";
this.className += " OnLoad";
});
}
</script>
While it works, it's highly unreliable.
I cannot comprehend the problem, but I shall say it works only 50% of the times, sometimes just reloading the page can solve/break the problem, with no apparent change to css/html/cache as such.
Please help me improve on this, or build a better approach for it.
Edit:
As Suggested in Comments I tried different methods, including the links to other resources from GitHub.
Those methods are unreliable I would say they work less than 50% of times.
However I tried to use jQuery(document).ready() and add media="all" to all the css files, but that increases TBT (Total Blocking Time) thus impacting my site performance
Edit 2:
As many of you are repeatedly pointing out in answers, using DOMcontentLoaded and many other ways can help in doing what I want to implemnt.
However these approaches all contribute to significant increase in TBT (Total Blocking Time).
An approach that doesn't harm the TBT would be appreciated.
I would suggest using fetch().then() and injecting it as a style element:
var stylesheetURLS = ["style.css", "style2.css"]
stylesheetURLS.forEach(url => {
fetch(url).then(response => response.text()).then(stylesheet => {
var styleElement = document.createElement("style");
styleElement.textContent = stylesheet;
document.head.append(styleElement);
});
});
I am not sure if fetch is slow, but I wouldn't be surprised if it is.
Alternative to fetch: XMLHttpRequest
var stylesheetURLS = ["style.css", "style2.css"];
stylesheetURLS.forEach(url => {
var request = new XMLHttpRequest();
request.open("GET", url);
request.send();
request.onload = function() {
var styleElement = document.createElement("style");
styleElement.textContent = request.responseText || request.response;
document.head.append(styleElement);
}
});
I'm again not sure if this is any faster than fetch
I have tested downloading a css file that takes over 6 seconds to download and I can confirm that downloading does not contribute to the TBT. As stated by google TBT is the time that the browser is unavailable for user input for example when the user clicks a button or scrolls the browser will not react. A long TBT is often caused by the main thread being busy because it has too much work to do. I think processing the CSS (applying all the rules to the html) is what your TBT increases because downloading files is already done in the background (async) and won't be the cause of a long TBT time.
Below an example that the TBT isn't increased when downloading a large file:
TBT:
As you can see the downloading takes more than 6 seconds but doesn't add up to the TBT:
You could use a vanilla JS function based on jQuery .ready():
document.addEventListener("DOMContentLoaded", () => {
document.querySelectorAll(".AOcssLoad").forEach(el => {
el.media = "all"
console.log(`Loaded: ${el.href}`)
})
});
<link rel="stylesheet" media="print" class="AOcssLoad" href="https://cdn.jsdelivr.net/npm/bootstrap#5.1.1/dist/css/bootstrap.min.css" />
<link rel="stylesheet" media="print" class="AOcssLoad" href="https://cdn.jsdelivr.net/npm/bootstrap#5.1.1/dist/css/bootstrap-utilities.min.css" />
<link rel="stylesheet" media="print" class="AOcssLoad" href="https://cdn.jsdelivr.net/npm/bootstrap#5.1.1/dist/css/bootstrap-reboot.min.css" />
<link rel="stylesheet" media="print" class="AOcssLoad" href="https://cdn.jsdelivr.net/npm/bootstrap#5.1.1/dist/css/bootstrap-grid.min.css" />
<h1>Hello world!</h1>
However, no matter which solution you choose, be aware you will have to deal with Flash of Unstyled Content (FOUC). Consider other approachs for managing your stylesheet files.
why not inject it with
<script>
document.write("<link rel=\"stylesheet\" media=\"print\" class=\"AOcssLoad" href=\"\" />
");
</script>
and place the <script> tags right above the place where you want the <link> tag to be.
or just
<link rel="stylesheet" media="print" class="AOcssLoad" href="" />
nothing happens all except for the css will load asynchronously and you can have csp
Your script is just wrong, and it will just not work, not even 50% of the time.
var Script = document.getElementsByClassName("AOcssLoad");
for (var i = 0 ; i < Script.length; i++) {
this.className += " Loading"; // here `this` is `window`
Script[i].addEventListener("load", function({ // <-- this is an Object
this.onload=null;this.media="all"; // <-- this is a syntax error
this.className += " OnLoad";
});
}
Here is a rewrite of what you probably meant to write, which also includes a check to see if the link got loaded before your script ran, just in case (e.g cache).
const links = document.getElementsByClassName("AOcssLoad");
for (const link of links) {
link.className += " Loading";
if(link.sheet) { // "already loaded"
oncssloaded.call(link);
}
else {
link.addEventListener("load", oncssloaded, { once: true });
}
}
function oncssloaded() {
this.media = "all";
this.className += " OnLoad";
}
<link rel="stylesheet" media="print" class="AOcssLoad" href="data:text/css,body{color:green}" />
Some green text

SEO impact of CSS Stylesheet dynamic loading with JavaScript

Because my webpages are a bit heavy, I decided to use a preloader. The purpose of a preloader is to show some content before the main content loads, engaging the user from the start. Therefore it is very important to show preloader ASAP.
There is a slight problem though. Browser will typically wait for all CSS to be loaded before attempting to display and render HTML. This can be problematic if the document contains several biggish stylesheets.
So, my solution was thus:
<!-- In the head: -->
<noscript>
<link rel="stylesheet" href="/big-css1.css">
<link rel="stylesheet" href="/big-css2.css">
<link rel="stylesheet" href="/big-css3.css">
</noscript>
<script>
function LoadCSS(path) {
var head = document.getElementsByTagName('head')[0],
link = document.createElement('link');
link.setAttribute('href', path);
link.setAttribute('rel', 'stylesheet');
link.setAttribute('type', 'text/css');
head.appendChild(link);
}
</script>
<style>
/*preloader CSS is here*/
</style>
...
</head>
<body>
<div id="preloader">
Some nice content here
</div>
<!-- Main content starts below: -->
<div id="wrapper">
<script>
//hide main content until its loaded
//use JS and not CSS to support those with JS disabled
var el_preloader = document.getElementById("preloader");
var el_wrapper = document.getElementById("wrapper");
el_preloader.style.display = "block";
el_wrapper.style.display = "none";
LoadCSS('/big-css1.css');
LoadCSS('/big-css2.css');
LoadCSS('/big-css3.css');
</script>
...
<!-- After jQuery has been loaded -->
<script>
jQuery(window).load(function() {
document.getElementById("wrapper").style.display = "block";
jQuery('#preloader').fadeOut(1000, function () {
jQuery('#preloader').remove();
});
});
</script>
So, basically, if the use supports JavaScript, then JavaScript handles loading of stylesheets, and if JS is turned off, then the stuff inside the <noscript> tag is parsed and thus stylesheets are loaded normally.
This setup works fine in modern browsers, however I am not sure what impact it will have on SEO, considering Google and others are now evaluating user experience on the websites, and for that they need to parse CSS. Are they smart enough to render the website correctly with this solution? ARe there any adverse impacts on SEO with preloaders in general?
Thanks.

CSS Optimisation and PageSpeed Insights

I was running Google PageSpeed Insights on my website - www.gpsheatmap.com, and it suggested changing the loading of my stylesheets(https://developers.google.com/speed/docs/insights/OptimizeCSSDelivery#example) from -
<link href="/static/css/landing-page.css" rel="stylesheet">
To -
<script>
var cb = function() {
var l = document.createElement('link');
l.rel = 'stylesheet';
l.href = '/static/css/landing-page.css';
var h = document.getElementsByTagName('head')[0];
h.parentNode.insertBefore(l, h);
};
var raf = requestAnimationFrame || mozRequestAnimationFrame ||
webkitRequestAnimationFrame || msRequestAnimationFrame;
if (raf) raf(cb);
else window.addEventListener('load', cb);
</script>
I tried this for my stylesheets and it visibly changed the loading so you would see the pre-css view, then a second later you would see the stylesheet applied. This was in firefox
Should I disregard this approach, or can this be fixed?
You should consider the critical path and also put all the necessary style in your head section so as to avoid the FOUC (just the style for contents above the fold). This can be done either extracting the style by hand or — for larger sites — with an automatic task like critical-path-css-demo for gulp
Anyway if you choose to load all the stylesheets with javascript consider to still include them inside a <noscript> block, so they can be loaded also when JS is not available.
<noscript>
<link rel="stylesheet" ...>
</noscript>
As a further optimization for near-future browser (at this time it works only on Chrome Canary) it will be possible to early preload stylesheets using
<link rel="preload" href="..." as="style">
and to create an async loader in a simpler way
<link rel="preload" href="..." as="style" onload="this.rel='stylesheet'">
Another interesting and recent approach is described by Jake Archibald and it's called "Multi-stage CSS loading": it requires to load a small piece of CSS just before the markup that has to be styled and thus avoid the need for critical CSS, e.g.
<link rel="stylesheet" href="/site-header.css">
<header>…</header>
<link rel="stylesheet" href="/article.css">
<main>…</main>
<link rel="stylesheet" href="/comment.css">
<section class="comments">…</section>
The plan is for each to block rendering of subsequent content while the stylesheet loads, but allow the rendering of content before it. The stylesheets load in parallel, but they apply in series. This makes behave similar to <script src="…"></script>.

HTML5 Boilerplate and cirical rendering path / deffering scripts and styles

I used to build my websites based on the HTML5 Boilerplate: styles and modenizr in the head, jQuery (google CDN or hosted file) and scripts before the closing body tag. Something like that:
<!DOCTYPE html>
<!-- modernizr conditional comments here -->
<html class="no-js">
<head>
<link rel="stylesheet" href="css/normalize.css">
<link rel="stylesheet" href="css/main.css">
<script src="js/vendor/modernizr-2.6.2.min.js"></script>
</head>
<body>
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js"></script>
<script>window.jQuery || document.write('<script src="js/vendor/jquery-1.10.2.min.js"><\/script>')</script>
<script src="js/plugins.js"></script>
<script src="js/main.js"></script>
</body>
</html>
Now I want to remove all render-blocking below-the-fold css and js as suggested by Googles PageSpeed Insight.
How do I defer the css and js files including the jQuery library loaded from google?
What should I do about modernizr?
To remove this particular warning you need to do the following:
defer the load of all external CSS until after page onload
defer the load of all external JS until after page onload
In practice that means you need to do the following:
split your CSS into that required to avoid the "flash of unstyled content" (FOUC) and the rest
split your javascript likewise
inline the CSS and JS that is required
defer the load of the other CSS and JS until after page onload.
Using build tools is the only sane way of doing this. You can do it with various Grunt tools, or with the ant-based H5BP Build Script.
The basic method of deferring loads is as follows:
(function () {
// Load jQuery after page onload
function loadJS() {
var url = "https://ajax.googleapis.com/ajax/libs/jquery/1.11.3/jquery.min.js";
var n = document.createElement("script");
n.src = url;
n.onload = loadJS1;
document.body.appendChild(n);
}
// Load some JS after jquery has been loaded.
function loadJS1() {
var url = "js/main.js";
var n = document.createElement("script");
n.src = url;
// Continue chaining loads if needed.
//n.onload = loadJS2;
document.body.appendChild(n);
}
// Check for browser support of event handling capability
if (window.addEventListener) {
window.addEventListener("load", loadJS, false);
} else if (window.attachEvent) {
window.attachEvent("onload", loadJS);
} else {
window.onload = loadJS;
}
})();

Detect device and swap the CSS file - jQuery

My web application target to major Smartphones and I need to change the CSS file according to device (if there are issues in the UI need to hit them), and I’m planning swap CSS using following jQuery. Just want to know whether is it a best practice and good in performance?
<link rel="stylesheet" href="basic.css" type="text/css" class="cssLink" />
<link rel="stylesheet" href="general.css" type="text/css" />
<script type="text/javascript">
$(document).ready(function() {
// css file based on the device
var controlCss;
// get the device agent and conver to lover case
var deviceAgent = navigator.userAgent.toLowerCase();
if(deviceAgent.match(/android/i)){
controlCss = "android.css";
$(".cssLink").attr("href", controlCss);
}
else if(deviceAgent.match(/webso/i)){
controlCss = "webOS.css";
$(".cssLink").attr("href", controlCss);
}
else if(deviceAgent.match(/iphone/i)){
controlCss = "iphone.css";
$(".cssLink").attr("href", controlCss);
}
else if(deviceAgent.match(/ipod/i)){
controlCss = "ipad.css";
$(".cssLink").attr("href", controlCss);
}
else if(deviceAgent.match(/blackberry/i)){
controlCss = "bb.css";
$(".cssLink").attr("href", controlCss);
}
else {
controlCss = "basic.css";
$(".cssLink").attr("href", controlCss);
}
});
</script>
1.Is it best practice?
Depends on what you think of as best practice, also what best practice is in the context of your application and your company. One thing this makes me think about is: Can you guarantee all your pages will be using jQuery? If so then I think this is a good approach to achieve what you are after. An alternative would be to do this server-side, that would guarantee best-performance but there may be other reasons why you dont want to do this (maybe you dont have access to server-side code, or you want to maintain most of the functionality in the hands of front-end programmers).
2.Is it good in performance?
The short answer is no. On top of needing the 100K+ payload of jQuery to inject the CSS on the page. The way you've approached the problem at the moment is to wait for the whole page (and all dependencies) to load before adding styles to it. This will create a noticeable 'jump' between when the page gets displayed at first (without styles) and when the styles get loaded and everything moves around.
Loading the CSS server-side will get rid of this, but I think you can still do this in the UI and keep the majority of your code-base in JavaScript which will make it easier to maintain. In order to do this, remove the bit where you wait for the document to be loaded before calling up your CSS file:
<link rel="stylesheet" href="basic.css" type="text/css" class="cssLink" />
<link rel="stylesheet" href="general.css" type="text/css" />
<script type="text/javascript">
// No need to wait for document to load
// $(document).ready(function() {
// css file based on the device
var controlCss;
// get the device agent and conver to lover case
var deviceAgent = navigator.userAgent.toLowerCase();
if(deviceAgent.match(/android/i)){
controlCss = "android.css";
$(".cssLink").attr("href", controlCss);
}
// etc..
// });
</script>
To further improve performance you could use a solution that does not depend on jQuery, instead of
$(".cssLink").attr("href", controlCss);
you could add #cssLink to the stylesheet <link> element and use the DOM to do the same:
document.getElementById("cssLink").setAttribute("href", controlCss);
This would make you code look as follows:
<link rel="stylesheet" href="basic.css" type="text/css" css="cssLink" id="cssLink" />
<link rel="stylesheet" href="general.css" type="text/css" />
<script type="text/javascript">
// .. blah blah ..
if(deviceAgent.match(/android/i)){
controlCss = "android.css";
// use a solution that does not need jQuery
document.getElementById("cssLink").setAttribute("href", controlCss);
}
// etc..
</script>
This way you will remove the dependency on the 100K plus payload of jQuery before you can apply your stylesheets to the page.
UPDATE:
It is also possible to apply CSS rules based on screen size rather than device.
Have you had a look at #media queries?

Categories

Resources