Writing in the front

In daily work, the page animation effect is very common requirements. So, how to achieve an efficient animation?

This article was originally published on the public account CoyPan as expected

Note, the browsers mentioned in this article are all modern browsers based on Chromium.

Page Rendering principles

A page presented to the user, in a nutshell, goes through these five steps. We can call the above diagram a pixel pipeline.

  • Javascript: Perform JS logic, modify DOM, modify CSS, etc.
  • Style: calculation Style.
  • Layout: Once you know which rules to apply to an element, the browser can start calculating how much space it should take up and where it should be on the screen. This step is what we call rearrangement.
  • Paint: The process of filling pixels. It involves drawing text, colors, images, borders, and shadows, basically including every visible part of an element. Drawing is typically done on multiple surfaces (often called layers). This is what we call redrawing.
  • Composite: Render layer merge. As you know from the previous step, the DOM elements in the page are drawn on multiple layers. Once the drawing process is complete on each layer, the browser merges all layers into one layer in a reasonable order and displays them on the screen.

In the browser, page rendering is done by the browser’s renderer process, which consists of the main thread, worker thread, Compositer thread, and Raster thread. Among the 5 processes of the above pixel pipeline, the first 4 processes are completed by the main thread, and the last step is mainly completed by the Raster thread and Compositer thread.

JavaScript, Style, Layout

The first three steps in the Pixel pipeline are familiar. JavaScript, Style two steps, a picture to cover:

Next comes Layout, where the browser iterates over each node in the Render Tree, calculating its exact size and location. Finally, a Layout Tree is formed.

Paint

Before Paint, the browser determines the Layer of objects to be drawn based on the Layout Tree. This Layer is called the render Layer. This stage is called Update Layer Tree

In the Paint phase, the browser generates the Paint Records based on the Layer Tree.

Paint Records is a record that describes what to draw first and then, much like when we write canvas code. Paint Records are divided according to render layers. Let’s look at an example of Paint Records:

Although Pain Records are generated, the actual drawing is not done in the Paint phase, but in the Composite phase by the Raster thread.

Composite

After the previous steps, the browser main thread has broken up the content of the page into several rendering layers. In order to improve performance, certain render layers will be promoted to compositing layers. We can force an element to the composition layer by using two CSS properties:

will-change: transform; // transform: translateZ(0);Copy the code

Note: The conditions for upgrading to a synthetic layer are complicated, so we will not expand them here. Check out this article:

Taobaofed.org/blog/2016/0…

After processing all the data, the main thread submits the data to the Compositer thread. The Composite thread uses the Raster thread to perform rasterization and store the processed content in memory. As the Composite thread completes the rendering layer compositing operation and throws it to the GPU, the page is finally rendered to the screen.

You can view the compositing Layer using Layer in Chrome Developer Tools:

Other ways to run the pixel pipeline

The pixel pipeline described above has five steps. Not every frame is always processed in every part of the pipeline. In fact, whether using JavaScript, CSS, or web animation, there are two other ways for pipes to run on a given frame when implementing visual changes:

The first is what we call the page is not rearranged, it’s just redrawn; The second is that the page is neither rearranged nor redrawn.

This last approach is the least expensive and is suitable for animation on the page.

Animation effect

Regardless of canvas, there are three common ways to implement dynamic effects on a page,

  1. Use setTimeout, setInterval, requestAnimationFrame and js to modify the DOM style to achieve animation.
  2. Use pure CSS3 for animation.
  3. Js and CSS3 are combined to achieve animation.

In general, with the first method, some animation effects will not trigger a Layout in the pixel pipeline, but Paint will be unavoidable. With CSS3, we can skip the Layout and Paint steps.

Let’s take a look at how the browser handles these three implementations.

Fully JS driven animation

The code is as follows:

<html>
	<head>
		<style type="text/css">
			#test2 {
				margin-top: 100px;
				width: 100px;
				height: 100px;
				position: relative;
				background-color: black;
			}
		</style>
	</head>
	<body>
		<p>This is a useless passage, this is a useless passage, this is a useless passage, this is a useless passage, this is a useless passage</p>
		<div id="test2"></div>
		<script type="text/javascript">
			window.onload = function() {
				const el = document.getElementById('test2');
				let left = 0;
        const startTimeStamp = Date.now();
				const fn = function() {
					left += 2;
					if(Date.now() - startTimeStamp > 2000) {
            return;
          }
					el.style.left = left + 'px';
					return window.requestAnimationFrame(fn);
				}
				window.requestAnimationFrame(fn)
			}
		</script>
	</body>
