How is CSS applied by the browser, and are repaints affected by it?
Asked Answered
W

3

37

Let's say we have an HTML page with a single stylesheet <link>. How does the browser take the rules in this stylesheet and apply it to the HTML? I'm not asking about how to make it faster, I want to know how the rendering itself is handled.

Does it apply each rule one-by-one as it parses the stylesheet and render the result progressively? Or, are the CSS file's contents completely downloaded, then fully evaluated, and then applied to the HTML all at once? Or something else?

I ask this after posting an answer earlier on a question about CSS rule order affecting rendering speed, with the assumption that the styles were rendered as the stylesheet loaded, so the first rules would be applied before the last ones, and not all at once. I'm not sure where I picked up the idea, it's just something I have always thought.

I tried a demo on my server that looked like this:

<!DOCTYPE html>
<html>
<head>
   <title>Test</title>
   <link rel="stylesheet" href="test.css" />
</head>
<body></body>
</html>

test.css contents:

html { background:green }
/* thousands of lines of irrelevant CSS to make the download slow */
html { background:red }

Testing in Firefox 5, I expected to see green at first, then turn to red. It didn't happen. I tried with two separate stylesheets with conflicting rules and got the same results. After many combinations, the only way I got it to work was an inline <style> block in the <head>, with the conflicting rules coming from a <link> in the <body> (the body itself was completely empty except for the link tag). Even using an inline style attribute on the <html> tag, and then loading this stylesheet did not create the flicker that I expected.

Are repaints affected in any way by the CSS, or is the final output applied all at once after the entire stylesheet is downloaded and it's rules computed to what the final output should be? Do CSS files download in paralel with the HTML itself or block it (like script tags do)? How does this actually work?

I am not looking for optimization tips, I'm looking for authoritative references on the subject, so that I can cite them in the future. It's been very difficult to search for this information without turning up tons of unrelated material. Summary:

  • Is all CSS content downloaded before any of it is applied? (reference please)
  • How is this affected by things like @import, multiple <link>s, inline style attributes, <style> blocks in the head, and different rendering engines?
  • Does the download of CSS content block the downloading of the HTML document itself?
Wolter answered 5/8, 2011 at 3:36 Comment(1)
One example of why I care about this: I create a minified CSS file from 10-15 small files. Everything is "namespaced", or using specific enough selectors where the order can be swapped in many cases. I've always included the "less relevant" CSS last, thinking that the styles would be applied last, and that having them be first would make the more important stuff (page layout or common classes for instance) be evaluated later. I have very strong feelings that this is totally irrelevant, but am looking for facts to back this up. An answer to this should answer the linked question as well.Wolter
T
17

How does the browser take the rules in this stylesheet and apply it to the HTML?

Typically this is done in a streaming fashion. The browser reads the HTML tags as a stream, and applies what rules it can to the elements it has seen so far. (Obviously this is a simplification.)

An interesting related Q&A: Use CSS selectors to collect HTML elements from a streaming parser (e.g. SAX stream) (a diversion while I search for the article I have in mind).


Ah, here it is: Why we don't have a parent selector.

We often think of our pages as these full and complete documents full of elements and content. However, browsers are designed to handle documents like a stream. They begin to receive the document from the server and can render the document before it has completely downloaded. Each node is evaluated and rendered to the viewport as it is received.

Take a look at the body of an example document:

<body>
   <div id="content">
      <div class="module intro">
         <p>Lorem Ipsum</p>
      </div>
      <div class="module">
         <p>Lorem Ipsum</p>
         <p>Lorem Ipsum</p>
         <p>Lorem Ipsum <span>Test</span></p>
      </div>
   </div>
</body>

The browser starts at the top and sees a body element. At this point, it thinks it's empty. It hasn't evaluated anything else. The browser will determine what the computed styles are and apply them to the element. What is the font, the color, the line height? After it figures this out, it paints it to the screen.

Next, it sees a div element with an ID of content. Again, at this point, it thinks it's empty. It hasn't evaluated anything else. The browser figures out the styles and then the div gets painted. The browser will determine if it needs to repaint the body—did the element get wider or taller? (I suspect there are other considerations but width and height changes are the most common effects child elements have on their parents.)

