Avoid bounding box usage in sticky headers & improve reliability

We now use offsets and scroll information to determine where the headers should be stuck to, still supporting the transparent background.

Some scroll jumps were originally introduced as part of the change in numbering, so they have been fixed here. By proxy, some additional scroll jump/instability should be fixed as well.

This has a lingering problem of still causing a huge number of no-op UI updates though, which will be dealt with in a future commit.
This commit is contained in:
Travis Ralston 2020-07-08 14:36:55 -06:00
parent 6e20850567
commit f9aca7c05e
3 changed files with 39 additions and 42 deletions

View File

@ -122,16 +122,19 @@ $tagPanelWidth: 70px; // only applies in this file, used for calculations
}
.mx_LeftPanel2_roomListWrapper {
// Create a flexbox to ensure the containing items cause appropriate overflow.
display: flex;
flex-grow: 1;
overflow: hidden;
min-height: 0;
margin-top: 12px; // so we're not up against the search/filter
&.stickyBottom {
&.mx_LeftPanel2_roomListWrapper_stickyBottom {
padding-bottom: 32px;
}
&.stickyTop {
&.mx_LeftPanel2_roomListWrapper_stickyTop {
padding-top: 32px;
}
}

View File

@ -24,10 +24,6 @@ limitations under the License.
margin-left: 8px;
width: 100%;
&:first-child {
margin-top: 12px; // so we're not up against the search/filter
}
.mx_RoomSublist2_headerContainer {
// Create a flexbox to make alignment easy
display: flex;
@ -49,10 +45,15 @@ limitations under the License.
padding-bottom: 8px;
height: 24px;
// Hide the header container if the contained element is stickied.
// We don't use display:none as that causes the header to go away too.
&.mx_RoomSublist2_headerContainer_hasSticky {
height: 0;
}
.mx_RoomSublist2_stickable {
flex: 1;
max-width: 100%;
z-index: 2; // Prioritize headers in the visible list over sticky ones
// Create a flexbox to make ordering easy
display: flex;
@ -64,7 +65,6 @@ limitations under the License.
// when sticky scrolls instead of collapses the list.
&.mx_RoomSublist2_headerContainer_sticky {
position: fixed;
z-index: 1; // over top of other elements, but still under the ones in the visible list
height: 32px; // to match the header container
// width set by JS
}

View File

@ -124,42 +124,42 @@ export default class LeftPanel2 extends React.Component<IProps, IState> {
}
private doStickyHeaders(list: HTMLDivElement) {
const rlRect = list.getBoundingClientRect();
const bottom = rlRect.bottom;
const top = rlRect.top;
const topEdge = list.scrollTop;
const bottomEdge = list.offsetHeight + list.scrollTop;
const sublists = list.querySelectorAll<HTMLDivElement>(".mx_RoomSublist2");
const headerRightMargin = 24; // calculated from margins and widths to align with non-sticky tiles
const headerStickyWidth = rlRect.width - headerRightMargin;
const headerRightMargin = 16; // calculated from margins and widths to align with non-sticky tiles
const headerStickyWidth = list.clientWidth - headerRightMargin;
let gotBottom = false;
let lastTopHeader;
let firstBottomHeader;
for (const sublist of sublists) {
const slRect = sublist.getBoundingClientRect();
const header = sublist.querySelector<HTMLDivElement>(".mx_RoomSublist2_stickable");
const headerContainer = header.parentElement; // .mx_RoomSublist2_headerContainer
header.style.removeProperty("display"); // always clear display:none first
headerContainer.classList.remove("mx_RoomSublist2_headerContainer_hasSticky");
if (slRect.top + HEADER_HEIGHT > bottom && !gotBottom) {
header.classList.add("mx_RoomSublist2_headerContainer_sticky");
header.classList.add("mx_RoomSublist2_headerContainer_stickyBottom");
header.style.width = `${headerStickyWidth}px`;
header.style.removeProperty("top");
gotBottom = true;
} else if (((slRect.top - (HEADER_HEIGHT * 0.6) + HEADER_HEIGHT) < top) || sublist === sublists[0]) {
// the header should become sticky once it is 60% or less out of view at the top.
// We also add HEADER_HEIGHT because the sticky header is put above the scrollable area,
// into the padding of .mx_LeftPanel2_roomListWrapper,
// by subtracting HEADER_HEIGHT from the top below.
// We also always try to make the first sublist header sticky.
// When an element is <=40% off screen, make it take over
const offScreenFactor = 0.4;
const isOffTop = (sublist.offsetTop + (offScreenFactor * HEADER_HEIGHT)) <= topEdge;
const isOffBottom = (sublist.offsetTop + (offScreenFactor * HEADER_HEIGHT)) >= bottomEdge;
if (isOffTop || sublist === sublists[0]) {
header.classList.add("mx_RoomSublist2_headerContainer_sticky");
header.classList.add("mx_RoomSublist2_headerContainer_stickyTop");
headerContainer.classList.add("mx_RoomSublist2_headerContainer_hasSticky");
header.style.width = `${headerStickyWidth}px`;
header.style.top = `${rlRect.top - HEADER_HEIGHT}px`;
header.style.top = `${list.parentElement.offsetTop}px`;
if (lastTopHeader) {
lastTopHeader.style.display = "none";
}
lastTopHeader = header;
} else if (isOffBottom && !firstBottomHeader) {
header.classList.add("mx_RoomSublist2_headerContainer_sticky");
header.classList.add("mx_RoomSublist2_headerContainer_stickyBottom");
headerContainer.classList.add("mx_RoomSublist2_headerContainer_hasSticky");
header.style.width = `${headerStickyWidth}px`;
firstBottomHeader = header;
} else {
header.classList.remove("mx_RoomSublist2_headerContainer_sticky");
header.classList.remove("mx_RoomSublist2_headerContainer_stickyTop");
@ -171,22 +171,16 @@ export default class LeftPanel2 extends React.Component<IProps, IState> {
// add appropriate sticky classes to wrapper so it has
// the necessary top/bottom padding to put the sticky header in
const listWrapper = list.parentElement;
if (gotBottom) {
listWrapper.classList.add("stickyBottom");
} else {
listWrapper.classList.remove("stickyBottom");
}
const listWrapper = list.parentElement; // .mx_LeftPanel2_roomListWrapper
if (lastTopHeader) {
listWrapper.classList.add("stickyTop");
listWrapper.classList.add("mx_LeftPanel2_roomListWrapper_stickyTop");
} else {
listWrapper.classList.remove("stickyTop");
listWrapper.classList.remove("mx_LeftPanel2_roomListWrapper_stickyTop");
}
// ensure scroll doesn't go above the gap left by the header of
// the first sublist always being sticky if no other header is sticky
if (list.scrollTop < HEADER_HEIGHT) {
list.scrollTop = HEADER_HEIGHT;
if (firstBottomHeader) {
listWrapper.classList.add("mx_LeftPanel2_roomListWrapper_stickyBottom");
} else {
listWrapper.classList.remove("mx_LeftPanel2_roomListWrapper_stickyBottom");
}
}