Show blue dot in chrome tab [duplicate] - javascript

I have a web application that's branded according to the user that's currently logged in. I'd like to change the favicon of the page to be the logo of the private label, but I'm unable to find any code or any examples of how to do this. Has anybody successfully done this before?
I'm picturing having a dozen icons in a folder, and the reference to which favicon.ico file to use is just generated dynamically along with the HTML page. Thoughts?

Why not?
var link = document.querySelector("link[rel~='icon']");
if (!link) {
link = document.createElement('link');
link.rel = 'icon';
document.head.appendChild(link);
}
link.href = 'https://stackoverflow.com/favicon.ico';

Here’s some code that works in Firefox, Opera, and Chrome (unlike every other answer posted here). Here is a different demo of code that works in IE11 too. The following example might not work in Safari or Internet Explorer.
/*!
* Dynamically changing favicons with JavaScript
* Works in all A-grade browsers except Safari and Internet Explorer
* Demo: http://mathiasbynens.be/demo/dynamic-favicons
*/
// HTML5™, baby! http://mathiasbynens.be/notes/document-head
document.head = document.head || document.getElementsByTagName('head')[0];
function changeFavicon(src) {
var link = document.createElement('link'),
oldLink = document.getElementById('dynamic-favicon');
link.id = 'dynamic-favicon';
link.rel = 'shortcut icon';
link.href = src;
if (oldLink) {
document.head.removeChild(oldLink);
}
document.head.appendChild(link);
}
You would then use it as follows:
var btn = document.getElementsByTagName('button')[0];
btn.onclick = function() {
changeFavicon('http://www.google.com/favicon.ico');
};
Fork away or view a demo.