This process continues on until it reaches the end of the document.

CSS gets evaluated from right to left.

To determine whether a CSS rule applies to a particular element, it starts from the right of the rule and works it's way left.

If you have a rule like body div#content p { color: #003366; } then for every element—as it gets rendered to the page—it'll first ask if it's a paragraph element. If it is, it'll work its way up the DOM and ask if it's a div with an ID of content. If it finds what it's looking for, it'll continue its way up the DOM until it reaches the body.

By working right to left, the browser can determine whether a rule applies to this particular element that it is trying to paint to the viewport much faster. To determine which rule is more or less performant, you need to figure out how many nodes need to be evaluated to determine whether a style can be applied to an element.


So why was the stylesheet content not applied progressively (green first, then red)?

I think the answer is that external stylesheets are parsed as they are downloaded, but not applied until the entire stylesheet has been parsed. Surely, in parsing a stylesheet, the browser optimizes away unnecessary and redundant CSS rules.

I don't have any proof to back that up right now, but that explanation sounds reasonable to me and agrees with what you're seeing, both with external and inline styles.

Thane answered 5/8, 2011 at 3:45 Comment(4)
I think I have a fair grasp of how repaints are affected by new HTML being fed to the browser, and I have read this article recently, but I think this isn't quite what I'm after, or is it? Why did my test fail? The <html> tag existed before any CSS was applied right? So why was the stylesheet content not applied progressively (green first, then red)? I'm trying to be concise but I'm loaded with questions. In my test there was literally no content but the minimum tags to make the demo. Was I right or wrong in my other post?Wolter
See my edit (at the very bottom of the question). Sorry for dumping in that whole blog post... which you already read. Still, I think it's revelvant if others haven't read it, and I do try to make my answers as self-contained as possible.Thane
No that's great stuff man, and it seems you have come to the same conclusion as I have but with uncertainty. Let me ask you this out of curiosity: Before hearing my results, what would you have expected to happen with the test I performed? I'll be back in the morning for votes and high fives and whatnot, I needed to crank out this question before I got consumed by something else, but ATM I am exhausted.Wolter
On second thought, only external scripts are downloaded sequentially. Other resources can be downloaded in parallel. I think it's just that the browser parses the whole stylesheet as it downloads, but doesn't apply the style until the whole thing is parsed. (Yeah, I need to hit the sack as well...)Thane
T
9

The first and most important thing to understand is that browsers cannot begin painting a page until all CSS is downloaded. (Keep in mind, the W3C spec says that CSS links are only allowed in the head, so when you start linking to stylesheets in the body tag as you did, different browsers will handle this situation differently.)

Now, a web page is read as a stream, and CSS rules are applied to HTML elements as they get fed into the page. To quote the Google article linked below:

As the browser parses HTML, it constructs an internal document tree representing all the elements to be displayed. It then matches elements to styles specified in various stylesheets, according to the standard CSS cascade, inheritance, and ordering rules.

So to now address your questions:

Does it apply each rule one-by-one as it parses the stylesheet and render the result progressively? Or, are the CSS file's contents completely downloaded, then fully evaluated, and then applied to the HTML all at once? Or something else?

Downloads all CSS, then begins painting the document from the top-down.

Testing in Firefox 5, I expected to see green at first, then turn to red. It didn't happen. I tried with two separate stylesheets with conflicting rules and got the same results.

This is because the CSS is all downloaded first, then when it encountered your element it only applied the red style, because of how the cascade works.

After many combinations, the only way I got it to work was an inline <style> block in the <head>, with the conflicting rules coming from a <link> in the <body>

While I cannot say exactly why this happened, I imagine the browser did not look for CSS in the body tag, began painting, encountered the body CSS, then repainted.

Are repaints affected in any way by the CSS?

I would honestly be more worried about JS caused repaints. But if you have a very large DOM, it makes sense to structure your CSS in such a way that you are not causing reflows due to odd positioning. @Matt gave you some good links covering that issue Some good resources:

http://www.dayofjs.com/videos/22158462/web-browsers_alex-russel Alex Russell goes into great detail about 36 minutes in about how webkit parses CSS, how reflows and repaints work, and what triggers them.

