Translated from: HowJavaScript works:the rendering engine and tips to optimize its performance
This is the 11th of a series on exploring JavaScript and its building components. In the process of identifying and describing core elements, we share some of the rules of thumb used when building sessionstack . Sessionstack is a robust and high-performance JavaScript application that helps users to see and reproduce the flaws of their WEB applications in real time.
When building a WEB application, you're not just writing a piece of JavaScript code that runs independently. The JavaScript you write needs to interact with the environment. Understanding how the environment works and what it is made of, you will be able to build better applications and better handle the potential problems that will manifest after the application is published.
In this article, we'll focus on the rendering engine because it handles parsing and visualization of HTML and CSS, which is where most JavaScript applications continue to interact.
The primary responsibility of the rendering engine is to display the requested page on the browser screen.
The rendering engine can display html/xml documents and images. If you use another plugin, it can also display different types of documents, such as PDFs.
Like the JavaScript engine, different browsers also use different rendering engines. Common to have these:
The rendering engine receives the contents of the requested document from the network layer.
The first step in the rendering engine is to parse the HTML document and convert the parsed elements into the actual Dom nodes in the DOM tree .
Build CSSOMCSSOM refers to the CSS object Model . When the browser builds the DOM of the page, it head
encounters a label that references an external theme.css
CSS style sheet link
. The browser anticipates that it may need the resource to render the page, so it makes a request immediately. Let's assume that theme.css
the file contains the following content:
body { font-size: 16px;}p { font-weight: bold; }span { color: red; }p span { display: none; }img { float: right; }
Like HTML, the engine needs to convert the CSS into something--cssom the browser can use. Here's what the CSSOM tree looks like:
Do you know why CSSOM is a tree-like structure? When the final style set of an object on a page is computed, the browser starts with the most general rule applicable to that node (for example, if it is a child of the BODY element, all of the body's styles are applied), then recursively thinning, and the style is calculated by applying more specific rules.
Let's take a look at concrete examples. Any text in a label contained within an element has a body
span
font size of 16 pixels and is red. These styles are body
inherited from elements. If an span
element is a p
child of an element, its contents are not displayed because it is applied to a more specific style ( display: none
).
Also note that the tree above is not a complete CSSOM tree, but only shows the styles we have decided to override in the style sheet. Each browser provides a default set of styles, also known as user-agent styles, that we see when we don't explicitly specify any style. Our styles override these default values.
Build a render TreeThe view directives in HTML are combined with the style data in the CSSOM tree to create a render tree .
You might ask what a render tree is. A rendering tree is a tree structure that is composed of visual elements in the order in which they appear on the screen. It is a visual representation of HTML and the corresponding CSS. The purpose of this tree is to draw the content in the correct order.
The nodes in the render tree are called renderers or render objects in Webkit.
This is what the shader tree looks like for the DOM and CSSOM trees above:
In order to build the render tree, the browser has done roughly the following:
- Starting from the root of the DOM tree, the browser iterates through each visible node. Some nodes are not visible (such as script, meta, etc.) and are ignored because they do not require rendering. Some nodes that are hidden through CSS are also omitted from the render tree. For example, a span node-in the above example, it does not exist in the render tree because we explicitly set the property on it
display: none
.
- For each visible node, the browser finds the appropriate CSSOM rules and applies them.
- The browser outputs a visible node with content and its computed style
Here you can see the source code for RenderObject (in WebKit):https://github.com/WebKit/webkit/blob/ Fde57e46b1f8d7dde4b2006aaf7ebe5a09a6984b/source/webcore/rendering/renderobject.h
Let's take a look at some of the core elements of this class:
class RenderObject : public CachedImageClient { // Repaint the entire object. Called when, e.g., the color of a border changes, or when a border // style changes. Node* node() const { ... } RenderStyle* style; // the computed style const RenderStyle& style() const; ...}
Each renderer represents a rectangular area, usually corresponding to a node's CSS box model. It contains geometry information, such as width, height, and position.
Layout of the Render treeWhen the renderer is created and added to the tree, it does not have a location or size. The process of calculating these values is called layout.
HTML uses a flow-based layout model, which means that it can calculate the layout for most of the time in one traversal (single pass). The coordinate system is relative to the root renderer, using the upper-left origin coordinates.
A layout is a recursive process-it starts with the root renderer, corresponds to an element of an HTML document , and recursively calculates layout information for each renderer that needs a layout through a hierarchy of parts or the entire renderer.
The location of the root renderer is the size of the 0,0
visible part of the browser window (also known as a viewport).
Starting the layout process means giving each node exactly the coordinates it should appear on the screen.
Draw a render treeAt this stage, the browser traverses the renderer tree and invokes the renderer's method to display the content on the paint()
screen.
The drawing can be global or incremental (similar to layout):
- Global --the whole tree is redrawn
- Incremental -only some renderers change in a way that does not affect the entire tree. The renderer marks its rectangular area on the screen as invalid, which causes the operating system to treat it as
paint
an area where the event needs to be redrawn and generated. The operating system completes the drawing by merging several areas into a single area of intelligent mode.
In general, it is important to understand that drawing is a gradual process. For a better user experience, the rendering engine tries to display content on the screen as soon as possible. It won't wait until all the HTML is parsed to start building and laying out the render tree. A small portion of the content is parsed and displayed, while progressively rendering the rest of the content from the network.
Order of processing scripts and style sheetsWhen the parser arrives at <script>
the tag, the script is parsed and executed immediately. The parsing of the document will be paused until the script finishes executing. This means that the process is synchronous .
If the script is external, it must first be fetched from the network (also synchronized). All parsing stops until the network request is complete.
HTML5 adds an option to mark the script as asynchronous, when the script is parsed and executed by another thread.
Optimize rendering PerformanceIf you want to optimize your app, then you need to focus on five main areas. These are the places you can control:
- JavaScript -In previous articles, we covered topics on writing high-performance code that did not block the UI, and that memory efficiency was high, and so on. When it comes to rendering, we need to consider how JavaScript code interacts with DOM elements on the page. JavaScript can generate a lot of updates in the UI, especially in the SPA.
- style Calculations -This is the process of determining which CSS rule applies to which element, based on the matching selector. Once a rule is defined, the rules are applied and the final style of each element is calculated.
- Layout -Once the browser knows which rules apply to the element, it can begin to calculate the space occupied by the latter and where it is located on the browser screen. The layout model of the WEB defines an element that can affect other elements. For example,
<body>
the width affects the width of child elements, and so on. All this means that the layout process is computationally intensive. The drawing is done on multiple layers.
- Draw --This starts to populate the actual pixel. The process includes drawing text, colors, images, borders, shadows, and so on-each visual part of each element.
- compositing -because page parts are divided into multiple layers, they need to be drawn to the screen in the correct order to render the page correctly. This is important, especially for overlapping elements.
Optimize your JavaScriptJavaScript often triggers visual changes in the browser, especially when building a SPA.
Here are some tips on what parts of JavaScript can be optimized to improve rendering performance:
- Avoid using
setTimeout
or setInterval
making view updates. These will be called at an indeterminate point in time in the frame callback
, possibly at the end. What we want to do is to trigger a visual change at the beginning of the frame rather than miss it.
- Move long-running JavaScript computing tasks to the Web Workers, as we discussed earlier
- Use a micro task to change the DOM in multiple frames. This is to handle the case where the task in the Web worker requires access to the DOM, and the Web worker does not allow access to the DOM. That means you can break a big task into small tasks and
requestAnimationFrame
run them in, or, depending on the nature of the task setTimeout
setInterval
.
Optimize your CSSModifying the DOM by adding and removing elements, changing properties, and so on can cause the browser to recalculate element styles and, in many cases, re-layout the entire page or at least part of it.
To optimize rendering performance, consider the following methods:
- Reduce the complexity of selectors. Rather than building the style itself, a complex selector can increase the time it takes to calculate an element's style by 50%.
- Reduce the number of elements that must be calculated for the style. In essence, style changes are made directly to several elements, rather than making the entire page invalid.
Optimizing layoutsThe recalculation of the layout can be very stressful for the browser. Please consider the following optimizations:
- Reduce the number of layouts as much as possible. When you change the style, the browser checks to see if the layout needs to be recalculated. Changes to properties, such as width, height, left, top, and other geometry-related properties, require a re-layout. So try to avoid changing them.
- Try
flexbox
to use rather than the old layout model. It runs faster and can create huge performance benefits for your application.
- Avoid forcing a synchronized layout. It is important to note that all old layout values in the previous frame are known and can be queried while JavaScript is running. If you query
box.offsetHeight
is not a problem. However, if you change the style of the element before querying the element (for example, adding some CSS classes to the element dynamically), the browser must first apply the style changes and perform the layout procedure. This can be time-consuming and resource-intensive, so avoid it as much as possible.
Optimize your drawing
This is usually the longest running time in all tasks, so it is important to avoid this as much as possible. Here are the things we can do:
- In addition to transformations (transform) and transparency, changing any other property triggers a re-drawing, so use caution.
- If the layout is triggered, the drawing will also be triggered because changing the layout will cause the element's visual effect to change.
- Reduce redraw areas with layer elevation and animation orchestration.
Rendering is one of the key points of Sessionstack operation. When users navigate through your Web app, Sessionstack must re-build a video of the problems they encounter. To do this, Sessionstack only uses our library to collect data: User events, DOM changes, network requests, exceptions, debug messages, and so on. Our players are highly optimized to render and use all the data collected in sequence, from both visual and technical aspects to provide you with a perfect pixel-level simulation of everything the user has done in the browser.
If you want to try it, you can try Sessionstackfor free.
Resources
- Https://developers.google.com/web/fundamentals/performance/critical-rendering-path/constructing-the-object-model
- Https://developers.google.com/web/fundamentals/performance/rendering/reduce-the-scope-and-complexity-of-style-calculations
- https://www.html5rocks.com/en/tutorials/internals/howbrowserswork/#The_parsing_algorithm
This article from the NetEase cloud community, by the author Xu Zihai authorized release.
Original address: How JavaScript works: Rendering engine and performance tuning tips
More NetEase research and development, product, operation experience Sharing please visit NetEase Cloud community.
How JavaScript works: Rendering engine and performance tuning tips