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> </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> </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>
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> </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> </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.
Comments