Introduction to the
There are two main graphic rendering schemes in HTML: SVG and Canvas. The former is easier to use, while the latter has greater potential. This paper mainly focuses on how to use Canvas to draw more graphics and provide more smooth interaction. The contents of this paper are as follows:
- Apply colours to a drawing mechanism
- Performance bottleneck
- Draw more graphics
- Make the interaction smoother
- WebGL implements 2D rendering
Apply colours to a drawing mechanism
Let’s use a simple circle as an example to compare SVG and Canvas rendering:
- SVG is similar to other HTML tags. Each image corresponds to a tag, and the graph is drawn the same as the HTML tag
<svg>
<circle cx="100" cy="50" r="40" stroke="black" stroke-width="2" fill="red"/>
</svg>
Copy the code
- However, Canvas is essentially an image. No matter how many graphics have only one label, javascript is needed to draw the graphics
<canvas></canvas>
<script>
var ctx=c.getContext("2d");
ctx.strokeStyle = 'black';
ctx.fillStyle = 'red'
ctx.lineWidth = 2;
ctx.beginPath();
ctx.arc(100.50.40.0.2 * Math.PI);
ctx.stroke();
</script>
Copy the code
PS: You can understand SVG as making graphics one by one and putting them on the page, while Canvas is drawing graphics one by one with a brush. This article is not a comparison between SVG and Canvas, but w3CShool’s description of the difference is very accurate.
How do I pick up graphics
- The browser provides isPointInPath and isPointInStroke methods to determine whether a point is inside the graph or on the edge of the graph.
- Caches the properties of all graphs, using mathematical methods to determine the graph where the specified point is located.
Again using a circle as an example, let’s look at these two ways:
Browser method
The browser method requires a re-drawing of the graph:
ctx.beginPath();
ctx.arc(100.50.40.0.2 * Math.PI);
const inPath = ctx.isPointInPath(100.100);
const inStroke = ctx.isPointInStroke(100.100);
Copy the code
- This is an easy way to use and work with all graphics, but it costs a lot. For example, every time the mouse moves over the canvas, all graphics are drawn again.
- It is recommended to use this scheme when the number of graphs is less than 500.
Mathematics to pick up
Each graph needs to provide a way to determine whether it is inside the graph and on the edge of the graph:
function isInCircle(point, x, y, r) {
return distance(point.x, point.y, x, y) <= r;
}
function isInCircleStroke(point, x, y, r, lineWidth) {
const d = distance(point.x, point.y, x, y);
return d <= r + lineWidth / 2 && d >= r - lineWidth / 2;
}
const point = {x: 100.y: 100};
const inPath = isInCircle(point, 100.50.40);
const inStroke = isInCircle(point, 100.50.40.2);
Copy the code
- Good performance: In terms of performance, mathematical pickup is about 20 times faster than using a browser
- Implementation complexity: in terms of implementation, it is necessary to realize all the mathematical calculations of geometric figures, and more mathematical calculations refer to 2D graphic calculations
PS: Performance test comparison between the two
The browser API | Mathematical calculations | |
---|---|---|
1 | 111.02999997092411 | 4.779999959282577 |
2 | 110.53000000538304 | 5.694999999832362 |
3 | 117.55500000435859 | 7.979999994859099 |
4 | 126.2599999899976 | 5.354999972041696 |
5 | 110.8949999907054 | 4.725000006146729 |
6 | 121.6549999662675 | 6.2049999833106995 |
7 | 121.18500005453825 | 4.529999976512045 |
8 | 116.78500002017245 | 8.094999997410923 |
9 | 124.06000000191852 | 8.925000031013042 |
10 | 124.42499998724088 | 4.849999968428165 |
The average | 118.43799999915063 | 6.113999988883734 |
For more and faster pickup schemes, see 2D graphics pickup schemes
Graphics update
For SVG graphics, you can directly modify the attributes of the corresponding label, and the browser controls the refreshing of the graphics. However, for Canvas, the whole Canvas needs to be cleared and all graphics redrawn, that is to say, there are 10W graphics on Canvas. When only one graph is updated, the other 99,999 graphics also need to be redrawn.
function drawAll() {
// Draw all graphics
}
function repaint() {
ctx.clearRect(0.0, width, height);
drawAll();
}
Copy the code
Performance bottleneck
From the above rendering mechanism, we can naturally deduce that the performance bottleneck of Canvas graphic rendering is mainly in three aspects:
- Drawing too many graphics at a time blocks the browser process and renders the page unresponsive
- Failure to capture the mouse while it is moving over the canvas may cause stalling
- Graphics update, redraw time is too long, the frame rate is very low
Cost of rendering
Let’s take drawing 1W circles as an example to see the cost of a single drawing:
Cost of pickups
Earlier we tested the difference between graphic pick and mathematical pick. 1W circles take about 11ms to pick, and if we add the response of graphic refresh, we can expect a very low frame rate.
Cost of renewal
Let’s move the mouse over the canvas and move in a circle that changes color. Let’s see what happens when the whole canvas is refreshed:
- You can see a significant delay, the mouse moved some distance before the dot responded
- Mouse moved over the path, most circles do not respond
If we animate the circle look at the frame rate:
Draw more graphics
Optimization for the first rendering
When there are too many graphics in one render, there is no stalling by dividing the render into multiple renderings and increasing each render time by a few milliseconds:
- This is a 10W point render (local canvas 1000 * 1000, only 500* 500 is shown here) that guarantees an effect of almost 60 frames
- The core of segmented rendering is that the spacing between the blanks should be small enough that there are a lot of algorithms in it, so it’s not going to unfold here
- See react Fiber for details on how to update data during segmented rendering.
Frequent rendering optimization
When the mouse moves on the canvas, it will constantly cause repainting, as long as we can guarantee the repainting frequency of 60 frames, so the repainting interval should not be less than 16ms. We can change the synchronous mechanism of continuous rendering to the asynchronous delayed rendering mechanism of every 16ms, which can greatly reduce the repainting frequency.
Deferred rendering implementation
Update optimization
We can implement a partial refresh of the graph by emptying only the bounding box in which the graph is located, and all the graphs that intersect the bounding box are refreshed. At this point, we can look at the two examples above: when the mouse is moved over the canvas, this is much smoother, with almost no delay
Optimization of pickups
Pick optimization we have carried out a simple explanation above, mathematical pick performance is far more than using the browser method to pick, more pick scheme for reference: 2D graphics pick scheme
WebGL implements 2D rendering
Improved rendering performance
Since webGL rendering is carried out on GPU, rendering efficiency can be significantly improved, as shown in the following example:
- This is an example of 80 * 80 * 80 = 512000 points
Some restrictions
- Since webGL’s rendering is based on raster (points), lines are drawn by points in essence. It is unrealistic to calculate Bessel curves point by point, so the curves drawn are not smooth enough.
- When drawing graphs, try not to directly calculate the geometric model of each graph in the CPU, and use shader to calculate and render the graph, otherwise the performance will decline
- Rendering text is very complex and does not provide performance gains
conclusion
Most of these optimizations are already in the 2 d graphics engine G, 2 d graphics rendering acceleration and local optimization mainly in asynchronous rendering, collect the rendering three aspects, but each aspect is very complex, can be read separately, this article only from the train of thought on the interpretation, more finer analysis will provide independent chapters to explain, behind please look!
AntV official website: ANTV. Vision / 2D drawing engine G: github.com/antvis/g