The HTML parser is very forgiving. When we violate HTML rules, it does its best to figure out what we meant and to present some output that might be what we’re looking for. Unfortunately, there is a performance penalty for that extra effort. For best performance, use well-formed XHTML instead of HTML.
Reduce the size of the content tree, especially its depth.
The style structure differs from browser engine to browser engine. There is no explicit or de facto standard, but that’s okay because there is no public API into it. Only the parser and the rendering engine need to understand it.
Minimize the number of CSS rules. Eliminate duplicated and unused rules.
After the page is completely loaded, it’s a different story. See the Reflow section in Part XIII for tips we can use after the page loads (i.e., in response to events).
Avoid HTML tables. Never use them for formatting; use <div>s instead. If you must use them, keep them as small as possible. Layout can almost always be calculated in a single pass, but HTML tables may need two passes.
There is no correspondence between the content tree’s structure and the render tree’s. The content tree’s structure reflects the document’s HTML structure. The render tree’s structure reflects the positioning of the frames seen by the end-user, left-to-right and top-to-bottom. These two structures can be quite different.
Some nodes found in the content tree are not found in the render tree. For example, the head node will not appear in the render tree because it requires no rendering. Another example: Content tree nodes that are not visible (e.g., display:none in CSS) are not included in the render tree.
Some nodes found in the render tree are not found in the content tree. For example, each line of text requires a separate node in the render tree, but they are lumped together into one text node in the content tree.
Did you notice how much repetition there is? Most frames are built and rebuilt several times. This is an example of the repetition mentioned in Build the Render Tree above. Each change can trigger any number of other changes.
Rendering does not really happen after the previous step. It can begin as soon as the layout engine puts something into the render tree. Then it runs concurrently with the parser and layout engine.
If the rendering engine responds too slowly to changes in the rendering tree, that is an obvious performance issue. However, responding too quickly can also become a performance issue. If a script or stylesheet changes previously-rendered nodes, the rendering engine can find itself rendering, re-rendering, re-re-rendering, and so on. [This is actually much more common than one might expect.]
Specify all formatting in the <head> and specify all content in the <body>. If a document provides formatting information after the content to which it applies, the rendering engine will use default formatting rules when it encounters the content. Later, when the layout engine sees the new formatting information, it will relayout the frame and all affected frames, then the rendering engine will have to do all its time-consuming work again.
If the layout engine were to wait a few milliseconds for the content tree and style structure to settle down into their final state, it can do the layout once rather than multiple times. This will trigger one change to the render tree instead of multiple changes, which will make the rendering engine do its job once instead of multiple times.
In fact, some browsers do improve performance by purposely delaying rendering, but in one way they are at the mercy of the programmer. If the script asks for current layout information, the layout engine has to immediately process all the queued frames before it can answer the question. By clearing the queue, answering the question eliminates the performance improvements that could have been. Programmers should avoid code that queries the size (height or width) or position (top or left) of any element.
In the grand scheme of things, it’s not really onLoad that determines when a document is interactive, it’s the end-user. Some elements must be present and fully functional before the end-user will consider the page ready to go. Other elements aren’t quite so urgent. They can wait a few milliseconds, or perhaps even a few hundred milliseconds.
The developer must be aware of the end-user’s thought processes. He must know what the user needs immediately vs. what he needs in a second or two. Example: A typical user needs to see the top of the document sooner than the bottom of the document. The top is therefore needed-now and the bottom is therefore needed-soon. Other elements that may be needed-soon or needed-later: footers, advertising, dynamic content, images, etc.
Code that creates needed-later elements can be executed after onLoad fires. We note that this is a perceived performance improvement, not a real one. However, from the end-user’s perspective, it is very real. He can get on with what he wants to do sooner. He may not even notice that some elements have not been fully created yet.
If, however, most users do notice the delay in an element, then we have incorrectly classified the element as needed-later when it is really needed-now. Performance must always relate back to the end-user’s wait time.
Part XIII tells how post-loading, event-driven scripts affect performance. Watch for it on the Monitor.Us Blog.