preface

I wrote a few words in the draft box before, but I suddenly turned it out and wanted to finish it. There is no problem with the general idea and implementation, and I don’t remember some technical details. If there is something wrong or not professional, please correct me.

demand

  1. Implement a table with fixed columns and table headers, dynamic columns (50+)
  2. The PC must be compatible with Chrome, FireFox, and Edge

technology

Vue3+TS+ElementUI

Train of thought

  1. Table component with ElementUI: We implemented a version of ElementUI’s Table component, which was fine, but it was so powerful and complex that horizontal scrollbars were stuck when the dynamic columns reached more than 50 and performed poorly.
  2. I decided to write it myself, referring to ElementUI’s Table component implementation and the related articles in nuggets, which are basically implemented like this: A fixed column is a copy of the original table body, overlaid on top of the original table using absolute positioning to achieve a fixed effect. DOM structure is somewhat complex and should be considered for compatibility and extensibility. After all, ElementUI can flexibly configure columns to be fixed. In addition to this, because header and body are separate tables, you also need to synchronize the header and body positions when you drag the horizontal scrollbar so that they are not misaligned.
  3. The sticky attribute of Position can be set on the th/ TD of the table header and columns that need to be fixed. This eliminates the need to make a copy of the table body, and also eliminates the need to synchronize header and body positions when scrolling horizontally (you still need to synchronize if you want to display the scroll bar only in the body of the table).

Mozilla’s notes are:

The element is positioned according to the normal document flow, and then relative to its nearest scrolling ancestor and containing block; Includes table-related elements, offset based on top, right, bottom, and left values. The offset value does not affect the position of any other elements. This value always creates a new stacking context. Note that a sticky element will “fix” on the nearest ancestor with a “scroll mechanism” (when the ancestor’s overflow is hidden, Scroll, auto, or overlay), even if the ancestor is not the closest true scrollable ancestor. This effectively inhibits any “sticky” behavior (see Github Issue on W3C CSSWG for details).

My own superficial understanding: sticky = relative + fixed, when the sticky element does not leave the viewport, it shows relative (following the document flow); When away from the viewport, the characteristic displayed is fixed (fixed in a certain position).

rendering

  1. Initialization looks like

  1. Fix the table header and the first column on the left

demo


<html>
    <head>
        <style type="text/css">
            .wrap{
                height: 200px;
                width: 150px;
            }
            .table_wrap{
                width: 100%;
                height: 100%;
                overflow: auto;
            }
            .table_header {
                position: sticky;
                top: 0px;
		z-index: 2;
            }
            table{
                border-collapse: collapse;
		border-spacing: 0;
                table-layout: fixed;
            }
            th.td{
                border: 1px solid # 000;
                background-color: #ddd;
            }
            .td_fixed{
                position: sticky;
                left: 0px;
                z-index: 1;
            }
        </style>
    </head>
    <body>
        <div class="wrap">
            <div class="table_wrap">
                <div class="table_header">
                    <table style="width: 210px">
                        <colgroup>
                            <col style="width: 70px" />
                            <col style="width: 70px" />
                            <col style="width: 70px" />
                        </colgroup>
                        <thead>
                            <tr>
                                <th class="td_fixed">index</th>
                                <th>col1</th>
                                <th>col2</th>
                            </tr>
                        </thead>
                    </table>
                </div>
                <div class="table_body">
                    <table style="width: 210px">
                        <colgroup>
                            <col style="width: 70px" />
                            <col style="width: 70px" />
                            <col style="width: 70px" />
                        </colgroup>
                        <tbody>
                            <tr>
                                <td class="td_fixed">start</td>
                                <td>1-1</td>
                                <td>2-1</td>
                            </tr>
                            <tr>
                                <td class="td_fixed">2</td>
                                <td>1 to 2</td>
                                <td>2-2</td>
                            </tr>
                            <tr>
                                <td class="td_fixed">3</td>
                                <td>1-3</td>
                                <td>2-3</td>
                            </tr>
                            <tr>
                                <td class="td_fixed">4</td>
                                <td>1-4</td>
                                <td>2-4</td>
                            </tr>
                            <tr>
                                <td class="td_fixed">5</td>
                                <td>From 1 to 5</td>
                                <td>2-5</td>
                            </tr>
                            <tr>
                                <td class="td_fixed">6</td>
                                <td>1-6</td>
                                <td>2-6</td>
                            </tr>
                            <tr>
                                <td class="td_fixed">7</td>
                                <td>1-7</td>
                                <td>2-7</td>
                            </tr>
                            <tr>
                                <td class="td_fixed">8</td>
                                <td>1-8 -</td>
                                <td>2-8 -</td>
                            </tr>
                            <tr>
                                <td class="td_fixed">end</td>
                                <td>1-7</td>
                                <td>2-7</td>
                            </tr>
                        </tbody>
                    </table>
                </div>
            </div>
        </div>
    </body>
</html>
Copy the code

compatibility

It can be seen from the following figure that compatibility is ok, but looking at the second image, it can be found that when dragging the horizontal scroll bar, the bottom border of the table header and the right border of the first column cannot be displayed, that is, the border display of the sticky element will be problematic.Solutions:Table border change to border-collapse: separate, then set th/ TD border separately

table{
    border-collapse: separate;
    border-spacing: 0;
    table-layout: fixed;
}
table th.td{
    background: #ddd;
    text-align:center;
    border-right: 1px solid # 000;
}
table th:first-child,td:first-child{
    border-left: 1px solid # 000;
}
.table_header table th{
    border-top: 1px solid # 000;
    border-bottom: 1px solid # 000;
}
.table_body table tr:not(:first-child) td{
    border-top: 1px solid # 000;
}
.td_fixed{
    position: sticky;
    left: 0px;
    z-index: 1;
    border-right: 1px solid # 000;
}
Copy the code

End result:

supplement

Of course, this effect is still relatively simple, can also be optimized after the point:

  1. Add a shadow to the right of the table header and table Body columns to show when scrollTop/scrollLeft > 0
  2. If you want to change the scroll bar to be displayed only in the table body, you can hide the scroll bar of the entire page, display the scroll bar in the table body, and listen to the horizontal scroll of the table body, while scrolling, adjust the scrollLeft of the table header

reference

  • Developer.mozilla.org/zh-CN/docs/…
  • Caniuse.com/?search=sti…
  • Juejin. Cn/post / 684490…