If you have the following HTML snippet:
<link id="favicon" rel="shortcut icon" type="image/png" href="favicon.png" />
You can change the favicon using Javascript by changing the HREF element on this link, for instance (assuming you're using JQuery):
$("#favicon").attr("href","favicon2.png");
You can also create a Canvas element and set the HREF as a ToDataURL() of the canvas, much like the Favicon Defender does.

jQuery Version:
$("link[rel='shortcut icon']").attr("href", "favicon.ico");
or even better:
$("link[rel*='icon']").attr("href", "favicon.ico");
Vanilla JS version:
document.querySelector("link[rel='shortcut icon']").href = "favicon.ico";
document.querySelector("link[rel*='icon']").href = "favicon.ico";

A more modern approach:
const changeFavicon = link => {
let $favicon = document.querySelector('link[rel="icon"]')
// If a <link rel="icon"> element already exists,
// change its href to the given link.
if ($favicon !== null) {
$favicon.href = link
// Otherwise, create a new element and append it to <head>.
} else {
$favicon = document.createElement("link")
$favicon.rel = "icon"
$favicon.href = link
document.head.appendChild($favicon)
}
}
You can then use it like this:
changeFavicon("http://www.stackoverflow.com/favicon.ico")

Here's a snippet to make the favicon be an emoji, or text. It works in the console when I'm at stackoverflow.
function changeFavicon(text) {
const canvas = document.createElement('canvas');
canvas.height = 64;
canvas.width = 64;
const ctx = canvas.getContext('2d');
ctx.font = '64px serif';
ctx.fillText(text, 0, 64);
const link = document.createElement('link');
const oldLinks = document.querySelectorAll('link[rel="shortcut icon"]');
oldLinks.forEach(e => e.parentNode.removeChild(e));
link.id = 'dynamic-favicon';
link.rel = 'shortcut icon';
link.href = canvas.toDataURL();
document.head.appendChild(link);
}
changeFavicon('❤️');

The favicon is declared in the head tag with something like:
<link rel="shortcut icon" type="image/ico" href="favicon.ico">
You should be able to just pass the name of the icon you want along in the view data and throw it into the head tag.

Here's some code I use to add dynamic favicon support to Opera, Firefox and Chrome. I couldn't get IE or Safari working though. Basically Chrome allows dynamic favicons, but it only updates them when the page's location (or an iframe etc in it) changes as far as I can tell:
var IE = navigator.userAgent.indexOf("MSIE")!=-1
var favicon = {
change: function(iconURL) {
if (arguments.length == 2) {
document.title = optionalDocTitle}
this.addLink(iconURL, "icon")
this.addLink(iconURL, "shortcut icon")
// Google Chrome HACK - whenever an IFrame changes location
// (even to about:blank), it updates the favicon for some reason
// It doesn't work on Safari at all though :-(
if (!IE) { // Disable the IE "click" sound
if (!window.__IFrame) {
__IFrame = document.createElement('iframe')
var s = __IFrame.style
s.height = s.width = s.left = s.top = s.border = 0
s.position = 'absolute'
s.visibility = 'hidden'
document.body.appendChild(__IFrame)}
__IFrame.src = 'about:blank'}},
addLink: function(iconURL, relValue) {
var link = document.createElement("link")
link.type = "image/x-icon"
link.rel = relValue
link.href = iconURL
this.removeLinkIfExists(relValue)
this.docHead.appendChild(link)},
removeLinkIfExists: function(relValue) {
var links = this.docHead.getElementsByTagName("link");
for (var i=0; i<links.length; i++) {
var link = links[i]
if (link.type == "image/x-icon" && link.rel == relValue) {
this.docHead.removeChild(link)
return}}}, // Assuming only one match at most.
docHead: document.getElementsByTagName("head")[0]}
To change favicons, just go favicon.change("ICON URL") using the above.
(credits to http://softwareas.com/dynamic-favicons for the code I based this on.)

Or if you want an emoticon :)
var canvas = document.createElement("canvas");
canvas.height = 64;
canvas.width = 64;
var ctx = canvas.getContext("2d");
ctx.font = "64px serif";
ctx.fillText("☠️", 0, 64);
$("link[rel*='icon']").prop("href", canvas.toDataURL());
Props to https://koddsson.com/posts/emoji-favicon/

in most cases, favicon is declared like this.
<link rel="icon" href"...." />
This way you can attain reference to it with this.
const linkElement = document.querySelector('link[rel=icon]');
and you can change the picture with this
linkElement.href = 'url/to/any/picture/remote/or/relative';

The only way to make this work for IE is to set you web server to treat requests for *.ico to call your server side scripting language (PHP, .NET, etc). Also setup *.ico to redirect to a single script and have this script deliver the correct favicon file. I'm sure there is still going to be some interesting issues with cache if you want to be able to bounce back and forth in the same browser between different favicons.

I would use Greg's approach and make a custom handler for favicon.ico
Here is a (simplified) handler that works:
using System;
using System.IO;
using System.Web;
namespace FaviconOverrider
{
public class IcoHandler : IHttpHandler
{
public void ProcessRequest(HttpContext context)
{
context.Response.ContentType = "image/x-icon";
byte[] imageData = imageToByteArray(context.Server.MapPath("/ear.ico"));
context.Response.BinaryWrite(imageData);
}
public bool IsReusable
{
get { return true; }
}
public byte[] imageToByteArray(string imagePath)
{
byte[] imageByteArray;
using (FileStream fs = new FileStream(imagePath, FileMode.Open, FileAccess.Read))
{
imageByteArray = new byte[fs.Length];
fs.Read(imageByteArray, 0, imageByteArray.Length);
}
return imageByteArray;
}
}
}
Then you can use that handler in the httpHandlers section of the web config in IIS6 or use the 'Handler Mappings' feature in IIS7.

There is a single line solution for those who use jQuery:
$("link[rel*='icon']").prop("href",'https://www.stackoverflow.com/favicon.ico');

I use this feature all the time when developing sites ... so I can see at-a-glance which tab has local, dev or prod running in it.
Now that Chrome supports SVG favicons it makes it a whole lot easier.
Tampermonkey Script
Have a gander at https://gist.github.com/elliz/bb7661d8ed1535c93d03afcd0609360f for a tampermonkey script that points to a demo site I chucked up at https://elliz.github.io/svg-favicon/
Basic code
Adapted this from another answer ... could be improved but good enough for my needs.
(function() {
'use strict';
// play with https://codepen.io/elliz/full/ygvgay for getting it right
// viewBox is required but does not need to be 16x16
const svg = `
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
<circle cx="8" cy="8" r="7.2" fill="gold" stroke="#000" stroke-width="1" />
<circle cx="8" cy="8" r="3.1" fill="#fff" stroke="#000" stroke-width="1" />
</svg>
`;
var favicon_link_html = document.createElement('link');
favicon_link_html.rel = 'icon';
favicon_link_html.href = svgToDataUri(svg);
favicon_link_html.type = 'image/svg+xml';
try {
let favicons = document.querySelectorAll('link[rel~="icon"]');
favicons.forEach(function(favicon) {
favicon.parentNode.removeChild(favicon);
});
const head = document.getElementsByTagName('head')[0];
head.insertBefore( favicon_link_html, head.firstChild );
}
catch(e) { }
// functions -------------------------------
function escapeRegExp(str) {
return str.replace(/([.*+?^=!:${}()|\[\]\/\\])/g, "\\$1");
}
function replaceAll(str, find, replace) {
return str.replace(new RegExp(escapeRegExp(find), 'g'), replace);
}
function svgToDataUri(svg) {
// these may not all be needed - used to be for uri-encoded svg in old browsers
var encoded = svg.replace(/\s+/g, " ")
encoded = replaceAll(encoded, "%", "%25");
encoded = replaceAll(encoded, "> <", "><"); // normalise spaces elements
encoded = replaceAll(encoded, "; }", ";}"); // normalise spaces css
encoded = replaceAll(encoded, "<", "%3c");
encoded = replaceAll(encoded, ">", "%3e");
encoded = replaceAll(encoded, "\"", "'"); // normalise quotes ... possible issues with quotes in <text>
encoded = replaceAll(encoded, "#", "%23"); // needed for ie and firefox
encoded = replaceAll(encoded, "{", "%7b");
encoded = replaceAll(encoded, "}", "%7d");
encoded = replaceAll(encoded, "|", "%7c");
encoded = replaceAll(encoded, "^", "%5e");
encoded = replaceAll(encoded, "`", "%60");
encoded = replaceAll(encoded, "#", "%40");
var dataUri = 'data:image/svg+xml;charset=UTF-8,' + encoded.trim();
return dataUri;
}
})();
Just pop your own SVG (maybe cleaned with Jake Archibald's SVGOMG if you're using a tool) into the const at the top. Make sure it is square (using the viewBox attribute) and you're good to go.

I use favico.js in my projects.
It allows to change the favicon to a range of predefined shapes and also custom ones.
Internally it uses canvas for rendering and base64 data URL for icon encoding.
The library also has nice features: icon badges and animations; purportedly, you can even stream the webcam video into the icon :)

