Improving (identify) render performance

Micha Schopman

I built a basic grid where header and content are separated. This is done to enable future expansion into more complex tabular visualizations. When scrolling the content, the header is synchronized.

The challenge I have, is that the scrolling lags. I already implemented a throttle but still it performs really poor (note I am just doing a basic scrollLeft synchronization, nothing fancy). It tried a variety of ranges, from 0ms up to 30ms without substantial improvement.

I then looked at the rendering performance, opened up developer tools to look at what parts of the page are repainted, and it was also limited to only the scrollbars and the header being repainted. (mp4 video https://filedropper.com/d/s/GxsNi1nG4URwQ25uNUSCU5606HKNF0)

I also tried looking at the css, eliminating the table-layout:fixed didn't make any difference and also here I am not doing something out of the ordinary that would really impact rendering.

I suspect some specific surrounding layout / flex-layout from heavily impacting rendering performance. Is there another way of really pinpointing where the delay is coming from? To be fair, running the snippet here on StackOverflow also shows the lag.

function Grid(parentNode){
    this.ready = false;
    
    this.parentNode = typeof parentNode === 'string' ? document.getElementById(parentNode) : parentNode;
    if (!this.parentNode) {
        console.error('Exception - Grid','Cannot find parentnode');
        return;
    }
    
    // quick n dirty references (normally created when rendering the grid)
    this.gridContent = this.parentNode.querySelector(".gridcontent");
    this.gridHeaderContent = this.parentNode.querySelector(".gridheadercontent");
    this.gridHeaderWrapper = this.parentNode.querySelector(".gridheaderwrapper");   
    
    this.Init();
}
Grid.prototype = {
    Init: function() {
        //this.gridContent.addEventListener('scroll',this.Scroll.bind(this));  
        this.gridContent.addEventListener('scroll', Throttle(30, this.Scroll.bind(this)));
    },
    Scroll: function () {
        let scrollbarWidth = this.GetScrollbarWidth();
        
        let scrollLeft = event.srcElement.scrollLeft;
        this.gridHeaderContent.scrollLeft = scrollLeft;

        if (scrollbarWidth > 0) {
            this.gridHeaderWrapper.style.paddingRight = scrollbarWidth + 'px';
        }
    },
    GetScrollbarWidth: function () {
        return this.gridContent.offsetWidth - this.gridContent.clientWidth;
    }
}

// manage the scroll event spam, resulting in too many redraws
function Throttle(ms, callback) {
    let lastCall = 0;

    return function () {
        let now = new Date().getTime(),
        diff = now - lastCall;
        if (diff >= ms) {
            lastCall = now;
            callback.apply(this, arguments);
        }
    };
}

new Grid("grid");
.component.grid {
     position: relative;
     border-color: #dee2e6;
     overflow: hidden;
     height: 100%;
}
 .component.grid .gridheaderwrapper {
     overflow: hidden;
     border: 1px solid lightgrey;
}
 .component.grid .gridheaderwrapper .gridheadercontent {
     overflow: hidden;
     border-right: 1px solid lightgrey;
}
 .component.grid .gridheaderwrapper .gridheadercontent .gridheadertable {
     border-collapse: collapse;
     table-layout: fixed;
}
 .component.grid .gridcontentwrapper {
     overflow: hidden;
     vertical-align: middle;
     background-color: #fff;
     height: 50%;
}
 .component.grid .gridcontentwrapper .gridcontent {
     position: relative;
     overflow: auto;
     height: 100%;
}
 .component.grid .gridcontentwrapper .gridcontent .gridcontenttable {
     border-collapse: collapse;
     table-layout: fixed;
}
 .component.grid th {
     text-align: left;
     white-space: nowrap;
     padding: 10px 8px;
}
 .component.grid th[rowspan] {
     vertical-align: bottom;
}
 .component.grid td {
     white-space: nowrap;
     text-overflow: ellipsis;
     overflow: hidden;
     padding: 10px 8px;
}
<div id="grid" style="padding: 10px;">
    <div class="component grid" id="51177476-273f-4277-954c-dd7e576c5c9d">
        <div class="gridheaderwrapper">
            <div class="gridheadercontent">
                <table class="gridheadertable" style="width: 1177px;">
                    <colgroup>
                        <col style="width: 55px;" />
                        <col style="width: 113px;" />
                        <col style="width: 80px;" />
                        <col style="width: 95px;" />
                        <col style="width: 93px;" />
                        <col style="width: 136px;" />
                        <col style="width: 61px;" />
                        <col style="width: 292px;" />
                        <col style="width: 88px;" />
                        <col style="width: 89px;" />
                        <col style="width: 75px;" />
                    </colgroup>
                    <thead>
                        <tr>
                            <th>
                                <p>
                                    <label><input type="checkbox" class="filled-in" /><span>&nbsp;</span></label>
                                </p>
                            </th>
                            <th>Name</th>
                            <th>DataType</th>
                            <th>PrimaryKey</th>
                            <th>ForeignKey</th>
                            <th>DataAnalysisType</th>
                            <th>Visible</th>
                            <th>Id</th>
                            <th>Created</th>
                            <th>Modified</th>
                            <th>Selected</th>
                        </tr>
                    </thead>
                </table>
            </div>
        </div>
        <div class="gridcontentwrapper">
            <div class="gridcontent">
                <table class="gridcontenttable" style="width: 1177px;">
                    <colgroup>
                        <col style="width: 55px;" />
                        <col style="width: 113px;" />
                        <col style="width: 80px;" />
                        <col style="width: 95px;" />
                        <col style="width: 93px;" />
                        <col style="width: 136px;" />
                        <col style="width: 61px;" />
                        <col style="width: 292px;" />
                        <col style="width: 88px;" />
                        <col style="width: 89px;" />
                        <col style="width: 75px;" />
                    </colgroup>
                    <tbody>
                        <tr id="daa93d55-bdd5-4a20-9bef-2567efe75971">
                            <td>
                                <p>
                                    <label><input type="checkbox" class="filled-in" /><span>&nbsp;</span></label>
                                </p>
                            </td>
                            <td>CategoryID</td>
                            <td>int</td>
                            <td>true</td>
                            <td>false</td>
                            <td>2</td>
                            <td>true</td>
                            <td>daa93d55-bdd5-4a20-9bef-2567efe75971</td>
                            <td>01/01/0001</td>
                            <td>01/01/0001</td>
                            <td>false</td>
                        </tr>
                       
                    </tbody>
                </table>
            </div>
        </div>
    </div>
</div>

Leyiang
  1. Reduce your throttle threshhold to a small number (like 5).
  2. If you still fill lagging while scrolling, try use transform to replace scrollLeft, and add will-change to hints browsers how an element is expected to change.

function Grid(parentNode){
    this.ready = false;
    
    this.parentNode = typeof parentNode === 'string' ? document.getElementById(parentNode) : parentNode;
    if (!this.parentNode) {
        console.error('Exception - Grid','Cannot find parentnode');
        return;
    }
    
    // quick n dirty references (normally created when rendering the grid)
    this.gridContent = this.parentNode.querySelector(".gridcontent");
    this.gridHeaderContent = this.parentNode.querySelector(".gridheadercontent");
    this.gridHeaderWrapper = this.parentNode.querySelector(".gridheaderwrapper");   
    this.gridTable = this.parentNode.querySelector(".gridheadertable");
    this.Init();
}
Grid.prototype = {
    Init: function() {
        //this.gridContent.addEventListener('scroll',this.Scroll.bind(this));  
        this.gridContent.addEventListener('scroll', Throttle(5, this.Scroll.bind(this)));
    },
    Scroll: function () {
        let scrollbarWidth = this.GetScrollbarWidth();
        
        let scrollLeft = event.srcElement.scrollLeft;
        this.gridTable.style.transform = `translateX(-${scrollLeft}px)`;
    },
    GetScrollbarWidth: function () {
        return this.gridContent.offsetWidth - this.gridContent.clientWidth;
    }
}

// manage the scroll event spam, resulting in too many redraws
function Throttle(ms, callback) {
    let lastCall = 0;

    return function () {
        let now = new Date().getTime(),
        diff = now - lastCall;
        if (diff >= ms) {
            lastCall = now;
            callback.apply(this, arguments);
        }
    };
}

new Grid("grid");
.component.grid {
     position: relative;
     border-color: #dee2e6;
     overflow: hidden;
     height: 100%;
}
 .component.grid .gridheaderwrapper {
     overflow: hidden;
     border: 1px solid lightgrey;
}
 .component.grid .gridheaderwrapper .gridheadercontent {
     overflow: hidden;
     border-right: 1px solid lightgrey;
}
 .component.grid .gridheaderwrapper .gridheadercontent .gridheadertable {
     border-collapse: collapse;
     table-layout: fixed;
     will-change: transform;
}
 .component.grid .gridcontentwrapper {
     overflow: hidden;
     vertical-align: middle;
     background-color: #fff;
     height: 50%;
}
 .component.grid .gridcontentwrapper .gridcontent {
     position: relative;
     overflow: auto;
     height: 100%;
}
 .component.grid .gridcontentwrapper .gridcontent .gridcontenttable {
     border-collapse: collapse;
     table-layout: fixed;
}
 .component.grid th {
     text-align: left;
     white-space: nowrap;
     padding: 10px 8px;
}
 .component.grid th[rowspan] {
     vertical-align: bottom;
}
 .component.grid td {
     white-space: nowrap;
     text-overflow: ellipsis;
     overflow: hidden;
     padding: 10px 8px;
}
<div id="grid" style="padding: 10px;">
    <div class="component grid" id="51177476-273f-4277-954c-dd7e576c5c9d">
        <div class="gridheaderwrapper">
            <div class="gridheadercontent">
                <table class="gridheadertable" style="width: 1177px;">
                    <colgroup>
                        <col style="width: 55px;" />
                        <col style="width: 113px;" />
                        <col style="width: 80px;" />
                        <col style="width: 95px;" />
                        <col style="width: 93px;" />
                        <col style="width: 136px;" />
                        <col style="width: 61px;" />
                        <col style="width: 292px;" />
                        <col style="width: 88px;" />
                        <col style="width: 89px;" />
                        <col style="width: 75px;" />
                    </colgroup>
                    <thead>
                        <tr>
                            <th>
                                <p>
                                    <label><input type="checkbox" class="filled-in" /><span>&nbsp;</span></label>
                                </p>
                            </th>
                            <th>Name</th>
                            <th>DataType</th>
                            <th>PrimaryKey</th>
                            <th>ForeignKey</th>
                            <th>DataAnalysisType</th>
                            <th>Visible</th>
                            <th>Id</th>
                            <th>Created</th>
                            <th>Modified</th>
                            <th>Selected</th>
                        </tr>
                    </thead>
                </table>
            </div>
        </div>
        <div class="gridcontentwrapper">
            <div class="gridcontent">
                <table class="gridcontenttable" style="width: 1177px;">
                    <colgroup>
                        <col style="width: 55px;" />
                        <col style="width: 113px;" />
                        <col style="width: 80px;" />
                        <col style="width: 95px;" />
                        <col style="width: 93px;" />
                        <col style="width: 136px;" />
                        <col style="width: 61px;" />
                        <col style="width: 292px;" />
                        <col style="width: 88px;" />
                        <col style="width: 89px;" />
                        <col style="width: 75px;" />
                    </colgroup>
                    <tbody>
                        <tr id="daa93d55-bdd5-4a20-9bef-2567efe75971">
                            <td>
                                <p>
                                    <label><input type="checkbox" class="filled-in" /><span>&nbsp;</span></label>
                                </p>
                            </td>
                            <td>CategoryID</td>
                            <td>int</td>
                            <td>true</td>
                            <td>false</td>
                            <td>2</td>
                            <td>true</td>
                            <td>daa93d55-bdd5-4a20-9bef-2567efe75971</td>
                            <td>01/01/0001</td>
                            <td>01/01/0001</td>
                            <td>false</td>
                        </tr>
                       
                    </tbody>
                </table>
            </div>
        </div>
    </div>
</div>

Collected from the Internet

Please contact [email protected] to delete if infringement.

edited at
0

Comments

0 comments
Login to comment

Related