http://code.google.com/speed/page-speed/docs/rendering.html This is a basic article on how to optimize CSS rendering

Titi answered 5/8, 2011 at 4:19 Comment(6)
Very interesting video, but I did not find what I was looking for, although the section you highlighted does a good job of explaining CSS selector specificity. To be clear, I'm not asking how to make things faster, but more for an explanation about how/when the CSS styles are actually applied, or how they are fed to the browser or rendering engine from a stylesheet. Your answer: Downloads all CSS, then begins painting the document from the top-down. does sound correct, but I can't seem to find any references or facts that support or deny this, other than my own (probably flawed) experiments.Wolter
@Wesley I did in fact address your question of how and when styles are applied. Styles are applied against the DOM elements from the top down because HTML is taken as a stream. Even more specifically, for each element the browser sees it checks the stylesheet for potential CSS matches (it reads CSS right/left), and then applies the applicable styles (HOW). They do not take effect until all css is downloaded, and that is when the browser starts painting the page (WHEN). In my two links, as well as the snook.ca link from Matt, the facts presented indeed back this up.Titi
The specific part that I'm looking for reference for is: They do not take effect until all css is downloaded. Is this up to the rendering engine, or "just the way it works"? In my test (which seems to support your statements), <html> was the only tag with CSS rules, and was not introduced after the CSS was downloaded, but before (unless you're saying the closing tag was necessary for the element to be discovered). The test also failed with inline style on the html tag which was interesting and unexpected. Also, wondering if the CSS downloading blocks the HTML or not.Wolter
@Wesley re: what downloads in parallel and what doesn't: it's complicated.Thane
@Matt: Being attentive to page speed and yslow, I was aware of the points in this video about how <script> blocks other external resources, but I believe this question is about something else entirely. For example, why didn't my test work when I used two separate <link> tags in the head? Is my question unclear or dumb? Is the answer right here but just not sinking in for me? See my example, in the comment I posted on the question itself, I think it explains one facet of my concerns clearly.Wolter
@Titi if the content is render only when all the style is downloaded,then how can we have the problem of FOUC ? en.wikipedia.org/wiki/Flash_of_unstyled_contentFreightage
D
1

I am not sure about the marked answer. I doubt it's correctness. As per this link from Google Developers the browser first downloads the HTML file and when it sees a CSS file linked to external resource it starts downloading the CSS file while it simultaneously creates the DOM structure for the given HTML file as CSS is not going to affect the DOM. Note that it doesn't apply any styles to the document when the browser is downloading the CSS file.

After downloading the CSS file (assume there is no script files) and if the DOM construction is complete, the browser starts mapping the CSS properties to those nodes in the DOM tree. After this it creates another tree called Render tree which builds all the objects which should be displayed, as rectangle boxes. Only after completing the render tree it starts painting on to the screen.

To summarize:

  • The browser downloads the CSS file completely.
  • The browser doesn't apply any styles to the page when it is downloading. Only after the donwload is complete it starts mapping the rules.
  • The rules are applied only during the render tree construction stage.
  • Downloading the CSS file doesn't block HTML download. You have to note that the browser

First downloads all the html files and then style and script files are downloaded.

You can use the chrome's Developer console to check these. Use the timeline tab to see all this.

A sample of the timeline image is shown here. The link i posted at the beginning of this answer explains everything.

Deflation answered 12/6, 2015 at 4:12 Comment(3)
Also take a look at this website. html5rocks.com/en/tutorials/internals/howbrowsersworkDeflation
I think the OP asked about how css styles will apply not about the order of rendering a html page.Phyto
It would be impossible for CSS to block already requested HTML download - the question is whether parsing/rendering is blocked. And note: the doc you link says that the sequence you summarize is a simplification - much happens in parallel: "For better user experience, the rendering engine will try to display contents on the screen as soon as possible. It will not wait until all HTML is parsed before starting to build and layout the render tree. Parts of the content will be parsed and displayed, while the process continues with the rest of the contents that keeps coming from the network."Randolphrandom

© 2022 - 2024 — McMap. All rights reserved.