According to WikiPedia, you can specify which favicon file to load using the link tag in the head section, with a parameter of rel="icon".
For example:
<link rel="icon" type="image/png" href="/path/image.png">
I imagine if you wanted to write some dynamic content for that call, you would have access to cookies so you could retrieve your session information that way and present appropriate content.
You may fall foul of file formats (IE reportedly only supports it's .ICO format, whilst most everyone else supports PNG and GIF images) and possibly caching issues, both on the browser and through proxies. This would be because of the original itention of favicon, specifically, for marking a bookmark with a site's mini-logo.

Yes totally possible
Use a querystring after the favicon.ico (and other files links -
see answer link below)
Simply make sure the server responds to the "someUserId" with
the correct image file (that could be static routing rules, or
dynamic server side code).
e.g.
<link rel="shortcut icon" href="/favicon.ico?userId=someUserId">
Then whatever server side language / framework you use should easily be able to find the file based on the userId and serve it up in response to that request.
But to do favicons properly (its actually a really complex subject) please see the answer here https://stackoverflow.com/a/45301651/661584
A lot lot easier than working out all the details yourself.
Enjoy.

Testing the proposed solutions on 2021 on Chrome, I found that some times the browser cache the favicon and do not show the change, even if the link was changed
This code worked (similar to previous proposal but adds a random parameter to avoid caching)
let oldFavicon = document.getElementById('favicon')
var link = document.createElement('link')
link.id = 'favicon';
link.type = 'image/x-icon'
link.rel = 'icon';
link.href = new_favicon_url +'?=' + Math.random();
if (oldFavicon) {
document.head.removeChild(oldFavicon);
}
document.head.appendChild(link);
Copied from https://gist.github.com/mathiasbynens/428626#gistcomment-1809869
in case that someone else have the same problem

Related

Trying to figure out how to create links using createTextNode

First off I would like to say, the person that originally created this portion of the code is no longer on the team.
We are creating a development tool to Administrate and Develop servers for our game, that has its own programming language.
I'm using JavaFX with WebView to generate the chat area of the development tool to communicate with other developers and staff. However I want it so hen you post a link it actually shows as a link instead of plain text. I have tried things such as AutoLinker with no success. Here is the HTML portion of the webview.
<script src=".././scripts/Autolinker.js"></script>
<script type="text/javascript">
app = null;
const messages = document.getElementById("messages");
function addMessage(message, options) {
const p = document.createElement("p");
const c = message.indexOf(":");
const modifiedMessage = message; //replaceURLWithHTMLLinks(message);
const ridBrackets = options.replace(/[\[\]']/g, "");
const tokenize = ridBrackets.split(",", 2);
const rcChatOptions = tokenize;
const mFontColor = tokenize[rcChatOptions.BFONTCOLOR];
let timeStampFormat = tokenize[rcChatOptions.TIMESTAMP];
if(c > -1) {
const u = document.createElement("span");
const a = document.createElement("a");
u.className = "user";
if(mFontColor != null) {
u.style.color = mFontColor;
} else {
u.style.color = "#00c02b";
}
//Turn plain text links into actual links
u.appendChild(document.createTextNode(Autolinker.link(modifiedMessage.substring(0, c + 1))));
p.appendChild(u);
if(document.selectedfont != null) {
p.style.fontFamily = document.selectedfont;
}
p.appendChild(document.createTextNode(modifiedMessage.substring(c + 1)));
} else {
p.appendChild(document.createTextNode(modifiedMessage));
}
// Append message and scroll to bottom (if at bottom)
const scrollTop = document.body.scrollTop;
const scrolledToBottom = scrollTop + window.innerHeight >= document.body.scrollHeight;
if(scrolledToBottom) {
messages.appendChild(p);
window.scrollTo(document.body.scrollLeft, document.body.scrollHeight - window.innerHeight);
} else {
messages.appendChild(p);
}
messages.style.backgroundColor = "transparent";
}
</script>
I removed portions of the code that I felt was just a distraction.
This what the tool looks like
https://share.getcloudapp.com/kpuNDB4m
this is what it looks like using AutoLinker
https://share.getcloudapp.com/8LunomDL
(So auto linker is doing its job, it just still isn't rending as HyperLinks)
It looks like the TextNode is created after collecting some substring which would be the link. Here's an example of what it would look like if a link was created directly in js then passed to the TextNode.
One thing you can do is place the text inside of an a tag within a paragraph and then convert like so:
var link = document.createElement('link');
link.innerHTML = 'Website: <a href="http://somelink.com" </a>
link.href = 'http://somelink.com';
link.appendChild(document.createTextNode('http://somelink.com'));
After getting pointed in the right direction (By Frank, Thank You) I found a javascript Library that helped me accomplish what I was looking for.
Library
https://github.com/cferdinandi/saferInnerHTML
Here is an example!
https://share.getcloudapp.com/nOuDPnlp
Usage:
saferInnerHTML(message, modifiedMessage, true);
The last param is an option, append or overwrite.
Obviously, I will have to do some CSS work to make them not display as buttons. But it is exactly what I was trying to achieve.

HTML IFrame not allowed to download file

im trying to download a file that constructs itself based on the value it recives. This is my code
<html>
<head>
<script>
var myList=[];
window.onmessage = function(event){
if (event.data) {
myList = event.data;
if (myList.length>0) {
buildHtmlTable();
}
}
else {
myList = [];
}
};
function buildHtmlTable() {
var columns = addAllColumnHeaders(myList);
for (var i = 0 ; i < myList.length ; i++) {
var row$ = $('<tr/>');
for (var colIndex = 0 ; colIndex < columns.length ; colIndex++) {
var cellValue = myList[i][columns[colIndex]];
if (cellValue == null) { cellValue = ""; }
row$.append($('<td/>').html(cellValue));
}
$("#excelDataTable").append(row$);
}
return exportF(); // Make Excel file download now
}
function addAllColumnHeaders(myList)
{
var columnSet = [];
var headerTr$ = $('<tr/>');
for (var i = 0 ; i < myList.length ; i++) {
var rowHash =`enter code here` myList[i];
for (var key in rowHash) {
if ($.inArray(key, columnSet) == -1){
columnSet.push(key);
headerTr$.append($('<th/>').html(key));
}
}
}
$("#excelDataTable").append(headerTr$);
return columnSet;
}
function exportF() {
var table = document.getElementById("excelDataTable");
var html = table.outerHTML;
var url = 'data:application/vnd.ms-excel,' + escape(html);
var link = document.getElementById("downloadLink");
link.setAttribute("href", url);
link.setAttribute("download", "export.xls"); // Choose the file name here
link.click(); // Download your excel file
return false;
}
</script>
</head>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<body onLoad="">
<table id="excelDataTable" border="1">
</table>
<a style="display: none" id="downloadLink"></a>
</body>
</html>
The code itself works, but the error i get is "Download is disallowed. The frame initiating or instantiating the download is sandboxed, but the flag ‘allow-downloads’ is not set. See https://www.chromestatus.com/feature/5706745674465280 for more details."
What can i do to work around this? It feels like ive tried everything i can get my hands on but nothing seems to work for it to download
As the warning message says, you can't initialize downloads from a sandboxed iframe if it doesn't have the allow-downloads permission.
All solutions will imply having access to the page where the iframe is displayed (hereafter "the embedder").
The easiest and recommended way,
is to ask the embedder to add this permission when they define their iframe:
<iframe src="yourpage.html" sandbox="allow-scripts allow-downloads"></iframe>
An other way would be to ask them to not sandbox that iframe at all,
<iframe src="yourpage.html"></iframe>
but I guess that if they did, it's because they don't trust your page enough.
Finally a more complex way would be to pass the generated file back to the parent window.
For this you'd need to define a new API with your clients.
You could obviously just emit a global message event back to them, but I guess the cleanest is to make them pass a MessageChannel's MessagePort along with the myList data, so they can wait for the response there easily and be sure they'll only catch the response and no other unrelated message.
So in the embedder page they'd do
frame.onload = (evt) => {
const channel = new MessageChannel();
// handle the response from the iframe
channel.port2.onmessage = (evt) => {
const file = evt.data;
saveAs( file, "file.html" ); // the embedder is reponsible to initialize the download
};
frame.contentWindow.postMessage( embedders_data, [ channel.port1 ] );
};
And in your page you'd do
window.onmessage = (evt) => {
const myList = evt.data;
// get the MessageChannel's port out of the transfer-list
const port = evt.ports[ 0 ];
// buildHtmlTable has to return the final file, not to make it download
const file = buildHtmlTable( myList );
if( port ) {
port.postMessage( file ); // send back to embedder
}
};
See it as a live plnkr.
Ps: note that your files are not xlsx files but HTML markup.
The correct answer
Under normal circumstances Kaiido's answer is indeed the correct solution to your problem. They will NOT work in your case though.
The answer that will work on WixSince you are using Wix there is no way for you to directly edit the Sandbox attribute of the iframe element. This is just how Wix does things. You can, however, use custom code (only applies to premium websites) to get the class name of the iframe and programatically use javascript to set the new attribute to the existing iframe.
You must use the web inspector to find out the class name (iframes in Wix do not have ids) then add "allow-downloads" to the sandbox attribute. You might then need to reload the iframe using js as well. Go to your website's settings -> Custom Code -> Create custom code at the end of the body tag
If you do not have a premium website then you unfortunately cannot do this. This is due to Wix's own limitations as a platform. If this is an absolute "must" for you project, I recommend you to not use Wix since they limit your freedom as a developer when it comes to working with
technologies that were not made by them. Not to mention that they lock features such as custom elements behind a pay wall. So we can't even test our ideas before committing to a hosting plan. For anyone reading this in the future, take this into consideration and look into other platforms.
Thanks for answers, i didnt find a sollution with the recomended answers. What i did is that i made a completely new page, instead of initializing a html iframe i redirected the current window to the new page i created. The new page took a variable from "www.page.com/?page={value} and downloaded what i needed from there instead. Its messy but it works so if anyone else has this problem i recomend this if you are using wix.

set a Blob as the "src" of an iframe

The following code work perfectly in Chrome
<script>
function myFunction() {
var blob = new Blob(['<a id="a"><b id="b">hey!</b></a>'], {type : 'text/html'});
var newurl = window.URL.createObjectURL(blob);
document.getElementById("myFrame").src = newurl;
}
</script>
But it is not working with IE. Can some one please tell me what is wrong here.
The iframe "src" also set to the blob as shown below.
<iframe id="myFrame" src="blob:0827B944-D600-410D-8356-96E71F316FE4"></iframe>
Note:
I went on the window.navigator.msSaveOrOpenBlob(newBlob) path as well but no luck so far.
According to http://caniuse.com/#feat=datauri IE 11 has only got partial support for Data URI's. It states support is limited to images and linked resources like CSS or JS, not HTML files.
Non-base64-encoded SVG data URIs need to be uriencoded to work in IE and Firefox as according to this specification.
An example I did for Blob as a source of iFrame and working great with one important CAUTION / WARNING:
const blobContent = new Blob([getIFrameContent()], {type: "text/html"});
var iFrame = document.createElement("iframe");
iFrame.src = URL.createObjectURL(blobContent);
parent.appendChild(iFrame);
iFrame with Blob is not auto redirect protocol, meaning, having <script src="//domain.com/script.js"</script> inside the iframe head or body won't load the JS script at all even on Chrome 61 (current version).
it doesn't know what to do with source "blob" as protocol. this is a BIG warning here.
Solution: This code will solve the issue, it runs mostly for VPAID ads and working for auto-protocol:
function createIFrame(iframeContent) {
let iFrame = document.createElement("iframe");
iFrame.src = "about:blank";
iFrameContainer.innerHTML = ""; // (optional) Totally Clear it if needed
iFrameContainer.appendChild(iFrame);
let iFrameDoc = iFrame.contentWindow && iFrame.contentWindow.document;
if (!iFrameDoc) {
console.log("iFrame security.");
return;
}
iFrameDoc.write(iframeContent);
iFrameDoc.close();
}
I've run into the same problem with IE. However, I've been able to get the download/save as piece working in IE 10+ using filesaver.js.
function onClick(e) {
var data = { x: 42, s: "hello, world", d: new Date() },
fileName = "my-download.json";
var json = JSON.stringify(data),
blob = new Blob([json], {type: "octet/stream"});
saveAs(blob, fileName);
e.preventDefault();
return false;
};
$('#download').click(onClick);
See http://jsfiddle.net/o0wk71n2/ (based on answer by #kol to JavaScript blob filename without link)

How to determine when document has loaded after loading external css

How to determine when document has loaded(or is loading) after loading external css?
Normal page has loaded and complete at first time(with using document.onreadystatechange or document.readyStage), but after time script will call function to place a new stylesheet CSS into HTML for changing a background or images. During change stylesheet, document has still stage complete. Stage never has been changed after calling function? Why?
Timeline(example):
Visit one page : localhost/index.html
Document has stage loading
Document has stage complete
User was trying to change a theme, at this time stage hasnt been changed yet.
UPDATE: Without jQuery:)
UPDATE:
Example problem with using one image:
<!DOCTYPE html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<script>
document.onreadystatechange = function(){
console.log(document.readyState);
};
function checkDocumentState(){
console.log(document.readyState);
return setTimeout(function(){
checkDocumentState();
}, 1000);
}
checkDocumentState();
</script>
</head>
<body>
<img src="" onclick="this.setAttribute('src','http://i.imgur.com/uRBtadp.jpg')" style="width:50px; height:50px; background-color:gray; " /> Press empty image and open new image.
</body>
</html>
FOUND ANSWER: How can I tell when a CSS background image has loaded? Is an event fired?
But hopeless .. lack of universality...
CSS is called after DOM elements are populated. This is why in the days of dial up internet, the page would load all funky looking, and then all of a sudden start to develop into the desired page bit by bit. I would suggest using Jquery instead, where you could use the following code to be able to ensure the document is fully loaded and the CSS is already implemented
$document.ready(function() {
//Insert Code here
}
Hope that helps
Answering the question, how to determine the document has loaded after dynamically loading a css file depends upon the different browser vendors out there. There is not a single sure shot way for all the browsers, but lets tackle the problem one by one for each of these browsers.
Preface
var url = "path_to_some_stylesheet.css",
head = document.getElementsByTagName('head')[0];
link = document.createElement('link');
link.type = "text/css";
link.rel = "stylesheet"
link.href = url;
head.appendChild(link);
Once that appending is done:
Internet Explorer : fires readystatechange and load.
Opera : fires load event via onload.
Chrome : Doesnt fire an event but increments document.styesheets.length only after the file has arrived.
Firefox: I was not able to reliably get anything other than mozAfterPaint.
I wrote this code, what i wanted and worked for me:
window.engineLoading = {images_count:0, images_loaded_count:0, fonts_count:0, fonts_loaded_count:0 };
document.querySelector("a").onclick = function(){ // first elemnet a
var before_stylesheets_length = document.styleSheets.length;
var before_fonts_size = document.fonts.size;
document.fonts.onloadingerror = function(a){
window.engineLoading.fonts_loaded_count++;
}
document.fonts.onloading = function(a){
window.engineLoading.fonts_count++;
}
document.fonts.onloadingdone = function(a){
window.engineLoading.fonts_loaded_count++;
}
var head= document.getElementsByTagName('head')[0];
var style= document.createElement('link');
style.rel= 'stylesheet';
style.setAttribute("href","./new_style.css");
style.onload = function(){
for(i=before_stylesheets_length; i<document.styleSheets.length; i++){
var rules = document.styleSheets[i].rules;
for(q=0; q<rules.length; q++){
var styles = rules[q].style;
for(s=0; s<styles.length; s++){
console.log(styles[s]);
if((styles[s] == "background-image" || styles[s] == "background") && styles.backgroundImage.length > 0){
window.engineLoading.images_count++;
var body= document.getElementsByTagName('body')[0];
var image = document.createElement('img');
var url = styles.backgroundImage;
url = url.replace(/^url\(["']?/, '').replace(/["']?\)$/, '');
image.src = url;
image.width = 0;
image.height = 0;
image.setAttribute("class","pace-load-style");
image.onload = function(e){
console.log(e);
window.engineLoading.images_loaded_count++;
};
image.onerror = function(e){
window.engineLoading.images_laoded_count++;
}
body.appendChild(image);
break;
}
}
}
}
};
style.onerror = function(){};
head.appendChild(style);
setTimeout(function(){
checkCurrentState();
}, 1000);
return false;
};
function checkCurrentState(){
if(window.engineLoading.images_count == window.engineLoading.images_loaded_count && window.engineLoading.fonts_count == window.engineLoading.fonts_loaded_count){
console.log("loaded"); return true;
}console.log("still loading...");
return setTimeout(function(){
checkCurrentState();
}, 1000);
};
UPDATE: Scipt has bug on localfile because of empty rule. CSSRules is empty I don't worry about it , and no need fix it.
UPDATE: Mozilla Firefox hasnt reference document.fonts.

link element onload

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

Categories

Resources