This article introduces a fashionable website design method and how to use HTML5 and browser rendering mechanisms to build high-performance websites from simple to deep.
This article involves the principles of browser re-painting and performance optimization, and is also an extension and continuation of Web rolling performance optimization practices. The difficulty is an intermediate level, please read this article before reading it.
Introduction
Recently, Parallax websites are popular. Just look at the following websites:
- Old Pulteney Row to the Pole
- Adidas Snowboarding
- BBC News-James Bond: Cars, catchphrases and kisses
If you do not know about them, they are actually the sites where the visual structure of the page changes with the scrolling. Normally, the elements on the page are scaled, rotated, or moved to the scroll position proportionally.
Our demonstration page of parallax effect
If you do not like the parallax website, it is one thing, but we can be sure that this is definitely a performance black hole. The reason is that when you scroll, the browser will try to optimize the performance of the new content (based on the rolling direction). In general, the less the scrolling, the better the browser performance. This is rare for Parallax websites, because the large visual elements on the entire page are changed multiple times, causing the browser to re-paint the entire page (why is it a performance black hole, refer to my article "Web rolling performance optimization practices").
It is reasonable to generalize the parallax website into the following features:
1. When you scroll up or down the page, the background element changes its position, rotates, or scales.
2. The page content, such as text or small images, is scroll from top to bottom in a special way.
We have introduced the rolling performance and optimization methods before. You can improve the application response capability. This article is based on this, so you need to read the above article first.
So now the question is, if you are building a parallax rolling website, do you have to perform expensive re-painting, or are there other ways to maximize performance? Let's take a look at the available methods.
Method 1: Use DOM elements and absolute positioning
This may be the way most people choose. There are many elements in the page. When a rolling event is triggered, many visual updates will occur on these elements. Here is a demo page.
If you enable the frame mode of the developer tool timeline and scroll up or down, you will notice expensive full-screen rendering operations. If you scroll multiple times, you may be able to see multiple rolling events in a single frame, each of which triggers layout work.
The developer tool shows a frame with a large number of drawing operations and multiple la s triggered by events.
It is important to remember that in order to reach 60 FPS (which matches the typical display update rate of 60Hz), we must do everything within 16 Ms. In the first version, every time we get a rolling event, we need to execute a visual update, however, as we discussed in the previous article "use requestAnimationFrame for simpler Animation" and "Web rolling performance optimization practices", this is not consistent with the browser update pace. So we either miss the frame or do too much work in one frame. This will make your site easy to look uncomfortable and unnatural, causing users to feel disappointed.
Let's move the Visual update code from the rolling event to the requestAnimationFrame callback, and simply get the rolling value in the callback of the rolling event. We showed this change in the second demonstration.
If you repeat the rolling test, you may notice slight improvements, though not much. The reason is that layout operations triggered by scrolling are expensive, but now we only perform the layout operation once per frame.
The developer tool shows a frame with a large number of drawing operations and multiple la s triggered by events.
We can now process one or hundreds of rolling events in each frame, but most importantly, we only store the most recent rolling value for use when the requestAnimationFrame callback is triggered, and perform visual updates. The key is that we have optimized the Visual update every time we receive a rolling event to handle the appropriate time that the browser gives us. Do you think this is awesome?
The main problem with this method is that, whether using requestAnimationFrame or not, we basically generate the layer of the entire page. When moving these visual elements, a large amount of and expensive re-painting is required. Normally, re-painting is a blocking operation (although it will be optimized), which means that the browser cannot do other work at the same time, and we may often exceed the processing time limit of the browser's 16 Ms frame, this means that performance is stuck.
Method 2: Use DOM elements and 3D Conversion
In addition to absolute positioning, another method we can use is the 3D conversion (transform ). In this case, we can see that each element processed with 3D conversion will generate a new layer. In contrast, if there is any change in method 1, we have to repaint most of the layers on the page.
This means that this method is quite different: we may have a layer for any element that applies 3D conversion. If this is done through the conversion of more elements, we do not need to redraw any layer, and the GPU can process mobile elements and synthesize the entire page. Maybe you want to know why 3D conversion replaces 3D, because 2D conversion cannot guarantee a new layer, while 3D conversion can.
This is another demonstration using 3D conversion. When rolling, you can see that the performance has greatly improved.
Many times people use the-webkit-transform: translateZ (0) technique to see amazing performance improvements (Yujie Note: For this method, in fact, it is an Hack that uses 3D conversion to enable browser hardware acceleration. Few materials are mentioned in China, while many foreign articles about mobile App development performance optimization have mentioned this. In China, you can see "improve HTML5 web page performance". In foreign countries, you can see "IncreasingPerformance of HTML and JavaScript on Mobile Devices"). This method can work normally now, but it may cause some problems:
1. It is not browser compatible;
2. It forces the browser to create a new layer for each converted element. A large number of layers may cause other performance bottlenecks, so you need to use them in a controlled manner.
3. It is disabled for some Webkit versions.
Therefore, you need to be very cautious when using this method, which is a temporary solution for solving the problem. In perfect circumstances, we don't even consider it, and the browser is improving every day. Who knows that we may not need it someday.
Method 3: Use a Fixed Position Canvas or WebGL
The final method we should consider is to use a fixed positioned Canvas on the page, and draw the converted image on it. At first glance, this may not be the most efficient solution, but it has several advantages:
- We no longer need a lot of merging work, because the page has only one element-Canvas;
- We can efficiently process a separate bitmap through hardware acceleration;
- Canvas2D API is very suitable for the conversion type we want to execute, which means that development and maintenance are easier to manage.
The Canvas element provides us with a new layer, but it only has one layer. In method 2, we create a new layer for each element that applies 3D conversion, so there is extra work to combine these layers.
If you look at the demo of this method and observe it in the developer tool, you will find that it has better performance. In this method, we only need to call the drawImage API on the Canvas, set the background image, and each color block to be drawn in the correct position on the screen.
- /**
- * Updates and draws in the underlying visual elements to the canvas.
- */
- Function updateElements (){
- Var relativeY = lastScrollY/h;
- // Fill the canvas up
- Context. fillStyle = "#1e2124 ";
- Context. fillRect (0, 0, canvas. width, canvas. height );
- // Draw the background
- Context. drawImage (bg, 0, pos (0,-3600, relativeY, 0 ));
- // Draw each of the blobs in turn
- Context. drawImage (blob1, 484, pos (254,-4400, relativeY, 0 ));
- Context. drawImage (blob2, 84, pos (954,-5400, relativeY, 0 ));
- Context. drawImage (blob3, 584, pos (1054,-3900, relativeY, 0 ));
- Context. drawImage (blob4, 44, pos (1400,-6900, relativeY, 0 ));
- Context. drawImage (blob5,-40, pos (1730,-5900, relativeY, 0 ));
- Context. drawImage (blob6, 325, pos (2860,-7900, relativeY, 0 ));
- Context. drawImage (blob7, 725, pos (2550,-4900, relativeY, 0 ));
- Context. drawImage (blob8, 570, pos (2300,-3700, relativeY, 0 ));
- Context. drawImage (blob9, 640, pos (3700,-9000, relativeY, 0 ));
- // Allow another rAF call to be scheduled
- Ticking = false;
- }
- /**
- * Calculates a relative disposition given the page's scroll
- * Range normalized from 0 to 1
- * @ Param {number} base The starting value.
- * @ Param {number} range The amount of pixels it can move.
- * @ Param {number} relY The normalized scroll value.
- * @ Param {number} offset A base normalized value from which to start the scroll behavior.
- * @ Returns {number} The updated position value.
- */
- Function pos (base, range, relY, offset ){
- Return base + limit (0, 1, relY-offset) * range;
- }
- /**
- * Clamps a number to a range.
- * @ Param {number} min The minimum value.
- * @ Param {number} max The maximum value.
- * @ Param {number} value The value to limit.
- * @ Returns {number} The clamped value.
- */
- Function limit (min, max, value ){
- Return Math. max (min, Math. min (max, value ));
- }
/** * Updates and draws in the underlying visual elements to the canvas. */function updateElements () { var relativeY = lastScrollY / h; // Fill the canvas up context.fillStyle = "#1e2124"; context.fillRect(0, 0, canvas.width, canvas.height); // Draw the background context.drawImage(bg, 0, pos(0, -3600, relativeY, 0)); // Draw each of the blobs in turn context.drawImage(blob1, 484, pos(254, -4400, relativeY, 0)); context.drawImage(blob2, 84, pos(954, -5400, relativeY, 0)); context.drawImage(blob3, 584, pos(1054, -3900, relativeY, 0)); context.drawImage(blob4, 44, pos(1400, -6900, relativeY, 0)); context.drawImage(blob5, -40, pos(1730, -5900, relativeY, 0)); context.drawImage(blob6, 325, pos(2860, -7900, relativeY, 0)); context.drawImage(blob7, 725, pos(2550, -4900, relativeY, 0)); context.drawImage(blob8, 570, pos(2300, -3700, relativeY, 0)); context.drawImage(blob9, 640, pos(3700, -9000, relativeY, 0)); // Allow another rAF call to be scheduled ticking = false;}/** * Calculates a relative disposition given the page’s scroll * range normalized from 0 to 1 * @param {number} base The starting value. * @param {number} range The amount of pixels it can move. * @param {number} relY The normalized scroll value. * @param {number} offset A base normalized value from which to start the scroll behavior. * @returns {number} The updated position value. */function pos(base, range, relY, offset) { return base + limit(0, 1, relY - offset) * range;}/** * Clamps a number to a range. * @param {number} min The minimum value. * @param {number} max The maximum value. * @param {number} value The value to limit. * @returns {number} The clamped value. */function limit(min, max, value) { return Math.max(min, Math.min(max, value));}
This approach is certainly challenging when dealing with large images (or other elements that are easily written to a Canvas) or large pieces of text. But on your website, it may prove to be the most appropriate solution. If you have to process text on the Canvas, you may need to use the fillText API, but it has access costs (you just converted the text to bitmap !) In addition, you need to handle text line breaks and other issues. You need to avoid doing so.
After so much discussion, we have no reason to assume that the Canvas element must be used for the parallax operation. If the browser supports this function, we can use WebGL. The key here is that WebGL is the most direct method from all APIs to the video card, and the performance is most likely to reach 60fps in the case of complicated site effects.
Your most direct response may be that the adoption of WebGL is overcorrected, or it is not widely supported, but if you use Three. js library, you can roll back to use the Canvas Element at any time, and your code can be abstracted in a consistent and friendly way. All we need to do is to use Modernizr to detect the corresponding API support:
- // Check for WebGL support, otherwise switch to canvas
- If (Modernizr. webgl ){
- Renderer = new THREE. WebGLRenderer ();
- } Else if (Modernizr. canvas ){
- Renderer = new THREE. CanvasRenderer ();
- }
// check for WebGL support, otherwise switch to canvasif (Modernizr.webgl) { renderer = new THREE.WebGLRenderer();} else if (Modernizr.canvas) { renderer = new THREE.CanvasRenderer();}
Use the Three. js API instead of processing the context by yourself. Here is a demonstration that supports two rendering methods.
The last problem with this method is that if you are not particularly interested in adding additional elements to the page, you can always use canvas as the background element in Firefox and Webkit browsers. Obviously, this is not universally applicable, so you should be cautious about it.
Gradual degradation
The main reason why developers use absolute positioning elements by default rather than other methods may be simply browser support. This method is incorrect to a certain extent, because it can only provide a very poor rendering experience for old browsers. Even in modern browsers, absolute positioning may not provide good performance.
A better solution is to avoid try the parallax effect on the old browser, and ensure that the site effect can be displayed with the correct API only on the Best Browser. Of course, if you use Three. js, you should be able to easily switch between Renderer Based on the needed support.
Conclusion
We evaluated several methods to process a large number of repainted areas, from absolute positioning elements to using fixed positioning Canvas. Of course, the implementation method you want to adopt depends on your goals and specific designs. But it is a good thing to know that there are multiple options. In the example in this article, we try to optimize the smooth and 60fps effects from relatively choppy and below 30fps.
As usual, no matter which method you try, you should try it yourself instead of making guesses.
As usual, repost Please note: from Jiang Yujie's blog (http://blog.csdn.net/hfahe)
Translation: http://www.html5rocks.com/en/tutorials/speed/parallax/
Related Articles: Web rolling performance optimization practices