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">
Related
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.
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.
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>.
My css and javascript aren't taken into consideration in iframes.
For example I've this iframe :
<iframe name="richTextField" id="richTextField"/>
Where I inject a .css file after page load:
$(document).ready(iFrameOn);
function iFrameOn() {
richTextField.document.designMode = "On";
richTextField.document.head.innerHTML = '<link rel="stylesheet" type="text/css" href="iframe.css">';
}
In chrome the head of my iframe will contain my stylesheet but it won't have any effect, in ie it's not even included.
Anyway I noticed that alert doesn't work for me on jsfiddle while it works for other users. Take this code for instance. No alert dialog appears for me on chrome. I think this might be linked. Any idea why this would happen ?
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?