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>.
Related
In a (Wordpress) website's <head>, I have the following code to load an additional stylesheet after all dynamic html and inline CSS created by a (third-party) slider plugin has been created via JS:
<link rel="preload" as="style" type="text/css" href="<?php bloginfo('stylesheet_directory'); ?>/slider-styles1.css" onload="this.rel='stylesheet'">
The reason for the necessity to load that additional stylesheet later than the main stylesheet is that it contains some calc values for height settings which depend on other calculated (inline css) values created by the plugin which again depend on the the size of images loaded by the plugin. A rather complex thing, there was actually some trial and error involved before I got it working, since I don't know what the plugin actually does in which order.
My problem: This works in all current browsers, but in some older browsers (for example Firefox < 55, reported by a user with a very old computer) the related stylesheet is not loaded at all. Apparently (also according to caniuse) older browsers don't know <link rel="preload">.
So my question is if there is anything I can replace that line of code with, which also would work in older browsers?
It's not clear what the purpose is of this pattern:
<link
rel="preload"
as="style"
type="text/css"
href="<?php bloginfo('stylesheet_directory'); ?>/slider-styles1.css"
onload="this.rel='stylesheet'">
It is preloading a stylesheet, then after it preloads it's changing rel to stylesheet so that it loads for real. Usually rel="preload" is for kicking off a download of a resource that isn't loading up front ahead of schedule, prepping it in the cache, whether that's to avoid excessive download waterfalls or to let something that will be loaded dynamically later on will complete sooner.
Since in your case you're wanting it to always load the stylesheet on page load, there's no need to do anything with preloading. Just replace with this, and it should be compatible with browsers that don't support preloading:
<link
rel="stylesheet"
type="text/css"
href="<?php bloginfo('stylesheet_directory'); ?>/slider-styles1.css">
If the idea was to use this preload + onload as a way to delay loading stylesheet until after page load, a better approach may be to inject the link via some JavaScript:
<script>
var sliderStylesheet = "<?php bloginfo('stylesheet_directory'); ?>/slider-styles1.css";
document.addEventListener('load', function () {
var link = document.createElement('link');
link.setAttribute('rel', 'stylesheet');
link.setAttribute('type', 'text/css');
link.setAttribute('href', sliderStylesheet);
document.head.appendChild(link);
});
</script>
I found a solution myself: I added an ID to the <link> tag to be able to address it in a simple way and used a simple script wrapped inside a setTimeout function to change the value of the rel attribute.
Note: Since there is no visible content, there is no visible result in the following snippet window, but the code with the changed attribute can be seen when inspecting the snippet with the browser tools. It also works when the link is inside the <head> section, BTW, which is my real-world situation.
jQuery(document).ready(function() {
setTimeout(function() {
jQuery('#link_to_sliderstyles1').attr("rel", "stylesheet");
}, 200);
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<link rel="preload" as="style" type="text/css" href="<?php bloginfo('stylesheet_directory'); ?>/slider-styles1.css" onload="this.rel='stylesheet'" id="link_to_sliderstyles1">
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
When downloading multiple commonly used javascript/css files (e.g. boostrap and jquery), many topics like this one recommend the use of a CDN, with one of the main arguments that it can then be used to load them asynchronously.
How does that work? To the best of my knowledge, <script> tags in the header are read synchronously, so it won't actually look at the second CDN file until the first one is finished.
<script src="//ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/popper.js/1.16.0/umd/popper.min.js"></script>
<script src="//maxcdn.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.min.js"></script>
How can I make the page download the scripts asynchronously, but execute them synchronously? Or is that actually happening by default somehow? And what about CSS files, will my
<link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css">
behave any different in that sense? I would like to understand the loading process properly before adding my own failovers to local code (for if the CDN is down), as to prevent getting stuck with synchronous downloading.
(Note that, despite the near-identical title, this is not a duplicate of this question, which is about loading scripts dynamically.)
Also note that I can't use defer (at least in the vanilla way that I know) as that would prevent me from adding said failover when the CDN is down, e.g.
<script src="//netdna.bootstrapcdn.com/twitter-bootstrap/2.2.1/js/bootstrap.min.js"></script>
<script> $.fn.modal || document.write('<script src="Script/bootstrap.min.js">\x3C/script>')</script>
would be broken by simply adding defer.
It's more about parallelism than asynchronousness. (They're certainly related, but the CDN argument related to limits on multiple downloads from the same origin is about parallelism.)
How can I make the page download the scripts asynchronously, but execute them synchronously?
Any decent browser, when given the three script tags you've shown, will download them in parallel (up to its parallel-from-the-same-site limit) and then execute them in order. You don't have to do anything to make that happen. Browsers read ahead in the HTML to find resources to fetch.
Adding fallback scripts with document.write might complicate the browser's ability to do that, or even prevent it, but you can ensure it declaratively using <link rel="preload" as="script" href="..."> (more on MDN). Combining that with fallback scripts for failed CDN resources, it might look something like this:
<head>
<!-- ... -->
<link rel="preload" as="script" href="//ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js">
<link rel="preload" as="script" href="//cdnjs.cloudflare.com/ajax/libs/popper.js/1.16.0/umd/popper.min.js">
<link rel="preload" as="script" href="//maxcdn.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.min.js">
</head>
<body>
<!-- ... -->
<script src="//ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
<script>if (!/*loaded condition*/) document.write(/*fallback*/);</script>
<script src="//cdnjs.cloudflare.com/ajax/libs/popper.js/1.16.0/umd/popper.min.js"></script>
<script>if (!/*loaded condition*/) document.write(/*fallback*/);</script>
<script src="//maxcdn.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.min.js"></script>
<script>if (!/*loaded condition*/) document.write(/*fallback*/);</script>
</body>
</html>
Note that that doesn't preload the fallbacks. You could, but then you'd be loading them even when the CDN was working, which wastes the end user's bandwidth. The fallbacks would be for the presumably-temporary degraded situation where the CDN was unavailable, where a degraded user experience is probably okay. (You could even show the user an indicator of a problem when scheduling the fallback, like Gmail's "something is taking longer than usual" indicator.)
If you're bothered by repeating the URLs and you're okay with document.write in small doses (as you seem to be), you can avoid duplicating the URLs by doing something along these lines:
<head>
<!-- ... -->
<script>
var scripts = [
{
url: "//ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js",
okay: function() { return /*check it loaded*/; }
},
{
url: "//cdnjs.cloudflare.com/ajax/libs/popper.js/1.16.0/umd/popper.min.js",
okay: function() { return /*check it loaded*/; }
},
{
url: "//maxcdn.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.min.js",
okay: function() { return /*check it loaded*/; }
},
];
scripts.forEach(function(script) {
document.write('<link rel="preload" as="script" href="' + script.url + '">');
});
</script>
</head>
<body>
<!-- ... -->
<script>
scripts.forEach(function(script, index) {
var fallback = script.url.substring(script.url.lastIndexOf('/') + 1);
document.write('<script src="' + script.url + '"><\/script>');
document.write('<script>if (!scripts[' + index + '].okay()) document.write(\'<script src="' + fallback + '"><\\/script>\');<\/script>');
});
</script>
</body>
</html>
(Since that's all inline script you're unlikely to transpile, I've kept the syntax to ES5 level in case you have to support obsolete environments.)
I think you can still use defer, just put your fallback code into an event handler...
https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script
defer
This Boolean attribute is set to indicate to a browser that the
script is meant to be executed after the document has been parsed, but
before firing DOMContentLoaded.
Scripts with the defer attribute will prevent the DOMContentLoaded
event from firing until the script has loaded and finished evaluating.
[...]
Scripts with the defer attribute will execute in the order in which
they appear in the document.
... so DOMContentLoaded could be a good pick.
Or, you can also put the fallback code into a separate .js file, and then it can be loaded with defer too, relying on the bottom part of the quotation, so the in-order execution.
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>
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.