Skip to content

Instantly share code, notes, and snippets.

@igrigorik
Created June 15, 2012 07:48
Show Gist options
  • Save igrigorik/2935269 to your computer and use it in GitHub Desktop.
Save igrigorik/2935269 to your computer and use it in GitHub Desktop.
mobile first css loading
<!DOCTYPE html>
<html>
<head>
<meta name="description" content="test for paint vs onload css behavior" />
<meta charset="utf-8" />
<title>Awesome mobile-first site</title>
<!--
Base stylesheet across all devices, loads first, and *needs* to be blocking to avoid
showing unstyled content.
Script injected stylesheets are marked as non-blocking in FF and can result in a self
inflicted FOUC. WebKit sister bug: https://bugs.webkit.org/show_bug.cgi?id=88869
Providing the link element in the source also allows the preloader to pick it up as soon
as it gets the first bytes of the actual page. For *best* CSS performance, you should
try to serve this element within the first 1480 bytes (first packet) of the response.
This also must take into account HTTP headers (cookies can be up to 4k).
Alternatively, the server can return a "Link" http header with URL to the stylesheet. Ex:
Link: <base.css>; rel="stylesheet"; title="base"
-->
<link rel='stylesheet' href='css/base.css' title='base'>
<!--
If we're on mobile (small screen), and have low DPI, ideally neither of the below sheets
should be loaded. Unfortunately today that's not the case.
Current WebKit behavior:
- Any "complex" query is assumed to be "screen" device type by preloader
- Preloader will schedule the download
- Parser will evaluate the media query and mark both stylesheet as NonBlocking
if the media queries are evaluated to false. Hence, neither will block the
painting of the page on the screen, but we *will* download the assets.
Also, if we are on desktop browser, we don't want to write these in via doc.write because
of behavior we saw above: stylesheets injected via scripts are marked as non-blocking in FF.
(Perhaps the FF behavior should be changed)
-->
<link rel='stylesheet' href='css/desktop-base.css' media='(min-width: 1600px)' title='hires-desktop'>
<link rel='stylesheet' href='css/desktop-hires.css' media='(-webkit-device-pixel-ratio: 2.0)' title='hires-desktop'>
<!--
Preloader will only schedule resources targeted to "screen" media. Hence, print is
marked as NonBlocking and will not block rendering.
-->
<link rel='stylesheet' href='css/print.css' media='print' title='print' disabled>
</head>
<body>
Hello mobile first world!
<!--
Stylesheets marked as "disabled" when toggled will not fetch the content: http://jsbin.com/3/omulof/2/quiet
If the stylesheet is marked as disabled via JS, then subresources (url references, etc) are not fetched and
when we toggle the stylesheet, they are successfully fetched: http://jsbin.com/3/omulof/3/quiet
Disabling the stylesheet via JS does not cancel the preloader fetch - perhaps worth thinking about?
-->
<script type="text/javascript">
// if marking a stylesheet as disabled would cancel the download
// then we could avoid downloading extra resources on mobile clients
// Ex..
function onOnload() {
// don't download print stylesheet
document.styleSheets.item('print').disabled = false;
// cancel desktop downloads if media queries do not match
if (window.matchMedia("(-webkit-device-pixel-ratio: 2.0)").matches
&& window.matchMedia("(min-width: 1600px)").matches) {
document.styleSheets.item('hires-desktop').disabled = false;
}
}
if (window.addEventListener)
window.addEventListener("load", onOnload, false);
else if (window.attachEvent)
window.attachEvent("onload", onOload);
else window.onload = onOnload;
</script>
</body>
</html>
  • Non-screen stylesheets (ex, print) are not an issue: they do not block rendering or DCL events (ex)
  • Media queries which evaluate to false do not block rendering or DCL event (ex)
  • Alternate stylesheets do not block rendering or DCL events (ex)
  • Script injected stylesheets should follow same behavior: if media query is false then NonBlocking; blocking otherwise

There is currently no way to have a element in the Document and have the browser conditionally load it (the download always happens). The only way around this is to inject the link element via JS on the client. It is also not clear that the browser should skip the download: if you have two stylesheets for portrait and landscape and we only load portrait, then rotating the device would result in a slow resource fetch and a poor user experience. That is, unless we assume that this is something the designer would have to take into account and set their breakpoints + file boundaries such that they avoid these jarring experiences. (IMHO, this seems reasonable).

Ex: make sure mobile.css contains all the necessary breakpoints, but if a user docks his mobile phone to a larger screen/projector, then we can trigger the downloads, since that's a significant step-change in the nature of the device (and is likely to cause window manager changes as well).

If above scenario is valid, then the browser could (in theory) be extended to have preloader+parser lazy-load the resources for media query breakpoints. Otherwise, the browser behavior is good as is, and we would have to rely on client-side code to manage this process and conditionally + dynamically inject stylesheets into the document. Which works, but is unlikely to be a popular pattern, since it places the burden on the developer to make it work, and also hides all of the resource information from the parser and preloader, which is a poor long-term plan as it doesn't allow us to introduce further optimizations.


Preloader schedules all link assets that evaluate to "screen" media type

  • complex media queries are implicitly assumed to target "screen" (not actually evaluated)
  • non screen media types do not block the parser or DCL events, ex: http://jsbin.com/3/omulof/6/quiet

Non-blocking stylesheets loading after DCL

  • Loading of non-blocking stylesheets is deferred until after DCL event: http://jsbin.com/3/omulof/7/quiet
  • Seems like we can improve this case. If pre-loader reached EOF, and the parser is blocked, we could schedule the pending downloads to accelerate onload?

Preloader + Media Queries

  • Extend the logic in preloader to evaluate (at least some) of the complex media queries and disable download?
  • Evaluating media queries in preloader requires a lot of work. Alternative: remove pending downloads in parser after eval.

Bug: Disabled stylesheets are not downloaded when enabled

@BenjaminRCooper
Copy link

With regards to delivering content within the first 14kb, have you ever thought about using something like phantom to grab the above the fold content as a screenshot and displaying that as an image and then deferring all resources?

You could blur the image and then remove it once the CSS and any content dependent JS has loaded.

@samkatakouzinos
Copy link

https://gist.github.com/igrigorik/2935269#file-mobile-first-html-L81
Should read;

window.attachEvent("onload", onOnload);

currently onOnload is written as onOload

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment