Improve scrolling performance for sticky headers
The layout updates are anecdotal based on devtools flagging the values which are "changing" even if they aren't. The scrolling feels better with this as well, though this might be placebo.
This commit is contained in:
parent
f9aca7c05e
commit
74ca0618ac
1 changed files with 74 additions and 16 deletions
|
@ -131,13 +131,19 @@ export default class LeftPanel2 extends React.Component<IProps, IState> {
|
||||||
const headerRightMargin = 16; // calculated from margins and widths to align with non-sticky tiles
|
const headerRightMargin = 16; // calculated from margins and widths to align with non-sticky tiles
|
||||||
const headerStickyWidth = list.clientWidth - headerRightMargin;
|
const headerStickyWidth = list.clientWidth - headerRightMargin;
|
||||||
|
|
||||||
|
// We track which styles we want on a target before making the changes to avoid
|
||||||
|
// excessive layout updates.
|
||||||
|
const targetStyles = new Map<HTMLDivElement, {
|
||||||
|
stickyTop?: boolean;
|
||||||
|
stickyBottom?: boolean;
|
||||||
|
makeInvisible?: boolean;
|
||||||
|
}>();
|
||||||
|
|
||||||
let lastTopHeader;
|
let lastTopHeader;
|
||||||
let firstBottomHeader;
|
let firstBottomHeader;
|
||||||
for (const sublist of sublists) {
|
for (const sublist of sublists) {
|
||||||
const header = sublist.querySelector<HTMLDivElement>(".mx_RoomSublist2_stickable");
|
const header = sublist.querySelector<HTMLDivElement>(".mx_RoomSublist2_stickable");
|
||||||
const headerContainer = header.parentElement; // .mx_RoomSublist2_headerContainer
|
|
||||||
header.style.removeProperty("display"); // always clear display:none first
|
header.style.removeProperty("display"); // always clear display:none first
|
||||||
headerContainer.classList.remove("mx_RoomSublist2_headerContainer_hasSticky");
|
|
||||||
|
|
||||||
// When an element is <=40% off screen, make it take over
|
// When an element is <=40% off screen, make it take over
|
||||||
const offScreenFactor = 0.4;
|
const offScreenFactor = 0.4;
|
||||||
|
@ -145,27 +151,79 @@ export default class LeftPanel2 extends React.Component<IProps, IState> {
|
||||||
const isOffBottom = (sublist.offsetTop + (offScreenFactor * HEADER_HEIGHT)) >= bottomEdge;
|
const isOffBottom = (sublist.offsetTop + (offScreenFactor * HEADER_HEIGHT)) >= bottomEdge;
|
||||||
|
|
||||||
if (isOffTop || sublist === sublists[0]) {
|
if (isOffTop || sublist === sublists[0]) {
|
||||||
header.classList.add("mx_RoomSublist2_headerContainer_sticky");
|
targetStyles.set(header, { stickyTop: true });
|
||||||
header.classList.add("mx_RoomSublist2_headerContainer_stickyTop");
|
|
||||||
headerContainer.classList.add("mx_RoomSublist2_headerContainer_hasSticky");
|
|
||||||
header.style.width = `${headerStickyWidth}px`;
|
|
||||||
header.style.top = `${list.parentElement.offsetTop}px`;
|
|
||||||
if (lastTopHeader) {
|
if (lastTopHeader) {
|
||||||
lastTopHeader.style.display = "none";
|
lastTopHeader.style.display = "none";
|
||||||
|
targetStyles.set(lastTopHeader, { makeInvisible: true });
|
||||||
}
|
}
|
||||||
lastTopHeader = header;
|
lastTopHeader = header;
|
||||||
} else if (isOffBottom && !firstBottomHeader) {
|
} else if (isOffBottom && !firstBottomHeader) {
|
||||||
header.classList.add("mx_RoomSublist2_headerContainer_sticky");
|
targetStyles.set(header, { stickyBottom: true });
|
||||||
header.classList.add("mx_RoomSublist2_headerContainer_stickyBottom");
|
|
||||||
headerContainer.classList.add("mx_RoomSublist2_headerContainer_hasSticky");
|
|
||||||
header.style.width = `${headerStickyWidth}px`;
|
|
||||||
firstBottomHeader = header;
|
firstBottomHeader = header;
|
||||||
} else {
|
} else {
|
||||||
|
targetStyles.set(header, {}); // nothing == clear
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run over the style changes and make them reality. We check to see if we're about to
|
||||||
|
// cause a no-op update, as adding/removing properties that are/aren't there cause
|
||||||
|
// layout updates.
|
||||||
|
for (const header of targetStyles.keys()) {
|
||||||
|
const style = targetStyles.get(header);
|
||||||
|
const headerContainer = header.parentElement; // .mx_RoomSublist2_headerContainer
|
||||||
|
|
||||||
|
if (style.makeInvisible) {
|
||||||
|
// we will have already removed the 'display: none', so add it back.
|
||||||
|
header.style.display = "none";
|
||||||
|
continue; // nothing else to do, even if sticky somehow
|
||||||
|
}
|
||||||
|
|
||||||
|
if (style.stickyTop) {
|
||||||
|
if (!header.classList.contains("mx_RoomSublist2_headerContainer_stickyTop")) {
|
||||||
|
header.classList.add("mx_RoomSublist2_headerContainer_stickyTop");
|
||||||
|
}
|
||||||
|
|
||||||
|
const newTop = `${list.parentElement.offsetTop}px`;
|
||||||
|
if (header.style.top !== newTop) {
|
||||||
|
header.style.top = newTop;
|
||||||
|
}
|
||||||
|
} else if (style.stickyBottom) {
|
||||||
|
if (!header.classList.contains("mx_RoomSublist2_headerContainer_stickyBottom")) {
|
||||||
|
header.classList.add("mx_RoomSublist2_headerContainer_stickyBottom");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (style.stickyTop || style.stickyBottom) {
|
||||||
|
if (!header.classList.contains("mx_RoomSublist2_headerContainer_sticky")) {
|
||||||
|
header.classList.add("mx_RoomSublist2_headerContainer_sticky");
|
||||||
|
}
|
||||||
|
if (!headerContainer.classList.contains("mx_RoomSublist2_headerContainer_hasSticky")) {
|
||||||
|
headerContainer.classList.add("mx_RoomSublist2_headerContainer_hasSticky");
|
||||||
|
}
|
||||||
|
|
||||||
|
const newWidth = `${headerStickyWidth}px`;
|
||||||
|
if (header.style.width !== newWidth) {
|
||||||
|
header.style.width = newWidth;
|
||||||
|
}
|
||||||
|
} else if (!style.stickyTop && !style.stickyBottom) {
|
||||||
|
if (header.classList.contains("mx_RoomSublist2_headerContainer_sticky")) {
|
||||||
header.classList.remove("mx_RoomSublist2_headerContainer_sticky");
|
header.classList.remove("mx_RoomSublist2_headerContainer_sticky");
|
||||||
|
}
|
||||||
|
if (header.classList.contains("mx_RoomSublist2_headerContainer_stickyTop")) {
|
||||||
header.classList.remove("mx_RoomSublist2_headerContainer_stickyTop");
|
header.classList.remove("mx_RoomSublist2_headerContainer_stickyTop");
|
||||||
|
}
|
||||||
|
if (header.classList.contains("mx_RoomSublist2_headerContainer_stickyBottom")) {
|
||||||
header.classList.remove("mx_RoomSublist2_headerContainer_stickyBottom");
|
header.classList.remove("mx_RoomSublist2_headerContainer_stickyBottom");
|
||||||
header.style.removeProperty("width");
|
}
|
||||||
header.style.removeProperty("top");
|
if (headerContainer.classList.contains("mx_RoomSublist2_headerContainer_hasSticky")) {
|
||||||
|
headerContainer.classList.remove("mx_RoomSublist2_headerContainer_hasSticky");
|
||||||
|
}
|
||||||
|
if (header.style.width) {
|
||||||
|
header.style.removeProperty('width');
|
||||||
|
}
|
||||||
|
if (header.style.top) {
|
||||||
|
header.style.removeProperty('top');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue