Related
Rephrase of question: What is the best way to provide alternate stylesheets for a document?
I have a list of stylesheets, all of which are referenced in the html file.
I use javascript to disable all but one file.
example:
style1 disabled = false
style2 disabled = true
In practice, the last stylesheet to load (style2) is the active one, regardless of the disabled property.
How can I alternate between stylesheets on a document in chrome?
I tried to set the value of href attribute, but it seems to be read only.
example of code I have been using: (I am using an object called MenuStyles that is storing various css information)
function setActiveStyleSheet(name) {
var selectedSheet;
var currentSheet;
for (var i = 0; i < MenuStyles.styleSheets.length; i++) {
currentSheet = MenuStyles.styleSheets[i];
if (currentSheet.name === name) {
selectedSheet = currentSheet.sheetObj;
currentSheet.disabled = false;
} else {
currentSheet.disabled = true;
}
}
return selectedSheet;
}
EDIT: it turns out the problem was due entirely to bugs in the code. disabled property works fine. below is the fixed function:
function setActiveStyleSheet(name) {
var selectedSheet;
var currentSheet;
for (var i = 0; i < MenuStyles.styleSheets.length; i++) {
currentSheet = MenuStyles.styleSheets[i];
if (currentSheet.name === name) {
selectedSheet = currentSheet.sheetObj;
currentSheet.sheetObj.disabled = false;
} else {
currentSheet.sheetObj.disabled = true;
}
}
return selectedSheet;
}
In general you'd subclass off the BODY tag and use a single stylesheet that uses these classes. Then just swap the BODY class, not the sylesheet. Otherwise, you should be doing this server-side.
<body class="sheet1">
then
sheet1.h1 {
...
}
sheet2.h1 {
...
}
If you know the order of your stylesheets you can use-
document.styleSheets[i].disabled=true or
document.styleSheets[i].disabled=false;
If you have 2 stylesheets you can toggle between them with-
var S=document.styleSheets;
if(S[0].disabled){
S[0].disabled=false;
S[1].disabled=true;
}
else{
S[1].disabled=false;
S[0].disabled=true;
}
Current browsers offer reasonable ability to dynamically enable/disable stylesheets through the use of either the 'disabled' DOM property (Gecko) or by adding/removing the disabled attribute (Webkit and IE).
Unfortunately, these approaches only reliably work on 'link' tags that reference an external stylesheet (not 'style' tags), unless you are using IE10+. Yes - I said that - only IE does the right thing here.
For inline CSS using 'style' tags on non-IE browsers, you need to find a more crude way to enable/disable like those discussed above (remove the style element, etc).
I was able to get this to work with setting the disabled property and by including a 'title' attribute the stylesheets.
title property makes the stylesheet PREFERRED rather than PERSISTENT. see http://www.alistapart.com/articles/alternate/
I've found a great way (IMHO) to achieve this:
Let's suppose you know the exactly order of your stylesheet. Let's say you want to alternate stylesheet 0 and 1 (first and second):
To get a stylesheet state (enabled/disabled) you can try this (i.e. testing the second one):
document.styleSheets[1].disabled
...and it returns trueor false.
So to alternate you can write this code in an onclick event:
document.styleSheets[0].disabled = !(
document.styleSheets[1].disabled = !(document.styleSheets[1].disabled)
);
HTH!
For me if the link is disabled, document.styleSheets does not return the link in the collection ( in Chrome) . Only the enabled links are returned.
I use document.head.getElementsByTagName('LINK'), to get them all, out of HEAD.
Like:
private changeStyle(styleName: string): void {
const links = document.head.getElementsByTagName('LINK');
for (let i = 0; i < links.length; i++) {
const link = links[i] as any;
if( !link.href) continue;
if (link.href.indexOf(styleName) === -1) {
link.disabled = true;
} else {
link.disabled = false;
}
}
}
I need to check (in Javascript) if a CSS file is loaded and if not then to load it. jQuery is fine.
Just check to see if a <link> element exists with the href attribute set to your CSS file's URL:
if (!$("link[href='/path/to.css']").length)
$('<link href="/path/to.css" rel="stylesheet">').appendTo("head");
The plain ol' JS method is simple too, using the document.styleSheets collection:
function loadCSSIfNotAlreadyLoadedForSomeReason () {
var ss = document.styleSheets;
for (var i = 0, max = ss.length; i < max; i++) {
if (ss[i].href == "/path/to.css")
return;
}
var link = document.createElement("link");
link.rel = "stylesheet";
link.href = "/path/to.css";
document.getElementsByTagName("head")[0].appendChild(link);
}
loadCSSIfNotAlreadyLoadedForSomeReason();
I just had to write something like that and I wanted to share it. This one is prepared for multiple cases.
If there is no request for the css file (css file isn't linked ...)
If there is a request for the css file, but if it failed (css file is no longer available ...)
var styles = document.styleSheets;
for (var i = 0; i < styles.length; i++) {
// checking if there is a request for template.css
if (styles[i].href.match("template")) {
console.log("(Iteration: " + i + ") Request for template.css is found.");
// checking if the request is not successful
// when it is successful .cssRules property is set to null
if (styles[i].cssRules != null && styles[i].cssRules.length == 0) {
console.log("(Iteration: " + i + ") Request for template.css failed.");
// fallback, make your modification
// since the request failed, we don't need to iterate through other stylesheets
break;
} else {
console.log("(Iteration: " + i + ") Request for template.css is successful.");
// template.css is loaded successfully, we don't need to iterate through other stylesheets
break;
}
}
// if there isn't a request, we fallback
// but we need to fallback when the iteration is done
// because we don't want to apply the fallback each iteration
// it's not like our css file is the first css to be loaded
else if (i == styles.length-1) {
console.log("(Iteration: " + i + ") There is no request for template.css.");
// fallback, make your modification
}
}
TL;DR version
var styles = document.styleSheets;
for (var i = 0; i < styles.length; i++) {
if (styles[i].href.match("css-file-name-here")) {
if (styles[i].cssRules != null && styles[i].cssRules.length == 0) {
// request for css file failed, make modification
break;
}
} else if (i == styles.length-1) {
// there is no request for the css file, make modification
}
}
Update: Since my answer got a few upvotes and this led me to revise the code, I decided to update it.
// document.styleSheets holds the style sheets from LINK and STYLE elements
for (var i = 0; i < document.styleSheets.length; i++) {
// Checking if there is a request for the css file
// We iterate the style sheets with href attribute that are created from LINK elements
// STYLE elements don't have href attribute, so we ignore them
// We also check if the href contains the css file name
if (document.styleSheets[i].href && document.styleSheets[i].href.match("/template.css")) {
console.log("There is a request for the css file.");
// Checking if the request is unsuccessful
// There is a request for the css file, but is it loaded?
// If it is, the length of styleSheets.cssRules should be greater than 0
// styleSheets.cssRules contains all of the rules in the css file
// E.g. b { color: red; } that's a rule
if (document.styleSheets[i].cssRules.length == 0) {
// There is no rule in styleSheets.cssRules, this suggests two things
// Either the browser couldn't load the css file, that the request failed
// or the css file is empty. Browser might have loaded the css file,
// but if it's empty, .cssRules will be empty. I couldn't find a way to
// detect if the request for the css file failed or if the css file is empty
console.log("Request for the css file failed.");
// There is a request for the css file, but it failed. Fallback
// We don't need to check other sheets, so we break;
break;
} else {
// If styleSheets.cssRules.length is not 0 (>0), this means
// rules from css file is loaded and the request is successful
console.log("Request for the css file is successful.");
break;
}
}
// If there isn't a request for the css file, we fallback
// But only when the iteration is done
// Because we don't want to apply the fallback at each iteration
else if (i == document.styleSheets.length - 1) {
// Fallback
console.log("There is no request for the css file.");
}
}
TL;DR
for (var i = 0; i < document.styleSheets.length; i++) {
if (document.styleSheets[i].href && document.styleSheets[i].href.match("/template.css")) {
if (document.styleSheets[i].cssRules.length == 0) {
// Fallback. There is a request for the css file, but it failed.
break;
}
} else if (i == document.styleSheets.length - 1) {
// Fallback. There is no request for the css file.
}
}
Something like this will do (using jQuery):
function checkStyleSheet(url){
var found = false;
for(var i = 0; i < document.styleSheets.length; i++){
if(document.styleSheets[i].href==url){
found=true;
break;
}
}
if(!found){
$('head').append(
$('<link rel="stylesheet" type="text/css" href="' + url + '" />')
);
}
}
Peer to the comment made by JFK about the accepted answer:
I understood the question as "how to check whether a css file is
loaded or not" rather than "how to check if the element
exists".
The element may exist (and the path may be correct too) but it
doesn't mean that the css file was successful loaded.
If you access a link Element via getElementById you'll not be able to check/read the style defined inside the CSS file.
In order to check if a style has been successfully loaded we have to use getComputedStyle (or currentStyle for IE).
HTML
//somewhere in your html document
<div id="css_anchor"></div>
CSS
//somewhere in your main stylesheet
#css_anchor{display:none;}
JAVASCRIPT
//js function to check the computed value of a style element
function get_computed_style(id, name){
var element = document.getElementById(id);
return element.currentStyle ? element.currentStyle[name] : window.getComputedStyle ? window.getComputedStyle(element, null).getPropertyValue(name) : null;
}
//on document ready check if #css_anchor has been loaded
$(document).ready( function() {
if(get_computed_style('css_anchor', 'display')!='none'){
//if #css_anchor style doesn't exist append an alternate stylesheet
var alternateCssUrl = 'http://example.com/my_alternate_stylesheet.css';
var stylesheet = document.createElement('link');
stylesheet.href = alternateCssUrl;
stylesheet.rel = 'stylesheet';
stylesheet.type = 'text/css';
document.getElementsByTagName('head')[0].appendChild(stylesheet);
}
});
Part of the answer comes from: myDiv.style.display returns blank when set in master stylesheet
Demo here: http://jsfiddle.net/R9F7R/
Apart from all the nice answers above, you can simply put a dummy element inside your markup and in your css file, give it any style other than default. Then in the code check if the attribute is applied to the dummy element, and if not, load the css. Just a thought though, not a neat way to do that thing you want done.
My 2 cents. This checks whether there are any rules set on the css or not, meaning that it was or not successfully loaded
if(jQuery("link[href='/style.css']").prop('sheet').cssRules.length == 0){
//Load the css you want
}
The document object contains a stylesheet collection with all the loaded stylesheets.
For a reference see http://www.javascriptkit.com/domref/stylesheet.shtml
You can loop this collection to verify that the stylesheet you want to verify is in it and thus loaded by the browser.
document.styleSheets[0] //access the first external style sheet on the page
There are some browser incompatibilities you should look out for though.
One way: use document.getElementsByTagName("link") iterate over each and check if its href is equal to the CSS file you check.
Another way: if you know some CSS rule being set only in that file, check if this rule really apply e.g. check if background of something is really red.
You can either check if the filename is within your markup, like:
var lnks = document.getElementsByTagName('link'),
loadcss = true;
for(var link in lnks) {
href = link.getAttribute('href');
if( href.indexOf('foooobar.css') > -1) ){
loadcss = false;
return false;
}
});
if( loadcss ) {
var lnk = document.createElement('link'),
head = document.getElementsByTagName('head')[0] || document.documentElement;
lnk.rel = 'stylesheet';
lnk.type = 'text/css';
lnk.href = '//' + location.host + 'foooobar.css';
head.insertBefore(lnk, head.firstChild);
}
or you could check for a specific className which should be available, if the stylesheet was loaded. This probably comes a little closer to a feature detection.
var links = document.getElementsByTagName('link');
var file = 'my/file.css';
var found = false;
for ( var i in links )
{
if ( links[i].type == 'text/css' && file == links[i].href ) {
found = true; break;
}
}
if ( !( found ) ) {
var styles = document.getElementsByTagName('style');
var regexp = new RegExp('/\#import url\("?' + file + '"?\);/');
for ( var i in styles )
{
if ( styles[i].src == file ) {
found = true; break;
} else if ( styles[i].innerHTML.match(regexp) ) {
found = true; break;
}
}
}
if ( !( found ) ) {
var elm = document.createElement('link');
elm.href = file;
document.documentElement.appendChild(elm);
}
For a nice consistent and repeatable experience, I've written these two jQuery plugins that mimic the $.getScript(url, callback) jQuery method (however they will NOT force reloading from the server like $.getScript(). There are two methods: one that will load a CSS file anytime it's called, and one that will only load it once. I find the former handy during development when I'm making changes, and the latter great for a speedy deployment.
/**
* An AJAX method to asynchronously load a CACHED CSS resource
* Note: This removes the jQuery default behaviour of forcing a refresh by means
* of appending a datestamp to the request URL. Actual caching WILL be subject to
* server/browser policies
*/
$.getCachedCss = function getCachedCss(url, callback)
{
$('<link>',{rel:'stylesheet', type:'text/css', 'href':url, media:'screen'}).appendTo('head');
if (typeof callback == 'function')
callback();
}
/**
* An AJAX method to asynchronously load a CACHED CSS resource Only ONCE.
* Note: This removes the jQuery default behaviour of forcing a refresh by means
* of appending a datestamp to the request URL. Actual caching WILL be subject to
* server/browser policies
*/
$.getCachedCssOnce = function getCachedCssOnce(url, callback)
{
if (!$("link[href='" + url + "']").length) {
$.getCachedCss(url, callback);
if (typeof callback == 'function')
callback();
}
}
Usage example:
$(function() {
$.getCachedCssOnce("pathToMyCss/main.css");
)}
Usage example with callback:
$(function() {
$.getCachedCssOnce("pathToMyCss/main.css", function() {
// Do something once the CSS is loaded
});
use .sheet in jQuery:
HTML:
<link rel="stylesheet" href="custom.css">
jQuery:
if($("link[href='custom.css']")[0].sheet.cssRules.length==0){
//custom.css was not loaded, do your backup loading here
}
simple way using javascript..,
loadCssIfNotLoaded('https://maxcdn.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css');
loadCssIfNotLoaded('https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css');
function loadCssIfNotLoaded(url) {
var element=document.querySelectorAll('link[href="' + url + '"]');
if (element.length == 0)
{
var link = document.createElement('link');
link.rel = 'stylesheet';
link.href = url;
document.getElementsByTagName("head")[0].appendChild(link);
}
}
In one line, with jQuery.
If the #witness div is visible, we have to load the css file.
In the HTML, we have a:
<div id="witness"></div>
In the CSS file to load, we have:
#witness{display:none;}
So, if the css file is loaded, the #witness div is not visible. We can check with jQuery and make decision.
!$('#witness').is(':visible') || loadCss() ;
As a snippet:
function loadCss(){
//...
console.log('Css file required');
};
!$('#witness').is(':visible') || loadCss();
#witness{display:none;}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/1.12.4/jquery.min.js"></script>
<div id="witness"></div>
I have a function doing the following using javascript:
Create link element and set href=cssFile.
Insert the link element in head tag.
Create a div element.
Set the class name using setAttribute
appendChild the div on body.
Now getting CSS rule value using document.defaultView.getComputedStyle(divElement, null)[cssRule].
Now getComputedStyle is returning the default values, and if I wait on breakpoint using Firebug before getComputedStyle call, then it returns the CSS rule from the CSS injected.
Regards,
Munim
You can create the dynamic css url and fetch the css as plain text using a normal ajax call.
Then use this to load the css:
function loadCss(cssText, callback){
var style = document.createElement('style');
style.type='text/css';
if(callBack != undefined){
style.onload = function(){
callBack();
};
}
style.innerHTML = cssText;
head.appendChild(style);
}
And use it like this:
loadCss(ajaxResponseText, function(){
console.log("yaay css loaded, now i can access css defs");
})
This is actually what I did.
To ensure a specific CSS file is loaded, I added a style in the end of the CSS file. For example:
#ensure-cssload-8473649 {
display: none
}
Now I have a JavaScript function which will fire the callback specified when the above style is loaded on the page:
var onCssLoad = function (options, callback) {
var body = $("body");
var div = document.createElement(constants.TAG_DIV);
for (var key in options) {
if (options.hasOwnProperty(key)) {
if (key.toLowerCase() === "css") {
continue;
}
div[key] = options[key];
}
}
var css = options.css;
if (css) {
body.appendChild(div);
var handle = -1;
handle = window.setInterval(function () {
var match = true;
for (var key in css) {
if (css.hasOwnProperty(key)) {
match = match && utils.getStyle(div, key) === css[key];
}
}
if (match === true) {
window.clearTimeout(handle);
body.removeChild(div);
callback();
}
}, 100);
}
}
And this is how I used the function above:
onCssLoad({
"id": "ensure-cssload-8473649",
css: {
display: "none"
}
}, function () {
// code when you want to execute
// after your CSS file is loaded
});
Here the 1st parameter takes the options where id is to check against the test style and css property to verify against what loaded from the CSS.
I assume you are doing this because you need to dynamically create the URL of the stylesheet.
Couple options come to mind:
1) Create the URL server-side and avoid this problem altogether.
2) Use a setTimeout to check whether or not the style has been loaded and check every 20ms or so until getComputedStyle returns the value you want.
I don't like #2 at all...but it's an option. If you use #2 make sure to clear the timeout even if there is an exception.
Here is a solution that seems to work across all browsers.
function loadCss(fileUrl) {
// check for css file type
if (fileUrl.indexOf(".css")==fileUrl.length-4) {
// Create link element
var fileref=document.createElement("link");
fileref.setAttribute("rel", "stylesheet");
fileref.setAttribute("type", "text/css");
fileref.setAttribute("href", fileUrl);
if (typeof fileref!="undefined") {
// remove the . if this is a relative link
if(fileUrl.indexOf('.')==0) {
fileUrl = fileUrl.substr(1);
}
// generate the full URL to use as the fileId
var pathname = window.location.pathname;
var pathUrl = pathname.substr(0,pathname.lastIndexOf("/"));
var fileId = window.location.protocol + "//" + window.location.host + pathUrl + fileUrl;
// append the newly created link tag to the head of the document
document.getElementsByTagName("head")[0].appendChild(fileref);
// begin checking for completion (100ms intervals up to 2000ms)
this.checkCSSLoaded(fileId,100,0,2000);
} else throw 'INVALID_CSS_ERROR';
} else throw 'INVALID_CSS_ERROR';
}
function checkCSSLoaded(cssHref,milliPerCheck,milliPerCount,milliTimeout) {
// Look through all sheets and try to find the cssHref
var atSheet = -1;
var sheetLength = document.styleSheets.length;
while(++atSheet < sheetLength ) {
if(cssHref == document.styleSheets[atSheet].href) {
// If found dispatch and event or call a procedure
/* Do whatever is next in your code here */
return;
}
}
// check for timeout
if(milliCount > milliTimeout) {
alert('INVALID_CSS_ERROR::'+" ("+cssHref+"+ not found!");
/* Do whatever happens if timeout is reached here */
return;
}
// else keep trying
setTimeout(checkCSSLoaded ,milliPerCheck, cssHref, milliPerCheck, milliCount+millPerCheck, milliTimeout);
}
Essentially we
Create a link tag.
Set its attributes so it knows its a stylesheet link tag
Create a file id in such a way that it will always be the full file URL
Append the link tag to the head of the document head
Perform consecutive tests to see if (stylesheet.href == fileID) comes into existence
If found do something else if timeout do something else keep checking
Using document.styleSheets to check if a css is loaded is wrong, since as soon as a css link is being added to the DOM, it will be available from document.styleSheets, even if it is not loaded yet.
Adding a marker to CSS is hacky too.
The correct solution is to listen to the onload event :
var loadedCss = {};
cssHref = "http://www.foo.com/bar.css";
css = document.createElement("link");
css.setAttribute("rel", "stylesheet");
css.setAttribute("type", "text/css");
css.setAttribute("href", cssHref);
css.onload = function(){
loadedCss[cssHref] = true;
}
document.getElementsByTagName("head")[0].appendChild(css);
function isCssLoaded(url) {
return loadCss[url];
}
How can I do this?
I tried
$('link[title="mystyle"]').remove();
and although the element is removed, the styles are still applied to the current page (in both Opera and Firefox).
Is there any other way?
To cater for ie you have to set the stylesheet to be disabled as it keeps the css styles in memory so removing the element will not work, it can also cause it to crash in some instances if I remember correctly.
This also works for cross browser.
e.g
document.styleSheets[0].disabled = true;
//so in your case using jquery try
$('link[title=mystyle]')[0].disabled=true;
I managed to do it with:
$('link[title="mystyle"]').attr('disabled', 'disabled');
it seems this is the only way to remove the styles from memory.
then I added:
$('link[title="mystyle"]').remove();
to remove the element too.
To disable your selected stylesheet:
$('link[title="mystyle"]').prop('disabled', true);
If you never want that stylesheet to be applied again, you can then .remove() it. But don’t do that if you want to be able to re-enable it later.
To re-enable the stylesheet, do this (as long as you didn’t remove the stylesheet’s element):
$('link[title="mystyle"]').prop('disabled', false);
In the code above, it is important to use .prop, not .attr. If you use .attr, the code will work in some browsers, but not Firefox. This is because, according to MDN, disabled is a property of the HTMLLinkElement DOM object, but not an attribute of the link HTML element. Using disabled as an HTML attribute is nonstandard.
no jQuery solution
if you can add id to your link tag
<link rel="stylesheet" href="css/animations.css" id="styles-animations">
document.getElementById("styles-animations").disabled = true;
if you know index position of your css file in document
document.styleSheets[0].disabled = true; // first
document.styleSheets[document.styleSheets.length - 1].disabled = true; // last
if you want to disable style by name you can use this function
/**
* #param [string] [styleName] [filename with suffix e.g. "style.css"]
* #param [boolean] [disabled] [true disables style]
*/
var disableStyle = function(styleName, disabled) {
var styles = document.styleSheets;
var href = "";
for (var i = 0; i < styles.length; i++) {
href = styles[i].href.split("/");
href = href[href.length - 1];
if (href === styleName) {
styles[i].disabled = disabled;
break;
}
}
};
note: make sure style file name is unique so you don't have "dir1/style.css" and "dir2/style.css". In that case it would disable only first style.
Using pure javascript:
var stylesheet = document.getElementById('stylesheetID');
stylesheet.parentNode.removeChild(stylesheet);
To remove a stylesheet:
$('link[src="<path>"]').remove();
To Replace a stylesheet:
$('link[src="<path>"]').attr('src','<NEW_FILE_PATH>');
If you want to do it only with the href attribute:
$('link[href="https://example.com/mycss.css"]').remove()
ES6 solution:
const disableStyle = styleName => {
const styles = document.styleSheets;
let href = "";
for (let i = 0; i < styles.length; i++) {
if (!styles[i].href) {
continue;
}
href = styles[i].href.split("/");
href = href[href.length - 1];
if (href === styleName) {
styles[i].disabled = true;
break;
}
}
};
Use it like disableStyle("MyUnwantedFile.css");.
Here's both an add & remove using the disabling principle mentioned in a number of these other posts to prevent cross browser issues. Note how my add checks to see if the sheet already exists, in which case it just enables it. Also, in contrast to some answers, this is designed to work using the url to a .css file as the sole argument to the functions (insulating the client from the use of id or title attributes).
function element( id ){ return document.getElementById( id ); }
function addStyleSheet( url ){
var id = _styleSheetUrlToId( url );
if( !_enableStyleSheet( id ) ) {
var link = document.createElement("link");
link.href = url;
link.type = "text/css";
link.rel = "stylesheet";
link.id = id;
document.getElementsByTagName("head")[0].appendChild( link );
}
}
function removeStyleSheet( url )
{ _enableStyleSheet( _styleSheetUrlToId( url ), false ); }
// "protected" function
function _styleSheetUrlToId( url ){
var urlParts = url.split("/");
return urlParts[urlParts.length-1].split(".")[0]
+ "-style";
}
// "protected" function
// returns if the sheet was found
function _enableStyleSheet( id, enable ) {
if( typeof(enable) == "undefined" ) enable = true;
var sheet = element( id );
if( sheet ) {
sheet.disabled = !enable;
return true;
}
return false;
}
Is there anyway to listen to the onload event for a <link> element?
F.ex:
var link = document.createElement('link');
link.rel = 'stylesheet';
link.href = 'styles.css';
link.onload = link.onreadystatechange = function(e) {
console.log(e);
};
This works for <script> elements, but not <link>. Is there another way?
I just need to know when the styles in the external stylesheet has applied to the DOM.
Update:
Would it be an idea to inject a hidden <iframe>, add the <link> to the head and listen for the window.onload event in the iframe? It should trigger when the css is loaded, but it might not guarantee that it's loaded in the top window...
Today, all modern browsers support the onload event on link tags. So I would guard hacks, such as creating an img element and setting the onerror:
if !('onload' in document.createElement('link')) {
imgTag = document.createElement(img);
imgTag.onerror = function() {};
imgTag.src = ...;
}
This should provide a workaround for FF-8 and earlier and old Safari & Chrome versions.
minor update:
As Michael pointed out, there are some browser exceptions for which we always want to apply the hack. In Coffeescript:
isSafari5: ->
!!navigator.userAgent.match(' Safari/') &&
!navigator.userAgent.match(' Chrom') &&
!!navigator.userAgent.match(' Version/5.')
# Webkit: 535.23 and above supports onload on link tags.
isWebkitNoOnloadSupport: ->
[supportedMajor, supportedMinor] = [535, 23]
if (match = navigator.userAgent.match(/\ AppleWebKit\/(\d+)\.(\d+)/))
match.shift()
[major, minor] = [+match[0], +match[1]]
major < supportedMajor || major == supportedMajor && minor < supportedMinor
This is kind of a hack, but if you can edit the CSS, you could add a special style (with no visible effect) that you can listen for using the technique in this post: http://www.west-wind.com/weblog/posts/478985.aspx
You would need an element in the page that has a class or an id that the CSS will affect. When your code detects that its style has changed, the CSS has been loaded.
A hack, as I said :)
The way I did it on Chrome (not tested on other browsers) is to load the CSS using an Image object and catching its onerror event. The thing is that browser does not know is this resource an image or not, so it will try fetching it anyway. However, since it is not an actual image it will trigger onerror handlers.
var css = new Image();
css.onerror = function() {
// method body
}
// Set the url of the CSS. In link case, link.href
// This will make the browser try to fetch the resource.
css.src = url_of_the_css;
Note that if the resource has already been fetched, this fetch request will hit the cache.
E.g. Android browser doesn't support "onload" / "onreadystatechange" events for element: http://pieisgood.org/test/script-link-events/
But it returns:
"onload" in link === true
So, my solution is to detect Android browser from userAgent and then wait for some special css rule in your stylesheet (e.g., reset for "body" margins).
If it's not Android browser and it supports "onload" event- we will use it:
var userAgent = navigator.userAgent,
iChromeBrowser = /CriOS|Chrome/.test(userAgent),
isAndroidBrowser = /Mozilla\/5.0/.test(userAgent) && /Android/.test(userAgent) && /AppleWebKit/.test(userAgent) && !iChromeBrowser;
addCssLink('PATH/NAME.css', function(){
console.log('css is loaded');
});
function addCssLink(href, onload) {
var css = document.createElement("link");
css.setAttribute("rel", "stylesheet");
css.setAttribute("type", "text/css");
css.setAttribute("href", href);
document.head.appendChild(css);
if (onload) {
if (isAndroidBrowser || !("onload" in css)) {
waitForCss({
success: onload
});
} else {
css.onload = onload;
}
}
}
// We will check for css reset for "body" element- if success-> than css is loaded
function waitForCss(params) {
var maxWaitTime = 1000,
stepTime = 50,
alreadyWaitedTime = 0;
function nextStep() {
var startTime = +new Date(),
endTime;
setTimeout(function () {
endTime = +new Date();
alreadyWaitedTime += (endTime - startTime);
if (alreadyWaitedTime >= maxWaitTime) {
params.fail && params.fail();
} else {
// check for style- if no- revoke timer
if (window.getComputedStyle(document.body).marginTop === '0px') {
params.success();
} else {
nextStep();
}
}
}, stepTime);
}
nextStep();
}
Demo: http://codepen.io/malyw/pen/AuCtH
Since you didn't like my hack :) I looked around for some other way and found one by brothercake.
Basically, what is suggested is to get the CSS using AJAX to make the browser cache it and then treat the link load as instantaneous, since the CSS is cached. This will probably not work every single time (since some browsers may have cache turned off, for example), but almost always.
Another way to do this is to check how many style sheets are loaded. For instance:
With "css_filename" the url or filename of the css file, and "callback" a callback function when the css is loaded:
var style_sheets_count=document.styleSheets.length;
var css = document.createElement('link');
css.setAttribute('rel', 'stylesheet');
css.setAttribute('type', 'text/css');
css.setAttribute('href', css_filename);
document.getElementsByTagName('head').item(0).appendChild(css);
include_javascript_wait_for_css(style_sheets_count, callback, new Date().getTime());
function include_javascript_wait_for_css(style_sheets_count, callback, starttime)
/* Wait some time for a style sheet to load. If the time expires or we succeed
* in loading it, call a callback function.
* Enter: style_sheet_count: the original number of style sheets in the
* document. If this changes, we think we finished
* loading the style sheet.
* callback: a function to call when we finish loading.
* starttime: epoch when we started. Used for a timeout. 12/7/11-DWM */
{
var timeout = 10000; // 10 seconds
if (document.styleSheets.length!=style_sheets_count || (new Date().getTime())-starttime>timeout)
callback();
else
window.setTimeout(function(){include_javascript_wait_for_css(style_sheets_count, callback, starttime)}, 50);
}
This trick is borrowed from the xLazyLoader jQuery plugin:
var count = 0;
(function(){
try {
link.sheet.cssRules;
} catch (e) {
if(count++ < 100)
cssTimeout = setTimeout(arguments.callee, 20);
else
console.log('load failed (FF)');
return;
};
if(link.sheet.cssRules && link.sheet.cssRules.length == 0) // fail in chrome?
console.log('load failed (Webkit)');
else
console.log('loaded');
})();
Tested and working locally in FF (3.6.3) and Chrome (linux - 6.0.408.1 dev)
Demo here (note that this won't work for cross-site css loading, as is done in the demo, under FF)
You either need a specific element which style you know, or if you control the CSS file, you can insert a dummy element for this purpose. This code will exactly make your callback run when the css file's content is applied to the DOM.
// dummy element in the html
<div id="cssloaded"></div>
// dummy element in the css
#cssloaded { height:1px; }
// event handler function
function cssOnload(id, callback) {
setTimeout(function listener(){
var el = document.getElementById(id),
comp = el.currentStyle || getComputedStyle(el, null);
if ( comp.height === "1px" )
callback();
else
setTimeout(listener, 50);
}, 50)
}
// attach an onload handler
cssOnload("cssloaded", function(){
alert("ok");
});
If you use this code in the bottom of the document, you can move the el and comp variables outside of the timer in order to get the element once. But if you want to attach the handler somewhere up in the document (like the head), you should leave the code as is.
Note: tested on FF 3+, IE 5.5+, Chrome
The xLazyLoader plugin fails since the cssRules properties are hidden for stylesheets that belong to other domains (breaks the same origin policy). So what you have to do is compare the ownerNode and owningElements.
Here is a thorough explanation of what todo:
http://yearofmoo.com/2011/03/cross-browser-stylesheet-preloading/
// this work in IE 10, 11 and Safari/Chrome/Firefox/Edge
// if you want to use Promise in an non-es6 browser, add an ES6 poly-fill (or rewrite to use a callback)
let fetchStyle = function(url) {
return new Promise((resolve, reject) => {
let link = document.createElement('link');
link.type = 'text/css';
link.rel = 'stylesheet';
link.onload = resolve;
link.href = url;
let headScript = document.querySelector('script');
headScript.parentNode.insertBefore(link, headScript);
});
};
This is a cross-browser solution
// Your css loader
var d = document,
css = d.head.appendChild(d.createElement('link'))
css.rel = 'stylesheet';
css.type = 'text/css';
css.href = "https://unpkg.com/tachyons#4.10.0/css/tachyons.css"
// Add this
if (typeof s.onload != 'undefined') s.onload = myFun;
} else {
var img = d.createElement("img");
img.onerror = function() {
myFun();
d.body.removeChild(img);
}
d.body.appendChild(img);
img.src = src;
}
function myFun() {
/* ..... PUT YOUR CODE HERE ..... */
}
The answer is based on this link that say:
What happens behind the scenes is that the browser tries to load the
CSS in the img element and, because a stylesheet is not a type of
image, the img element throws the onerror event and executes our
function. Thankfully, browsers load the entire CSS file before
determining its not an image and firing the onerror event.
In modern browsers you can do css.onload and add that code as a fallback to cover old browsers back to 2011 when only Opera and Internet Explorer supported the onload event and onreadystatechange respectively.
Note: I have answered here too and it is my duplicate and deserves to be punished for my honesteness :P