链接元素的onload事件

51
有没有办法监听 <link> 元素的 onload 事件?
例如:
var link = document.createElement('link');
link.rel = 'stylesheet';
link.href = 'styles.css';

link.onload = link.onreadystatechange = function(e) {
    console.log(e);
};

这个方法可以用于<script>元素,但不适用于<link>。有没有其他方法?

更新:

是否可以注入一个隐藏的 <iframe>,将 <link> 添加到头部,并在 iframe 中监听 window.onload 事件?这应该会在 CSS 加载时触发,但可能不能保证在顶层窗口中加载...


类似的问题可以在这里找到:https://dev59.com/aUfRa4cB1Zd3GeqP8V7j - viam0Zah
11个回答

24
今天,所有现代浏览器都支持在链接标签上使用 onload 事件。因此,我建议避免使用 hack 方法,例如创建 img 元素并设置 onerror 属性:
if !('onload' in document.createElement('link')) {
  imgTag = document.createElement(img);
  imgTag.onerror = function() {};
  imgTag.src = ...;
} 

这应该为FF-8及早期版本以及旧版Safari和Chrome提供一种解决方法。

小更新:

正如Michael指出的那样,有些浏览器例外情况需要始终应用此解决方法。使用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

1
你能解释一下img元素如何帮助吗?顺便说一句,最新版本的Chrome仍然没有为链接元素工作的onload。 - NoBugs
4
在最新版本的Chrome和Safari中,onload 对我有效。对于不支持 onload 的浏览器,您可以创建一个IMG元素,将src设置为样式表。 当文件加载时,img的 onerror 将被触发。 顺便说一下,我刚刚注意到,即使是不支持 load 事件的Safari 5,在 document.createElement('link')'onload' 返回 true。 - mlangenberg
5
不行。在从服务器接收到响应之后,onerror 就会被触发。这通常发生在 css 文件完全接收之前,时间差就是接收所需的时间,这取决于文件大小。换句话说,你只是检测到了服务器响应接收的时间,而不是 CSS 加载完成的时间。 - gztomas
守卫似乎不起作用:我在一个旧浏览器的链接元素中找到了'onload',但该事件并未被触发。 - Michael
@Michael 正确,Safari 5和Webkit版本低于535.23的是例外情况。 - mlangenberg

9
这是一种比较巧妙的方法,但如果你能够编辑CSS,就可以添加一个特殊的样式(没有可见效果),然后使用这篇文章中介绍的技术进行监听:http://www.west-wind.com/weblog/posts/478985.aspx 你需要在页面中添加一个具有类或ID的元素,以便CSS可以影响它。当你的代码检测到其样式已更改时,CSS已经被加载。
这是一种hack方法,如我所说的 :)

4

我在Chrome上的做法(其他浏览器未经测试)是使用Image对象加载CSS并捕获其onerror事件。问题在于浏览器不知道这个资源是不是图片,因此它仍会尝试获取它。但是,由于它实际上不是一张图片,因此会触发onerror处理程序。

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;

请注意,如果资源已经被获取,这个请求将会命中缓存。

4
例如,Android浏览器不支持为元素绑定“onload” / “onreadystatechange”事件:http://pieisgood.org/test/script-link-events/
但它会返回以下结果:
"onload" in link === true

所以,我的解决方案是从userAgent中检测Android浏览器,然后等待样式表中的特殊css规则(例如"body"边距的重置)。
如果不是Android浏览器且支持"onload"事件,我们将使用它:

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


2

既然您不喜欢我的黑客方式 :),我就找了其他的方法,发现了brothercake的方法

基本上,建议使用AJAX获取CSS以使浏览器缓存它,然后将链接加载视为瞬间完成,因为CSS已缓存。这可能并不总是有效(例如,某些浏览器可能已关闭缓存),但几乎总是有效的。


这不是一个坏主意,但我需要支持本地文件(XMLHttpRequest在FF 3+中不支持本地文件) - David Hellsing

1

另一种方法是检查加载了多少样式表。例如:

使用“css_filename”作为CSS文件的URL或文件名,“callback”作为CSS加载完成后的回调函数:

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);
}

1
// 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);
  });
};

0

你需要一个具有已知样式的特定元素,或者如果你控制CSS文件,可以插入一个虚拟元素来实现这个目的。这段代码将确保在CSS文件的内容应用到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"); 
});

如果您将此代码放在文档底部,您可以将elcomp变量移出计时器以便一次性获取元素。但是,如果您想在文档中的其他位置(如head)附加处理程序,则应保留代码不变。
注意:已在FF 3+、IE 5.5+、Chrome上测试。

1
你为什么一直点赞/踩/取消点赞/取消踩?要取消投票,只需再次点击相同的橙色箭头即可。 - BalusC
Peter说:“听起来这正是OP所需要的。” OP说,不,这不是我需要的。Peter加1,我减1... - gblazex
1
我不在意那个,只是想要帮忙,但你先是在同一分钟内先点了踩,然后又点了赞,接着又点了踩(或许之后还有一次)。我只是感到很困惑。 :) - BalusC
2
好的,但是你为什么也要给彼得的帖子点踩呢?他的回答和你的基本相同。这是在玩弄/赌博吗?我觉得这样做不公平且自我中心。;) - BalusC
因为他提供了一个过于复杂的jQuery解决方案,这对于一个简单的问题来说是臃肿的,并且可能不跨浏览器。这一切都关乎质量。投票的原因是为了推广优选的解决方案。我只是想帮助OP,找到最好的解决方案。 - gblazex

0

这个技巧借鉴自xLazyLoader jQuery插件

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');
})();

已在本地测试并在FF(3.6.3)和Chrome(linux-6.0.408.1 dev)中正常工作

演示此处(请注意,这不适用于跨站点css加载,如在FF下的演示中所做的那样)


旧版浏览器不支持try-catch。arguments.callee已经不再是该语言的一部分了。IE没有测试吗? - gblazex
不,IE不能在我的操作系统上运行,谢天谢地(或者说需要的努力超出了我的意愿)。不过我想声明一下 onload/onreadystatechange 事件在IE中是可以工作的。而且我也没有看到它必须在那么老的浏览器中工作的要求。 - sje397
1
而callee仅作为js 1.4中Function.arguments的属性,而不是函数本地arguments变量的属性。此外,它已被弃用 - sje397
你的链接是一个有关于旧版本的浏览器供应商网站,而不是目前的语言规范。让我来帮你。新版本(自2009年12月起):http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-262.pdf - gblazex
@galambalazs - 五年过去了,严格模式仍然是可选的 :-P - schlingel
显示剩余6条评论

0

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接