Every front end wants to make a perfect form, and the industry is also continuing to explore different ideas, such as nailing forms and linguistic forms.
The author’s data center team also has extremely high requirements for tables, especially self-service analysis tables, which need to take into account performance and interactive functions. This article is to record the high-performance research and development ideas of self-service analysis tables.
intensive reading
To make a table, you must first choose whether it is based on DOM or Canvas. This is the first step in technology selection. Such as nail table is on Canvas achieved , of course, this does not mean that implementation of Canvas better than the DOM implementations, technically their pros and cons:
- Canvas rendering efficiency is higher than DOM, which is caused by browser implementation.
- DOM is more extensible than Canvas, and DOM is preferred to Canvas for rendering custom content.
The technical selection depends on the specific business scenario. The pin table is actually online Excel. The form of Excel determines that the cell must be simple text plus some simple icons, so you don’t need to consider the scene of rendering custom content, so choose Canvas rendering In the future, there will be no trouble with bad expansion.
The self-service analysis form may naturally expand graphics, pictures, and operation buttons into cells, and the drag-and-drop response interaction of the axis is also very complicated. In order to prevent Canvas from becoming a bottleneck for future expansion, it is more appropriate to choose DOM.
So the question is, since DOM rendering efficiency is naturally lower than Canvas, how should we use DOM to implement a high-performance table?
In fact, there are many DOM table optimization solutions in the industry, mainly based on on-demand rendering and virtual scrolling, that is, some Buffer areas are reserved for filling during sliding, and the table only renders the visible area and the Buffer area. However, these solutions inevitably have the problem of white screen when sliding quickly. The author finally found a perfect solution through continuous attempts. Let's look down together!
Cells use DIV absolute positioning
That is, each cell is implemented with an absolute positioning DIV, and the entire table is spliced with DIVs with independent calculation positions:
The premise of this is:
- All cell positions must be calculated in advance, here you can use web workers to do parallel calculations.
- Cell merging only produces a larger cell, and its positioning method is no different from that of small cells.
The benefits are:
- When scrolling, cells can be reused to the greatest extent possible.
- For merged cells, the total number of cells rendered in the visible area will only be smaller, which is more conducive to performance improvement, rather than a performance burden.
As shown in the figure, there are 16 cells. When we slide one cell down to the right, the area of 3x3 or 9 cells in the middle will not be re-rendered at all, so that the scattered absolute positioning distribution can maintain the original cell to the greatest extent position. We can think that any cell will not be re-rendered as it scrolls as long as it does not exceed the screen range.
If you use the React framework to implement it, just set the key of each grid to be unique, such as the current row and column number.
Simulate scrolling instead of native scrolling
Generally speaking, because the logic of the axis is special, its rendering logic and cells will be maintained separately, so we divide the table into three areas: horizontal axis, vertical axis, and cells.
Obviously, common sense is that the horizontal axis can only be scrolled vertically, the vertical axis can only be scrolled horizontally, and the cell can be scrolled horizontally and vertically, so the horizontal and vertical scroll bars can only appear in the cell area:
There will be three problems:
- Cells use native scrolling. The horizontal and vertical axis can only monitor scrolling in the cell
.scroll
simulate scrolling through 06076b62521a3b. This will inevitably lead to a certain misalignment between the cell and the axis scrolling, that is, the axis scrolling has a lag of a few milliseconds. - You cannot scroll when the mouse is placed on the axis, because only the cell is
overflow: auto
, and the axis areaoverflow: hidden
cannot trigger scrolling. - A white screen appears during fast scrolling. Even if the Buffer area is left, there is nothing you can do during fast scrolling. This is because the rendering speed cannot keep up with the scrolling.
After some thinking, we can solve the above three problems at the same time by slightly adjusting the plan: that is, instead of using the native scroll bar, use .scroll
instead of scrolling, and use mousewheel
monitor the triggering of scrolling:
What changes will this bring?
- Both the axis and the cell area use
.scroll
trigger the scrolling, so that the axis and the cell will not be misaligned, because the axis and the cell are all triggered.scroll
- Any position can be monitored to scroll, so that the axis can also be scrolled, we no longer rely on the
overflow
attribute. - When scrolling fast, I was surprised to find that the screen will not be white. The reason is
js
control occurs after the rendering is completed, so the browser will complete the rendering before the scroll occurs, which is quite interesting.
When simulating scrolling, the entire table is actually overflow: hidden
, and the browser will not give its own scroll bar. We need to use DIV to make a virtual scroll bar instead, which is relatively easy.
Zero buffer area
When we use the analog scrolling scheme, it is equivalent to using the "high-frequency rendering" scheme during scrolling, so there is no need to use interception, let alone the Buffer area, because a larger Buffer area means greater rendering overhead.
When we removed the Buffer area, we found that when the rendering cells in the entire screen were within 1000, modern browsers even with Windows can quickly complete the refresh before scrolling without affecting the smoothness of scrolling.
Of course, scrolling too fast is still not a good thing. Since the scrolling is controlled by us, we can slightly control the scrolling speed. It is best to control the mousewheel
not to exceed 200 per trigger.
Precompute
Calculation logic such as cell merging, row and column hiding, cell formatting, etc., is best to be calculated before scrolling, otherwise real-time calculations during fast scrolling will inevitably bring additional calculation cost loss.
However, this kind of pre-calculation also has its drawbacks. When the number of cells exceeds 10w, the calculation will generally take more than 1 second. When the number of cells exceeds 100w, the calculation will generally take more than 10 seconds. The sacrifice of precalculation is used in exchange for scrolling. It’s smooth, but it’s still a bit regretful. We can think about the following, can we reduce the loss of pre-calculation?
Partial precomputation
Local pre-calculation is a solution. Even if the number of cells is 10 million, what if we only count the first 1w cells? No matter how large the data volume is, there will be no stuttering at all.
However, local pre-calculation has obvious disadvantages, that is, during the table rendering process, the local calculation results are not always equivalent to the global calculation results. Typical calculation fields include column width, row height, and cross-row and cross-column calculation fields.
We need to solve it in a targeted manner. For the calculation of cell width and height, partial calculation must be used, because the loss of full calculation is very large. But the partial calculation is definitely inaccurate, as shown in the following figure:
But for performance reasons, our initialization may only be able to calculate the height of the first three rows. At this time, we need to do two things when scrolling:
- When scrolling fast, send the expected scrolling position to the web worker, calculate the text width at these positions incrementally, and correct the total column width in real time. (Because the total column width only needs to store the maximum value after the calculation, the calculated order of magnitude will be compressed to O(1)).
- After the width is calculated, the current screen cell width is quickly refreshed, but while the width is calibrated, the left alignment in the visible area remains unchanged, as shown in the following figure:
In this way, although the cell will be suddenly opened during the scrolling process, the relative position will not move, which is the same as the visual content after the full expansion in advance. Therefore, the user experience will not have an actual impact, but the calculation time is reduced by O(row * column) drops to O(1), just count a constant number of cells.
The same is true for the calculation field. It can be pre-calculated by slice when scrolling, but it should be noted that it can only be performed when the calculation involves local cells. If the calculation is of a global nature, such as ranking, then the ranking of the local sort is definitely wrong Yes, we must perform full calculations.
Fortunately, even if it is a full calculation, we only need to consider a part of the data. Assuming that the number of rows and columns are all n, the computational complexity can be reduced from O(n²) to O(n):
This kind of calculation field processing cannot guarantee to support unlimited orders of data, but it can greatly reduce the calculation time. Assuming that the calculation time cost of a 1000w cell is 60s, this is an almost intolerable time. Assuming that a 1000w cell is 1w rows * 1k columns As a result, the cost of our local calculation is 1w rows (100ms) + 1k columns (10ms) = 0.1s, and users can hardly feel the freeze of 1000w cells.
In the case of 10w rows * 10w columns, the waiting time is 1+1 = 2s, and the user will experience obvious freezes, but the total number of cells is an astonishing 10 billion. The data may be only a few terabytes, and this is impossible. Aggregate data on a large scale.
Map Reduce
Front-end computing can also be accelerated by using multiple web workers. In short, don't let the CPU of the user's computer idle. We can window.navigator.hardwareConcurrency
, and we will instantiate the same amount of web workers for parallel computing.
Take the example of ranking just now, for the same number of cells of 1000w, what if there is only one column? The number of rows is a solid 1000w. In this case, even if the O(n) complexity calculation takes time, it may exceed 60s. At this time, we can calculate in segments. hardwareConcurrency
of my computer is 8, then 8 web workers are 0 ~ 125w
, 125w ~ 250w
..., 875w ~ 1000w
are calculated in parallel respectively, and finally 8 sections of ordered sequence are obtained, which are performed in the main worker thread. merge.
We can use divide-and-conquer merge, that is, for the sequentially received sorting results x1, x2, x3, x4..., the received results are merged into x12, x34, ..., and then merged into x1234 until merged into Up to an array.
Of course, Map Reduce cannot solve all problems. Assuming that the calculation of 1000w data takes 60s, we divide it into 8 parallel segments, and each segment takes an average of 7.5s, so the total time for the first round of sorting is 7.5s. The time complexity of divide and conquer merge is O(kn logk), where k is the number of segments, here is 8 segments, logk is approximately equal to 3, and the length of each segment is 125w is n, then a binary sorting of the order of 125w takes about 4.5s , The time complexity is O(n logn), so the equivalent is logn = 4.5s, what is kx logk? Here, since k is much smaller than n, the time consumption will be much less than 4.5s, and the total time consumption will not exceed 10s.
to sum up
If you want to build a high-performance table, DIV performance is enough, just pay attention to a little skill when implementing it. You can use DIV to implement a table that takes both performance and scalability into consideration. It's time to trust the DOM again!
The author recommends that after reading this article, you should make a small demo based on this line of thinking, and at the same time, think about what general functions of such a table can be abstracted? How to design API to become the base of various business forms? How to design functions to meet the numerous expansion demands of business-level tables?
The discussion address is: Intensive Reading of "High Performance Forms" · Issue #309 · dt-fe/weekly
If you want to participate in the discussion, please click here , a new theme every week, weekend or Monday. Front-end intensive reading-to help you filter reliable content.
Follow front-end intensive reading WeChat public
Copyright notice: Freely reproduced-non-commercial-non-derivative-keep the signature ( Creative Commons 3.0 License )
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。