Capturing load event on LINK
I'm trying to attach an event handler to the load event of a link tag, to execute some code after a stylesheet has loaded.
new_element = document.createElement('link');
new_element.type = 'text/css';
new_element.rel = 'stylesheet';
new_element.href = 'http://domain.tld/file.css';
new_element.addEventListener('load', function() { alert('foo'); }, false);
document.getElementsByTagName('head')[0].appendChild(new_element)
I have tried onreadystatechange
as well:
new_element.onreadystatechange = function() { alert('foo'); }
Unfortunately neither approach results in an alert being triggered.
Furthermore, new_element.onload
is null after registering a handler for the load
event with addEv开发者_开发问答entListener
. Is that normal?
PS: I may not use any framework in solving this.
Here's what is, in my opinion, a better solution for this issue that uses the IMG tag and its onerror event. This method will do the job without looping, doing contorted style observance, or loading files in iframes, etc. This solution fires correctly when the file is loads, and right away if the file is already cached (which is ironically better than how most DOM load events handle cached assets). Here's a post on my blog that explains the method - Back Alley Coder post - I just got tired of this not having a legit solution, enjoy!
var loadCSS = function(url, callback){
var link = document.createElement('link');
link.type = 'text/css';
link.rel = 'stylesheet';
link.href = url;
document.getElementsByTagName('head')[0].appendChild(link);
var img = document.createElement('img');
img.onerror = function(){
if(callback) callback(link);
}
img.src = url;
}
For CSS stylesheets (not LINK elements in general) i'm using manual interval, by poking it's rules length. It works crossbrowser (AFAIT).
try {
if ( cssStylesheet.sheet && cssStylesheet.sheet.cssRules.length > 0 )
cssLoaded = 1;
else if ( cssStylesheet.styleSheet && cssStylesheet.styleSheet.cssText.length > 0 )
cssLoaded = 1;
else if ( cssStylesheet.innerHTML && cssStylesheet.innerHTML.length > 0 )
cssLoaded = 1;
}
catch(ex){}
In code above, the cssStylesheet is DOMElement.
Even if you add an inline:
<link rel="stylesheet" type="text/css" href="foo.css" onload="alert('xxx')"/>
It won't fire in FireFox as there isn't an onload
event for link
elements. (It will work in IE)
All credit goes to Tobiasz up above, but here's a little expansion on what he said:
function _cssIsLoaded(cssStylesheet) {
var cssLoaded = 0;
try {
if ( cssStylesheet.sheet && cssStylesheet.sheet.cssRules.length > 0 )
cssLoaded = 1;
else if ( cssStylesheet.styleSheet && cssStylesheet.styleSheet.cssText.length > 0 )
cssLoaded = 1;
else if ( cssStylesheet.innerHTML && cssStylesheet.innerHTML.length > 0 )
cssLoaded = 1;
}
catch(ex){ }
if(cssLoaded) {
// your css is loaded! Do work!
// I'd recommend having listeners subscribe to cssLoaded event,
// and then here you can emit the event (ie. EventManager.emit('cssLoaded');
} else {
// I'm using underscore library, but setTimeout would work too
// You basically just need to call the function again in say, 50 ms
_.delay(_.bind(this._cssIsLoaded, this), 50, cssStylesheet);
}
}
You'd call it with something like (using jquery):
var link = $("<link>");
link.attr({
type: 'text/css',
rel: 'stylesheet',
href: sheet
});
$("head").append(link);
// link.get(0), because you want the actual element, not jQuery-wrapped element
self._cssIsLoaded(link.get(0));
The link.innerHTML, link.styleSheet and cssRules are all good approaches, but they do not work for stylesheets that belong to a domain outside of the origin domain (so cross-site stylesheet loading fails). This can be pretty unexpected when a subdomain vs a domain is used (www for example) or a static CNS is used. And its pretty annoying since elements have no same-origin restriction.
Here's a solution that that uses the onload method for browsers that support it (IE and Opera), but then uses a timed interval for browsers that do not and compares the ownerNode and owningElement nodes to check to see when the stylesheet has made its way into the DOM.
http://www.yearofmoo.com/2011/03/cross-browser-stylesheet-preloading.html
This function has held up on all browsers as well as in both cross domain and same domain situations, also this handles the injection of javascripts as well as stylesheets.
function loadScript(src, type, callback_fn) {
var loaded = false, scrpt, img;
if(type === 'script') {
scrpt = document.createElement('script');
scrpt.setAttribute('type', 'text/javascript')
scrpt.setAttribute('src', src);
} else if(type === 'css') {
scrpt = document.createElement('link')
scrpt.setAttribute('rel', 'stylesheet')
scrpt.setAttribute('type', 'text/css')
scrpt.setAttribute('href', src);
}
document.getElementsByTagName('head')[0].appendChild(scrpt);
scrpt.onreadystatechange = function(){
if (this.readyState === 'complete' || this.readyState === 'loaded') {
if(loaded === false) {
callback_fn();
}
loaded = true;
}
};
scrpt.onload = function() {
if(loaded === false) {
callback_fn();
}
loaded = true;
};
img = document.createElement('img');
img.onerror = function(){
if(loaded === false) {
callback_fn();
}
loaded = true;
}
img.src = src;
};
Nice shot Matt. I've created this helper with your comment.
var CSSload = function(link, callback) {
var cssLoaded = false;
try{
if ( link.sheet && link.sheet.cssRules.length > 0 ){
cssLoaded = true;
}else if ( link.styleSheet && link.styleSheet.cssText.length > 0 ){
cssLoaded = true;
}else if ( link.innerHTML && link.innerHTML.length > 0 ){
cssLoaded = true;
}
}
catch(ex){ }
if ( cssLoaded ){
callback();
}else{
setTimeout(function(){
CSSload(link);
}, 100);
}
};
Usage:
var $link = $('<link rel="stylesheet" media="screen" href="file.css"/>');
$('head').append($link);
CSSload($link.get(0), function(){
// do something onLoad
});
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
This method suits me better.
// Create a <link> and set it in an HTML node
const
$link = document.createElement("link");
$link.setAttribute('rel', 'stylesheet');
$link.setAttribute('href', '/build/style/test.css');
document.head.appendChild($link);
// Event about load
onloadCSS($link).then(function(url) {
console.log('CSS loaded: ' + url);
}).catch(function(e) {
console.log('Failed to load CSS');
});
function onloadCSS($link){
return new Promise(function(resolve, reject) {
const
options = {
headers: ({'Accept': 'text/css,*/*;q=0.1'})
},
url = $link.getAttribute('href');
fetch(url, options).then(function(response) {
if(response.status === 200){
return resolve(url);
}
return reject({status: response.status});
}).catch(function(e) {
return reject(e);
});
});
}
An href does not have a load event you need to apply your stuff as the page itself loads e.g.
window.onload = function(){
//code here
}
use the function on this page: http://www.dustindiaz.com/top-ten-javascript/
to add the event the body element (in line would be )
精彩评论