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?
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
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>
I'm trying to get the styles.css to not cache as the server is having issues with the css when it caches.
<script>
var numBAH = Math.floor(Math.random()*100);
</script>
<link href="styles.css+ numBAH +" rel="stylesheet" type="text/css" />
You can do this in the head of your document :
<script>
document.write('<link href="styles.css?r='+ Math.floor(Math.random()*100) +'" rel="stylesheet" type="text/css" />');
</script>
BUT :
you have great probabilities of finding two times the same number
you shouldn't generally avoid caching
Solutions I propose :
1) use (new Date()).getTime() instead of a random number
2) simply change the number when the version changes : styles.css?v=3 (without javascript)
If you have access to a server-side language it would be neater to render the link tag with a query string that is a hash of the entire content of the file. In that way, the cache invalidator ey will change only when the content of the file has actually changed.
After having seen the discussion that has followed, about how you never want to use cache, because it loads too quickly, I want to change my answer. Not to new Date(), but to: fix your page so that loading quickly is a desired result. If you're having specific problems with that, create a question that targets those problems, don't go directly for the lousy workaround.
Cache busting can be work without server-side rendering.
I tested recent versions of firefox, chrome and safari in both mobile and desktop, this code worked. (I'm Not sure about IE, though..)
<HEAD>
<SCRIPT LANGUAGE="JavaScript">
var numBAH = Math.floor(Math.random()*10000);
document.write('<LI' + 'NK HREF="./path/to/style.css?cacheBusting='+numBAH+'" rel="stylesheet">');
</SCRIPT>
</HEAD>
I've got a couple books that I'm reading on AJAX, but still quite new. All the tutorials and these books have the ubiquitous examples of: an auto-populating search bar and an asynchronous form validator. Those are both great, but not what I'm looking for. Specifically, I want to click a button and switch the external CSS file in my header include. Is this possible? Well... I know it's possible, but how do you do it?
PS: I have jQuery in this project, so if there is something built in from there, even better!
PPS: I'm realizing I have not included important information (don't shoot me!):
The final goal of this will be to have a user settings section where the user can click a radio button and decide the color scheme they want to use for our app. So we will eventually have something like 8 different CSS styles to choose from. Not sure if this will alter the best method to achieve this.
The user is logging into their account and changing their setting there. I want their changes to 'stick' until they decide to change the stylesheet again. I can do this manually in MySQL as we have a table called stylesheets with the various user stylesheets numbered... so in actuality, what I'm needing to do is change that MySQL value asynchronously so the CSS is immediately loaded.
Add an id attribute to the CSS link tag to manipulate the tag using JavaScript:
<link id="cssfile" href="css/avocado.css" type="text/css" rel="stylesheet">
The Javascript to set the href attribute resembles:
document.getElementById('cssfile').href = 'css/carrot.css';
Colours could be tweaked by the user, by clicking a link:
<a href="#"
onclick="document.getElementById('cssfile').href='css/carrot.css';">Carrots</a>
By changing the media type, this could also allow users to quickly change print layouts, the preferred layout on mobiles (or tablets), and more.
This solution does not require jQuery.
See also: http://www.webmasterworld.com/forum91/4554.htm
Stylesheet Switcher in jQuery.
In response to the 'newbie followup' comment, I will try to make it a little more instructional.
The page I was playing with to test on while writing can be found here.
Page Display
You're going to want to have your current stylesheet displayed in a <link> tag in the <head> of each of your pages. The <link> tag will need an id for reference later in JavaScript. Something like:
<?php
// Somewhere in the server side code, $current_stylesheet is read from the user's
// "preferences" - most likely from a database / session object
$current_stylesheet = $user->stylesheet;
?>
<link href='<?php echo $current_stylesheet ?>' rel='stylesheet' type='text/css' id='stylelink' />
Changing the preference
Once you are displaying the users stylesheet, you need a way to change it. Create a <form> that will send a request to the server when the user changes their stylesheet:
<form method="GET" id="style_form" >
<select name="stylesheet" id="styleswitch">
<option value="css1.css">Black & White</option>
<option value="css2.css" selected="selected">Shades of Grey</option>
</select>
<input value='save' type='submit' />
</form>
Server Side
Now, without jQuery, submitting this form should GET (you could change it to POST if you like) stylesheet={new stylesheet} on the current page. So somewhere in your bootstrap / sitewide include file, you do a check for it, a php sample:
$styles = array(
'css1.css' => 'Black & White',
'css2.css' => 'Shades of Grey',
);
if (!empty($_GET["sytlesheet"]) {
// VALIDATE IT IS A VALID STYLESHEET - VERY IMPORTANT
// $styles is the array of styles:
if (array_key_exists($_GET["stylesheet"], $styles)) {
$user->stylesheet = $_GET["stylesheet"];
$user->save();
}
}
Live Preview
At this point, you have a functioning styleswitcher for the lame people without javascript. Now you can add some jQuery to make this all happen a little more elegantly. You'll want to use the jQuery Form Plugin to make a nice ajaxForm() function, that will handle submitting the form. Add the jQuery and jQuery Form library to the page:
<script type='text/javascript' src='/js/jquery.js'></script>
<script type='text/javascript' src='/js/jquery.form.js'></script>
Now that we have the libraries included -
$(function() {
// When everything has loaded - this function will execute:
$("#style_form").ajaxForm(function() {
// the style form will be submitted using ajax, when it succeeds:
// this function is called:
$("#thediv").text('Now Using: '+$('#styleswitch').val());
});
$("#styleswitch").change(function() {
// When the styleswitch option changes, switch the style's href to preview
$("#stylelink").attr('href', $(this).val());
// We also want to submit the form to the server (will use our ajax)
$(this).closest('form').submit();
});
// now that you have made changing the select option submit the form,
// lets get rid of the submit button
$("#style_form input[type=submit]").remove();
});
Here's an example that uses jQuery.
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" type="text/css" href="style1.css" />
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.3.2/jquery.min.js"
type="text/javascript"></script>
<script type="text/javascript">
$(function(){
$('#change-css').click(function(e){
e.preventDefault();
$('link[rel="stylesheet"]').attr('href', 'style2.css');
});
});
</script>
</head>
<body>
<a id="change-css" href="#">change css</a>
</body>
</html>
The operative line is $('link[rel="stylesheet"]').attr('href', 'style2.css');. This finds any <link> tag that has rel="stylesheet" and changes its href attribute to style2.css.
It has nothing to do with Ajax. It has everything to do with JS and DOM manipulation (Some key words to search for tutorial).
I am using Mootools, which is a JS library and it has a built in function for that.
If doing it manually is your thing, then I would simply add a <link> element to the <head> or adjust the href attribute of an existing <link> element.
<link rel="stylesheet" href="http://sstatic.net/so/all.css?v=6063" id='bobo'>
...
...
...
<script>document.getElementById('bobo').href="http://my.doamin.com/new.css";</script>
You could also load both CSS files and preface the all of the selectors on the second file with a body classname.
body.secondsheet {}
body.secondsheet a {}
body.secondsheet hr {}
Then all you have to do is add/remove the "secondsheet" class to the body tag to switch stylesheets.
To add a new css file to a page just create a new <link> tag:
function addCss (url) {
var s = document.createElement('link');
s.rel = 'stylesheet';
s.type = 'text/css';
s.href = url;
document.getElementsByTagName('head')[0].appendChild(s);
}
addCss('http://path/to/stylesheet.css');
To remove a css file from a page just remove the <link> to it:
function removeCss (search) {
var css = document.getElementsByTagName('link');
for (var i=0;i<css.length;i++) {
var c = css[i];
if (c.rel === 'stylesheet' || c.type === 'text/css') {
if (c.href && c.href.match(search)) {
c.parentNode.removeChild(c);
}
}
}
}
// Remove all css that contains 'mycss_', can use regexp if necessary:
removeCss(/mycss_.*\.css/);