loadjs icon indicating copy to clipboard operation
loadjs copied to clipboard

loading css with media="none" first

Open matthiasg opened this issue 9 years ago • 13 comments

Does it make sense to follow the proposal of adding a media="none" attribute to the link and switch that with "all" once loaded ?

See http://keithclark.co.uk/articles/loading-css-without-blocking-render/

Idea being that whenever the css link element is added some other things might still be going on on the page and those might block once the link element is detected.

matthiasg avatar Dec 14 '16 10:12 matthiasg

That's interesting, I hadn't thought about using media="none" to control the parsing of CSS.

If you want, you can use the before callback to set the media attribute value before the <link> element gets added to the page:

loadjs(['/path/to/foo.js', '/path/to/bar.css'], {
  success: function() {},
  error: function(pathsNotFound) {},
  before: function(path, element) {
    /* called for each node before being embedded */
    if (path === '/path/to/bar.css') element.setAttribute('media', 'none');
  }
});

amorey avatar Dec 14 '16 13:12 amorey

@amorey great... this should also work for controlling javascript parsing .. will try

EDIT: adding scriptEl.setAttribute('type', 'preload'); on the before callback seems to disable the success callback sadly. This prevents me from preloading js files but only start executing them when certain other criteria are met

matthiasg avatar Jan 03 '17 14:01 matthiasg

it would be nice to have lading css without blocking render support on load, a lot of papers:

https://gist.github.com/scottjehl/87176715419617ae6994

outaTiME avatar Apr 11 '17 18:04 outaTiME

@outaTiME You can use LoadJS to load a CSS file after the window load event fires:

window.addEventListener('load', function() {
  loadjs('/path/to/file.css');
});

amorey avatar Apr 11 '17 18:04 amorey

Yup, im doing in that way but PageSpeed warns the css loading:

<!DOCTYPE html>
<html>
  <head>
    <script async src="//cdn.rawgit.com/muicss/loadjs/master/dist/loadjs.min.js"></script>
  </head>
  <body>
    <p class="status">Loading...</p>
    <script>
      var time = Date.now();
      window.addEventListener('load', function() {
        loadjs('//web.skycop.com.py/skyweb/build/css/skyweb-13cd7264d6.desktop.css', {
          success: function() {
            console.log('success', Date.now() - time);
            document.querySelector('.status').textContent = 'Load success';
          },
          error: function(pathsNotFound) {
            console.error('error', pathsNotFound);
            document.querySelector('.status').textContent = 'Load error';
          }
        });
      });
    </script>
  </body>
</html>

PageSpeed Insights output:

screen shot 2017-04-11 at 4 43 02 pm

Here my simple example:

http://local.outa.im:8080/desktop/test/non-block-v1.html

outaTiME avatar Apr 11 '17 19:04 outaTiME

@amorey Im trying to understand why async loading in window load event causes a block (as shows in google pagesepeed).

outaTiME avatar Apr 12 '17 05:04 outaTiME

@outaTiME I'm not sure why pagespeed treats it as blocking. Have you tried using the document DOMContentLoaded event instead?

document.addEventListener("DOMContentLoaded", function() {
  loadjs('/path/to/file.css');
});

amorey avatar Apr 12 '17 09:04 amorey

@amorey the DOMContentLoaded event does not guarantee execution after loading of the asynchronous scripts, i tried to do the following and didn't work either:

<!DOCTYPE html>
<html>
  <head>
    <script async src="//cdn.rawgit.com/muicss/loadjs/master/dist/loadjs.min.js" onload="myInit()"></script>
  </head>
  <body>
    <p class="status">Loading...</p>
    <script>
      var myInit = function() {
        var time = Date.now();
        loadjs('//web.skycop.com.py/skyweb/build/css/skyweb-13cd7264d6.desktop.css', {
          success: function() {
            console.log('success', Date.now() - time);
            document.querySelector('.status').textContent = 'Load success';
          },
          error: function(pathsNotFound) {
            console.error('error', pathsNotFound);
            document.querySelector('.status').textContent = 'Load error';
          }
        });
      }
    </script>
  </body>
</html>
screen shot 2017-04-12 at 1 04 24 pm

Here the new example:

http://local.outa.im:8080/desktop/test/non-block-v2.html

outaTiME avatar Apr 12 '17 16:04 outaTiME

I think that the browser will wait to fire the DOMContentLoaded event until all the scripts in the queue are loaded. This means that in your example, the browser will load loadjs.min.js and skyweb-13cd7264d6.desktop.css before the DOMContentLoaded event fires.

Have you tried loading the CSS file with the rel="preload" attribute? https://github.com/filamentgroup/loadCSS/blob/master/README.md

You can use the before callback method to set the attribute:

loadjs('//web.skycop.com.py/skyweb/build/css/skyweb-13cd7264d6.desktop.css', {
  before: function(path, el) {
    el.setAttribute('rel', 'preload');
  },
  success: function() {
    console.log('success', Date.now() - time);
    document.querySelector('.status').textContent = 'Load success';
  },
  error: function(pathsNotFound) {
    console.error('error', pathsNotFound);
    document.querySelector('.status').textContent = 'Load error';
  }
});

amorey avatar Apr 13 '17 05:04 amorey

hi again @amorey, i dont think so, take a look my first example using the load event, this event fires after DOMContentLoaded, yup preaload rule fix the warning but only for few browsers (https://caniuse.com/#search=preload).

The best efforts using loadCSS was (without pollyfill):

<link rel="preload" href="path/to/mystylesheet.css" as="style" onload="this.rel='stylesheet'">

its possible to took el reference in success callback? to execute the onload script?

outaTiME avatar Apr 18 '17 19:04 outaTiME

The DOM element doesn't get passed into the success callback but you can attach an id in the before callback and fetch it in the success callback. You can also cache a reference to it in before and use it in success.

amorey avatar Apr 18 '17 20:04 amorey

@amorey — Have you tried changing the rel attribute to rel="preload" in the before callback? Because I can't get it to work properly…

The way to do it adds an onload handler, changing the rel back to stylesheet.

Note: support for rel="preload" is assumed. You should feature-detect it (and with no support skip running all this code in before(). To test support I used the simple FilamentGroup check, but you could go further with DOMTokenList support.

The thing is, of course, that stylesheets downloaded with rel="preload" as="style" get downloaded, but not applied to the page. Changing to rel="stylesheet" is required to apply the styles.

Now: I've ran into strange issues adding the onload event handler to the e element:

  • It seems both the preload and the stylesheet (in my onload) trigger onload: my stylesheet is downloaded multiple times.
  • Even after changing to rel="stylesheet" my styles are not applied. The link is right there in the HTML (rel="stylesheet"), but my styles are not applied.

Any thoughts?

davidhund avatar May 18 '17 21:05 davidhund

@davidhund You can set rel="stylesheet" in the success callback after the CSS file loads:

var linkEl;

loadjs('//maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css', {
  before: function(path, el) {
    el.rel = "preload";
    el.as = "style";
    linkEl = el;
  },
  success: function() {
    linkEl.rel = "stylesheet";
  }
});

Here's an example with JSFiddle: https://jsfiddle.net/muicss/9qLjbc9e/

Let me know if that helps!

amorey avatar May 19 '17 04:05 amorey