-
Notifications
You must be signed in to change notification settings - Fork 6.8k
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
cdk-virtual-scroll-viewport does not handle css position: sticky cleanly #14833
Comments
any luck? looking for same solution. |
Just following up on this, any update. This is must requirement for table with large dataset. |
What i did to keep my header sticky was to use a sticky: true attribute in html. Hope this helps! |
@kelz2o3 I don't really get what your solution is. Could you add an example? |
i added this to my header row like so
|
sticky: true" is working fine with mat table if we don't use virtual scrolling, but it is messed up with virtual scrolling. Header starts moving when you start scrolling. |
My use case is that I need a sticky header always present above a virtually-scrolling list ; I'm probably not the only one. |
Here too, same problem now. @florenthobein which kind of workaround have you come up with? Edit: okay, it seems |
It's the I don't really have a neat solution. Tried to meddle with extending the So for now my "workaround" consists in:
...yeah. 😓 And a bit of a mess to have the good width for the header as it's not positioned inside the list. |
@fabioloreggian Thanks for the explanation! |
@florenthobein I found a solution to the tranformY compensation, but for some reason it's not sufficient, as once I started scrolling too far down, the headers will scroll with the content. Regardless, this is what I came up with after fiddling around a bit with the CDK scroll view port. cdk-virtual-scroll-viewport {
height: 100%;
table {
height: 100%;
width: 100%;
border-collapse: separate;
border-spacing: 0;
thead {
tr {
th {
height: 59px;
border-bottom: 1px solid #d0d0d0;
padding: 7px 20px 7px 7px;
position: sticky;
background-color: #ffffff;
top: 0;
}
}
}
} <cdk-virtual-scroll-viewport #scrollViewport itemSize="59" (scrolledIndexChange)="scrollIndexChanged($event)">
<table>
<thead>
<tr>
<th [style.transform]="inverseTranslation">Header 1</th>
<th [style.transform]="inverseTranslation">Header 2</th>
</tr>
</thead>
<tbody>
<tr *cdkVirtualFor="let movement of movements; even as even; index as i" [class.even]="even">...</tr>
</tbody>
</table>
</cdk-virtual-scroll-viewport> @ViewChild(CdkVirtualScrollViewport)
public viewPort: CdkVirtualScrollViewport;
public get inverseTranslation(): string {
if (!this.viewPort || !this.viewPort["_renderedContentTransform"]) {
return "translateY(0px)";
}
return this.viewPort["_renderedContentTransform"].replace(/translateY\((\d+)px\)/, "translateY(-$1px)");
} That gets rid of the weird effects, but only until you scrolled down for a while. Not sure what I'm doing wrong and if that's even a problem I could solve. I'm not too familiar with sticky positions, it's still sometimes a mystery how that works exactly. |
@wartab your solution is partially worked. |
@monu0751 Sadly I do not know how to solve this. That is what I mean with "but for some reason it's not sufficient, as once I started scrolling too far down, the headers will scroll with the content". This technique is to apply the opposite transform: translateY() to the column headers that CDK applies when using the virtual-scroll to create the scrollbar. This is done by adding a - sign in the regex replace. |
FWIW, I noticed that it does work as expected in Safari if you use |
I have been somewhat successful in getting around this issue by forcing |
The problem with the solution you posted is that you're using Instead use |
@lonestarjdavis Amazing, thanks for the heads up, that works like a charm! |
@wartab Could you please post a final solution, or create a Thanks. |
@artem-galas https://stackblitz.com/edit/components-issue-t3xvyz That works for my use case. |
@wartab Looks great with chrome. |
@lujian98 |
Thank you @wartab I am using latest Firefox. |
Nice one @wartab! |
inverseTranslation function can be improved as follows: get inverseTranslation(): string {
const offset = this.viewPort.getOffsetToRenderedContentStart();
return `-${offset}px`;
}
or
get inverseTranslation(): string {
return `-${this.viewPort.getOffsetToRenderedContentStart()}px`;
} |
@mmalerba Can you get this on the virtual scrolling bug list |
that was a good example and it actually keeps the header in top, but in my case I have resizable columns and header position fails when moving :( look here https://gifyu.com/image/kyj4 |
I am in need of this feature as well. @wartab Can I ask Also, it throws this pretty often:
|
The The CdkVirtualScrollViewport has a ScrollStrategy private property offering a <tr>
<th [style.top]="headerTop">Header 1</th>
<th [style.top]="headerTop">Header 2</th>
</tr> headerTop = '0px';
ngAfterViewInit(): void {
if (!!this.viewPort) {
this.viewPort['_scrollStrategy'].onRenderedOffsetChanged = () =>
(this.headerTop = `-${this.viewPort.getOffsetToRenderedContentStart()}px`);
}
} |
Working example with dynamic sticky items with correct placement: Remember to implement something like:
|
Hello there, I came across a similar issue and used the useful advices from this thread (thanks very much for sharing btw !!!). However, this doesn't work if your table is handled with an *ngIf directive. The sticky headers workaround gets broken, when the *ngIf directive removes the table from the dom, therefore the @ViewChild assignment "gets lost" as the variable value is not refreshed. My workaround was to use a setter instead of a variable, in my component :
In my template :
|
Here is my solution using a directive. It uses the MutationObserver api to detect attributes changes on the scroll view port wrapper and then update the header top position. directive:
css:
|
Once #24394 is released (I guess 14.1), it is possible to have sticky elements before the scroll-viewport but within the scrolling element (adopted from the dev-app): <div class="demo-viewport" cdkVirtualScrollingElement>
<p class="position: sticky; top: 0">Content before virtual scrolling items</p>
<cdk-virtual-scroll-viewport itemSize="50">
<div *cdkVirtualFor="let size of fixedSizeData; let i = index" class="demo-item"
[style.height.px]="size">
Item #{{i}} - ({{size}}px)
</div>
</cdk-virtual-scroll-viewport>
<p>Content after virtual scrolling items</p>
</div> So with this it should be possible to implement sticky headers. However, using @mmalerba what do you think about this approach: Exposing the current translate value as a css variable. Doing this would allow developers to make elements sticky like this, without the need of any additional js: .sticky-element {
position: sticky;
top: calc(<wanted-top-value> + var(--cdk-scrolling-offset))
} |
That's an interesting idea. Let me add it to our agenda for team discussion since we don't currently have any APIs like this |
Hi @spike-rabbit, I had a chance to discuss this with the team. In general we're not opposed to this type of API in situations where it makes sense. Folks did have a couple of concerns:
I do see that a lot of the requests here have to do with making sticky header elements that are not virtualized items, but just persistent elements that appear within the scrolling element. I guess I'm curious what the reason for putting these headers inside the scrolling element is. Why not just have them outside so they're naturally "sticky"? (Sorry if I'm just missing something obvious) <div class="box-with-border">
<div>Header</div>
</div>
<cdk-virtual-scroll-viewport>
<div *cdkVirtualFor="...">Item</div>
</cdk-virtual-scroll-viewport> If there's a good motivation for needing them to be inside the scrolling element, and the performance concerns turn out to not be an issue, I could see adding this. |
@mmalerba I poorly reimplemented a use case that I have: https://stackblitz.com/edit/angular-ujjkis. In the real life application, there are much more items so that we needed to virtualize scrolling. What I am trying to show there is, that sticky elements can be used in larger items that are scrolled, if they have some kind of title or other meta information that should be visible while the item is in the viewport. Regarding your concerns:
I hope this is understandable. Unfortunately, I cannot share the link to the real application as it is located in our Intranet. |
Ah ok, that seems like a reasonable use case. I wasn't thinking about a single item possibly being bigger than the viewport. I think we would probably be fine adding a CSS variable then. The next step would just be to do some quick testing to see if setting the variable somehow impacts performance |
The reason this gets cited a lot is because specifically tables tend to misbehave frequently if you insert things that don't belong there. Addtionally, they make it quite trivial to have the header columns the same width as the body columns without forcing the width. |
@mmalerba so how do we proceed here? Shall I create a pull request, and you do the performance testing? |
@spike-rabbit sure, if you send me a PR I can look into how to test the performance |
As you have mentioned, this only works for non-table elements. Since the
Have you ever considered making this a directive instead? Most of its logic could be the same, but instead of manipulating the DOM directly, the directive would provide (in addition to the other values) two "spacing" values. One that represents an offset from the top, and another one from the bottom. Using these values, the user could offset the elements appropriately. Take a look at a non-Angular example. This solution only refreshes the table on scroll finish event, however, the idea is the same. When the viewport items needs to be refreshed, the first and last "imaginary" row height is updated accordingly. |
Hey, is there any news on this? Are there plans to allow sticky elements to work inside a table? |
i found somewhat a clean solution, it has it's own caveat though the headers do flicker, but that is just about it
96 is just the double the row height value |
Hi all, has anyone found a solution for this without any header flicker? |
I'm also searching for a solution, can it be fixed? |
@mmalerba Is it possible to increase the priority for this issue? |
+1 |
1 similar comment
+1 |
More than 4 years have passed since the opening of the issue, and the solution has not appeared. It is sad. |
I managed to make it work. Not perfect, but it works.
|
my implementation, may help u
|
Good afternoon devs, developer here in Brazil, still no plausible solution for this header problem with CdkVirtualScroll?
but I don't find a solution... :/ |
based on that, i found working clean solution.
|
Worked for me although not when you do a responsive table for horizontal scroll(more this is a cdk virtual scrolling issue imo) |
You are best! |
I have fixed above issue by adding new class in the tr mat-header-row tag with class name-“table-row" put below css in the class name-
|
What is the expected behavior?
using the css attribute
position: sticky
should allow for elements to remain displayed according to their sticky parameters.What is the current behavior?
This works for a while but then breaks after a point. Im assuming its a matter of the 'stickied' dom elements getting recycled.
What are the steps to reproduce?
https://stackblitz.com/edit/angular-wnar6b-wzjdmh
This is a slightly modified version of an example from the docs. As you scroll the cdk-virtual-scroll-viewport you will notice the light blue headers stick to the the top of their area as expected. Once you exceed a certain amount of scrolling however the headers disappear. For me items 0-2 work but the 3rd one breaks.
Down below is an example using the [non-virtual] scrolling. Here the css attribute works as expected.
Which versions of Angular, Material, OS, TypeScript, browsers are affected?
Forked from https://stackblitz.com/angular/apvvljrovad from the docs. no versions changed.
Is there anything else we should know?
#11621 also gets at my base requirement; wanting some elements of the virtual scroll list to 'stick around'. It would be ideal if standard css properties worked as via this ticket but that might be hard as the whole point of virtual-scroll is to remove things from the dom that 'shouldn't' be rendered. The position: sticky css attribute would seem to break those assumptions.
The text was updated successfully, but these errors were encountered: