Reputation: 62
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.
Upvotes: 5
Views: 3471
Reputation: 1
I know this thread is pretty old but I'll throw this out there as it's something I'm currently working with in WordPress...
Firstly, add the stylesheet using standard WordPress tools (wp_enqueue_style()
).
Then:
add_filter('style_loader_tag', 'optimize_async_style',10,4);
function optimize_async_style(string $tag, string $handle, string $src, string $media): string
{
$nonce = wp_create_nonce();
$nonce = " nonce='{$nonce}'" : "";
$tag = "<link rel='preload' id='{$handle}-css' href='{$src}' as='style' media='{$media}'{$nonce}>\n".
"<script{$nonce}>document.getElementById('{$handle}-css').addEventListener(".
"'load',(e)=>{e.currentTarget.rel='stylesheet';},{once:true});".
"</script><noscript>" . trim($tag) . "</noscript>\n";
return $tag;
}
The style_loader_tag
filter lets us change the link
tag with rel='preload'
followed by a script
tag that changes it to rel='stylesheet'
once loaded. This provides an asynchronous load of the stylesheet. Note also that we assign a nonce to both the link
and script
tags for CSP.
The output should look something like:
<link rel='preload' id='AOcssLoad-1' href='...' as='style' media='all' nonce='4783a68661'>
<script nonce='4783a68661'>document.getElementById('AOcssLoad-1').addEventListener('load',(e)=>{e.currentTarget.rel='stylesheet';},{once:true});</script>
<noscript><link rel='stylesheet' id='AOcssLoad-1' href='...' media='all' /></noscript>
This is, however, using id not class.
Upvotes: 0
Reputation: 668
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:
As you can see the downloading takes more than 6 seconds but doesn't add up to the TBT:
Upvotes: 1
Reputation: 2869
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
Upvotes: 1
Reputation: 136698
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
Upvotes: 0
Reputation: 155
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
Upvotes: 0
Reputation: 1355
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/[email protected]/dist/css/bootstrap.min.css" />
<link rel="stylesheet" media="print" class="AOcssLoad" href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap-utilities.min.css" />
<link rel="stylesheet" media="print" class="AOcssLoad" href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap-reboot.min.css" />
<link rel="stylesheet" media="print" class="AOcssLoad" href="https://cdn.jsdelivr.net/npm/[email protected]/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.
Upvotes: 0