【问题标题】:link element onload链接元素加载
【发布时间】:2011-03-05 22:46:26
【问题描述】:

是否有监听 <link> 元素的 onload 事件?

F.ex:

var link = document.createElement('link');
link.rel = 'stylesheet';
link.href = 'styles.css';

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

这适用于<script> 元素,但不适用于<link>。还有其他方法吗? 我只需要知道外部样式表中的样式何时应用于 DOM。

更新:

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

【问题讨论】:

标签: javascript css dom hyperlink dom-events


【解决方案1】:

这是一个跨浏览器的解决方案

// 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 ..... */
}

答案基于this link 说:

幕后发生的事情是浏览器尝试加载 img 元素中的 CSS,因为样式表不是 图片,img 元素会抛出 onerror 事件并执行我们的 功能。幸运的是,浏览器在加载整个 CSS 文件之前 确定它不是图像并触发 onerror 事件。

在现代浏览器中,您可以执行 css.onload 并添加该代码作为回退,以覆盖 2011 年的旧浏览器,当时只有 Opera 和 Internet Explorer 分别支持 onload 事件和 onreadystatechange

注意:我也回答了here,这是我的重复,应该因为我的诚实而受到惩罚:P

【讨论】:

    【解决方案2】:
    // 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);
      });
    };
    

    【讨论】:

      【解决方案3】:

      今天,所有现代浏览器都支持链接标签上的 onload 事件。所以我会保护黑客,例如创建一个 img 元素并设置 onerror:

      if !('onload' in document.createElement('link')) {
        imgTag = document.createElement(img);
        imgTag.onerror = function() {};
        imgTag.src = ...;
      } 
      

      这应该为 FF-8 及更早和旧的 Safari 和 Chrome 版本提供解决方法。

      小更新:

      正如 Michael 所指出的,我们总是希望针对某些浏览器异常应用 hack。在 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
      

      【讨论】:

      • 你能解释一下 img 元素有什么帮助吗?顺便说一句,最新版本的 Chrome 仍然没有链接元素的工作负载。
      • onload 在最新的 Chrome 和 Safari 中对我有用。对于不支持 onload 的浏览器,您可以创建一个 IMG 元素,将 src 设置为样式表。 img 的onerrer 会在文件加载时触发。顺便说一句,我刚刚注意到 'onload' in document.createElement('link') 即使对于不支持 load 事件的 Safari 5 也会返回 true。
      • 那行不通。一旦从服务器收到响应,就会触发 onerror。这发生在完全接收 css 文件之前的一段时间。时间差是接收时间,取决于文件大小。换句话说,您只是检测接收到服务器响应的时间,而不是 CSS 加载完成的时间。
      • 防护似乎不起作用:我在旧版浏览器的链接元素中发现“onload”,它不会触发链接 onload 事件。
      • @Michael 正确,535.23 以下的 Safari 5 和 Webkit 是例外。
      【解决方案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();
      }
      

      演示:http://codepen.io/malyw/pen/AuCtH

      【讨论】:

        【解决方案5】:

        我在 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;
        

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

        【讨论】:

          【解决方案6】:

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

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

          【讨论】:

            【解决方案7】:

            xLazyLoader 插件失败,因为 cssRules 属性对于属于其他域的样式表是隐藏的(破坏了同源策略)。所以你要做的就是比较 ownerNode 和 owningElements。

            下面是对 todo 的详尽解释: http://yearofmoo.com/2011/03/cross-browser-stylesheet-preloading/

            【讨论】:

              【解决方案8】:

              您需要一个您知道样式的特定元素,或者如果您控制 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 变量移到计时器之外,以便获取元素一次。但是,如果您想将处理程序附加到文档中的某个位置(例如头部),您应该保持代码不变。

              注意:在 FF 3+​​、IE 5.5+、Chrome 上测试

              【讨论】:

              • 你为什么每次都投反对票/赞成票/反对票/赞成票?要删除投票,只需再次点击相同的橙色箭头即可。
              • Peter 说:“听起来这正是 OP 所需要的”。 OP说,不,这不是我需要的。 +1 来自彼得,-1 来自我......
              • 我不在乎,只是想提供帮助,但是您首先对其投反对票,然后对其投反对票,然后在同一分钟内再次对其投反对票(之后可能再次投反对票)。我只是有一个“wtf”? :)
              • 很好,但是为什么你也对彼得的帖子投了反对票,它的答案基本上和你一样?是游戏/赌博吗?我发现它老实说是不公平和以自我为中心的;)
              • 因为他提供了一个 overcomplicated jQuery 解决方案,对于一个简单的问题,它臃肿,并且可能不是跨浏览器。一切都与质量有关。投票赞成/反对票的原因是为了推广首选解决方案。我只是想帮助 OP,得到最好的。
              【解决方案9】:

              这个技巧是从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');
              })();
              

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

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

              【讨论】:

              • 旧版浏览器没有 try-catch。 arguments.callee 不再是语言的一部分。 IE没有测试???
              • 不。 IE 不能在我的操作系统上运行,谢天谢地(或者比我愿意投入更多的努力)。作为记录,我认为 onload/onreadystatechange 事件在 IE 中确实有效。而且我没有看到任何要求它可以在这么旧的浏览器中运行。
              • 而被调用者 deprecated 仅作为 js 1.4 中 Function.arguments 的属性,而不是函数的局部参数变量的属性。
              • 您的链接是关于旧版本浏览器供应商网站,而不是当前语言规范。让我帮你。新版本(自2009年12月起):ecma-international.org/publications/files/ECMA-ST/ECMA-262.pdf
              • 是的。这是说同样事情的许多链接之一。您链接到的文档说访问被调用者只会在 in struct mode 中引发错误。这与它“不是语言的一部分”非常不同。
              【解决方案10】:

              因为你不喜欢我的 hack :) 我环顾四周,找到了one by brothercake

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

              【讨论】:

              • 这主意不错,但我需要支持本地文件(XMLHttpRequest在FF 3+​​中不支持本地文件)
              【解决方案11】:

              这是一种 hack,但如果您可以编辑 CSS,您可以添加一个特殊样式(没有可见效果),您可以使用这篇文章中的技术来聆听:http://www.west-wind.com/weblog/posts/478985.aspx

              您需要页面中的元素具有 CSS 将影响的类或 id。当您的代码检测到其样式已更改时,CSS 已被加载。

              正如我所说的:)

              【讨论】:

                猜你喜欢
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 2012-06-08
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                相关资源
                最近更新 更多