admin管理员组

文章数量:1389178

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 prehend 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 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 prehend 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.

Share Improve this question edited Sep 23, 2021 at 11:22 Aditya Agarwal asked Sep 14, 2021 at 12:30 Aditya AgarwalAditya Agarwal 622 silver badges15 bronze badges 15
  • 2 AFAIK link tag loads css files asynchronously. – Teemu Commented Sep 14, 2021 at 12:32
  • @Teemu nope. The css file for mentioned was from Plugin Autoptimize, and developer confirmed that the onload script that I inserted is pulsory for CSS files to load async as a general rule – Aditya Agarwal Commented Sep 14, 2021 at 12:33
  • This may help you: github./filamentgroup/loadCSS/issues/312 – Bettylex Commented Sep 14, 2021 at 13:05
  • @Bettylex Agreed, this was helpful in building the script I added in question... It was only working for 1 ID, and I had multiple css styles, sometimes even 10... will try to modify – Aditya Agarwal Commented Sep 14, 2021 at 13:20
  • @Bettylex look at the JS included in my question, it was inspired from ur link only, however I adapted to work with classes using for loop. But in it's core it's pretty much same. Yet I can confirm inconsistent working on my site. It works ~50% of times, and I cant diagnose problem, so resorted to finding a different method – Aditya Agarwal Commented Sep 14, 2021 at 13:35
 |  Show 10 more ments

6 Answers 6

Reset to default 1

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/npm/[email protected]/dist/css/bootstrap.min.css" />
<link rel="stylesheet" media="print" class="AOcssLoad" href="https://cdn.jsdelivr/npm/[email protected]/dist/css/bootstrap-utilities.min.css" />
<link rel="stylesheet" media="print" class="AOcssLoad" href="https://cdn.jsdelivr/npm/[email protected]/dist/css/bootstrap-reboot.min.css" />
<link rel="stylesheet" media="print" class="AOcssLoad" href="https://cdn.jsdelivr/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.

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

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.

本文标签: javascriptHow to load CSS Asynchronously without Inline Scripts (comply with CSP)Stack Overflow