Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

bug(table): Sticky header flickering #21576

Open
tamtakoe opened this issue Jan 13, 2021 · 10 comments
Open

bug(table): Sticky header flickering #21576

tamtakoe opened this issue Jan 13, 2021 · 10 comments
Labels
area: material/table P4 A relatively minor issue that is not relevant to core functions

Comments

@tamtakoe
Copy link

tamtakoe commented Jan 13, 2021

Sticky header works with flickering with infinity scroll especially if scroll opposite direction. This is well-known issue for a couple of years, that appeared in some topics but still has no special topic.

Reproduction

Screen.Recording.2021-01-13.at.19.30.26.mov

E.g. with old version of Angular, but the same with last one. Also there are a lot of examples here #10122 (comment) or in other issues where the effect appeared
https://stackblitz.com/edit/mat-table-virtual-scroll-div

  • Angular: v10, v11
  • CDK/Material: v10, v11
  • Browser(s): Chrome, Firefox, Safari, Edge
  • Operating System: Windows, macOS, Ubuntu
@tamtakoe tamtakoe added the needs triage This issue needs to be triaged by the team label Jan 13, 2021
@crisbeto
Copy link
Member

I wasn't able to reproduce it, but my guess is that it's due to the top value being updated as the user is scrolling. Can you still see it if you apply transform: translateZ(0) to .mat-table-sticky. Note that you may have to do it in your global styles or disable view encapsulation in order for the style to be picked up.

@crisbeto crisbeto added area: material/table needs: clarification The issue does not contain enough information for the team to determine if it is a real bug and removed needs triage This issue needs to be triaged by the team labels Jan 18, 2021
@tamtakoe
Copy link
Author

tamtakoe commented Jan 19, 2021

@crisbeto yes, I tried translateZ(0) and some other hacks. Nothing helps. I think you have powerful PC. It has perceptible effect for huge tables with heavy rows. E.g. this example has flickering every time I scroll by vertical scroll bar because it causes lots of renderings. For heavy table I see flickering during wheel scroll also.

P.S. Other laptop with Windows 10
https://user-images.githubusercontent.com/2244675/104974758-f8c81600-5a09-11eb-8d30-f0bc2c60e842.MP4

@crisbeto
Copy link
Member

I see. It seems like we shouldn't really have to set the top value while scrolling, considering that we're using position: sticky already. @andrewseguin, do you know the context behind the current behavior?

@garrettld
Copy link

garrettld commented Jan 25, 2021

@crisbeto I think I can provide some context. I've spent a lot of time digging into implementing virtual scrolling with CDK tables recently and I've been able to address the flickering in my projects at work. Note: I'm using the latest Chrome on Windows 10 for testing this.

The reason that top must be set in the way shown in the example is that the sticky header row is translated the same distance as the viewport, even though it's sticky relative to the <cdk-virtual-scroll-viewport> and not the inner <div> that is being translated. The resulting user experience is that the header row will kind of "jump" down the equivalent of N rows at a time, where N is the number of rows at the "front" of the data set that are not actually rendered due to virtual scrolling. If you scroll down until the first rendered row is at index 2 (the third item) in the original data source, then the virtual scroll viewport's translateY will be 96px (2 rows * 48px) and the sticky header row will be positioned 96px lower than the top of the viewport. I've edited the stackblitz so that you can see that here (scroll down slowly to see it happen):

https://stackblitz.com/edit/mat-table-virtual-scroll-div-qxryk4