</html>
Copy the code

Select a frame from the animation process and the browser processes it as follows:

As you can see, in this frame, the browser completes the pixel pipeline: JavaScript ->Style->Layout->Paint->Composite.

Pure CSS animations

We use pure CSS for animation:

<html>
	<head>
		<style type="text/css">
			#test2 {
				margin-top: 100px;
				width: 100px;
				height: 100px;
				position: relative;
				background-color: black;
				animation: move 2s;
				animation-fill-mode: forwards;
			}
			@keyframes move {
				0% {
					transform: translate(0); 100%} {transform: translate(200px); }}</style>
	</head>
	<body>
		<p>This is a useless passage, this is a useless passage, this is a useless passage, this is a useless passage, this is a useless passage</p>
		<div id="test2"></div>
	</body>
</html>
Copy the code

Let’s look at the animation in progress:

As you can see, there are no tasks executing in the main thread, while the Composite thread, Raster thread, and GPU are working.

JS combined with CSS3

<html>
	<head>
		<style type="text/css">
			#test2 {
				margin-top: 100px;
				width: 100px;
				height: 100px;
				position: relative;
				background-color: black;
			}
		</style>
	</head>
	<body>
		<p>This is a useless passage, this is a useless passage, this is a useless passage, this is a useless passage, this is a useless passage</p>
		<div id="test2"></div>
		<script type="text/javascript">
			window.onload = function() {
				const el = document.getElementById('test2');
				let left = 0;
		    const startTimeStamp = Date.now();
				const fn = function() {
					left += 2;
					if(Date.now() - startTimeStamp > 2000) {
            	return;
	        }
					el.style.transform = `translate(${left}px)`;
					return window.requestAnimationFrame(fn);
				}
				window.requestAnimationFrame(fn);
			}
		</script>
	</body>
</html>
Copy the code

When the animation runs, the browser processes as shown below, without triggering Layout and Paint.

summary

As you can see from the examples above, when using CSS-only animation, the animation process is left entirely to the Composite thread, freeing up the main thread. In fact, when performing a pure CSS3 animation, the browser promotes the responding element to a separate compositing layer without affecting other elements on the page.

Use JS to manipulate CSS3 properties and skip Layout and Paint.

Of course, not all CSS properties can skip Layout and Paint and only trigger Composite. The most common properties are transform and opacity. Specific properties can be viewed at the following url:

csstriggers.com/

Here are a few more things to add:

  1. Paint is triggered every time the animation starts.

  2. For animations with pure CSS3 operations on transform and opacity, the browser automatically elevates animation elements to a compositing layer at the beginning of the animation, but the compositing layer becomes ineffective after the animation ends. The browser will trigger Paint on the frame after the animation ends (the compositing layer fails). If we force the animation element to be a compositing layer, the frame after the animation ends will not trigger Paint.

  3. For animations that js operates on transform and opacity of CSS3, the browser does not automatically promote animation elements to the composit layer during animation, but does not trigger Paint. At the end of the animation frame, Paint may be triggered when the page animation elements are nested in a complex way, regardless of whether or not we force the animation elements to be raised to the composition layer.

conclusion

To achieve high performance animation, try to use CSS animation or use JS to operate cSS3 properties, at the same time, pay attention to animation using CSS3 properties. The goal of the animation is to skip the browser Layout and Paint and only trigger Composite.

For a particular animation element, we can appropriately elevate it to the compositing layer so that it doesn’t affect the rest of the page. Of course, the composition layer should be used sparingly, as the composition layer imposes memory stress.

Write in the back

This article from the browser rendering principle, talked about how to achieve an efficient animation. In the process of writing this article, I learned and consolidated a lot of knowledge. There are some further points worth pursuing. As expected.

References:

  • Developers.google.com/web/updates…
  • Taobaofed.org/blog/2016/0…
  • Developers.google.com/web/fundame…
  • Juejin. Cn/post / 684490…