The reason that these elements are translated in spite of their position property is that layout (including sticky positioning) is applied by the browser before translation is considered, according to the spec. Since the sticky row is inside both the viewport and the translated div, the browser positions it correctly and then translates it afterwards. Then when we set top on the sticky row to account for the offset, the browser is forced to calculate layout again. The result is that the row is rendered in the incorrect position for one frame, then its top is changed and it gets rendered again in the correct position on the next frame, which causes the flickering. The interactions between sticky elements, scrollable containers, and transformed containers are discussed in various browser bug reports and related github repos (for example: w3c/csswg-drafts#3186). Similar issues occur when elements with position: fixed are contained by a transformed element and/or a scrollable element. The general sense I get from reading through these bug reports and issues is that it's working as intended.

Using position: absolute and top: #px instead of translateY(#px) on the <div> inside the virtual scroll viewport component prevents the flickering issue by avoiding having a transformed container in the first place. It also removes the need to set top on any sticky elements. That's a pretty significant change, so I don't know if that's something that the Angular team would want to or be able to land in the CDK. Since there's no way to change that behavior of the CDK virtual scroll viewport, I've thrown together a proof of concept to show that position: absolute + top: #px fixes the flickering issue, which you can see here: https://stackblitz.com/edit/virtual-scroll-cdk-table-test?file=src/app/my-table/my-table.component.html

If you un-comment the cdk-virtual-scroll-viewport and comment out the app-table-virtual-scroll-viewport you can see the flickering return.

Another solution is to move the sticky element(s) out of the transformed container so that they aren't affected by the translation. This isn't always possible, especially with components that require specific markup like tables. #20414 hints at doing that, but it's a bit unclear to me how that would work with virtual scrolling, and it only works for flex tables.

Sorry for the essay. I hope it helps!

@lujian98
Copy link

I also spent many hours try to put table with header under the virtual scroll and it seems have too many problems.
The simple way to avoid these flickering issues of the sticky header is create a separate table header which can align with virtual table.

@tamtakoe
Copy link
Author

@lujian98 Yes, I have the same conclusion. Unfortunately cdk-table doesn't provide this way. Probably it is because of bad semantics. I think this can be sacrificed for the infinite scrolling that has been working on computers since the last century.

@lujian98
Copy link

@tamtakoe Below is what I did for the table header columns, and table data rows with virtual scroll.
With a separate table header, the table data component can be cdk-table or tree grid with cdk-tree.

// table header component (table with only header columns without data row)
<cdk-table...>
  <ng-container...>
    <cdk-header-cell...>
      ...
    </cdk-header-cell>
  </ng-container>

  <cdk-header-row...>  // for table header columns
  </cdk-header-row>
</cdk-table>


// table data with virtual scroll component (table with only data without header row)
<cdk-virtual-scroll-viewport...>
  <cdk-table...>
    <ng-container...>
      <cdk-cell...>
        ...
      </cdk-cell>
    </<ng-container>

  <cdk-row...>    // for table data
  </cdk-row>
  </cdk-table>
</cdk-virtual-scroll-viewport>

@MichaelJamesParsons
Copy link
Collaborator

Moving table headers and footers outside the virtual viewport will resolve the flickering. As @garrettld noted, I experimented with the possibility of this in #20414. There are some challenges with this solution that we're working to resolve:

  1. Accessibility - Detaching the body from the rest of the table breaks a11y interactions between the table and screen readers.
  2. Horizontal scrolling - The header, footer, or body containers need to scroll in sync when scrolling horizontally, while hiding extra scroll bars that might appear between containers.

@tamtakoe
Copy link
Author

tamtakoe commented Jan 28, 2021

@lujian98 I tried the same way, but I used native one-row table for header and custom directive which reconciles column width for header and body. It works perfect but it has really terrible UX for developers. Especially if you have many tables which should be supported by several developers. That is why I offered this way which allows to wrap e.g. cdk-table template to custom one. Unfortunately it was declined by angular team.

@wagnermaciel wagnermaciel added needs triage This issue needs to be triaged by the team and removed needs: clarification The issue does not contain enough information for the team to determine if it is a real bug labels Jun 24, 2022
@zarend zarend added P4 A relatively minor issue that is not relevant to core functions and removed needs triage This issue needs to be triaged by the team labels Jul 8, 2022
@sunilpanc
Copy link

Any fix for this issue?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area: material/table P4 A relatively minor issue that is not relevant to core functions
Projects
None yet
Development

No branches or pull requests

8 participants