-
-
Notifications
You must be signed in to change notification settings - Fork 45
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
Table rows invisible when scrolling a table anchored part way down a document #178
Comments
@SabineWren thanks. At the moment, slotting with shadow DOM is not well supported, in case there's something that will affect the container distance to the first repeated item. For your case, forcefully changing it to 0 will probably cause issue in other usages where it's not at the top. I'll come up with some fix for this. Can you help with a repro? |
Yes, but I don't have time this week. |
Gist errored when I tried creating creating a fork, and I couldn't get CodeSandbox to work even with a basic custom element. So, you get a zip. I'm including a page and its custom elements. It's configured for typescript and webpack. You'll need to configure the route in a skeleton app. |
There's probably a way to make it work seamlessly with shadowDOM mode <div>
<div virtual-repeat.for="item of items" calc-top-distance.bind="calcTopDistance">...</div>
</div> And the method can have following signature: function calcTopDistance(topBuffer: HTMLElement, parent: HTMLElement, repeat: VirtualRepeat); number; I'm not 100% sure about this yet, thoughts? |
Yeah, closed is probably a no-go. However, I don't see why there would be a difference to the library between the normal (light) dom and (open) shadow doms except for the root. Replace the hard-coded document.root with virtualizableNode.getRootNode(), and everything else should be exactly the same. Soon after opening this issue I cloned this repo into our own codebase and fixed it. I couldn't read much of the code, so I deleted everything that wasn't related to tables, and I don't remember everything I changed or why. If you want I can code dump the whole thing, but it's not mergeable with how much I deleted. The most important part was probably the getViewRange function in array-virtual-repeat-strategy.ts
Maybe the requirements differ for virtualized elements that aren't table rows, but I don't think it matters where the 'top' element is. The library only needs to know how many rows it can show at once, and how many rows it has already scrolled. It shouldn't care where on the page, so no top distance needed. This is true even if the max-height container has other elements besides the table, such as a title or controls. The edge case is when you have less rows in the table than can fit in the max-height. I deleted the 500ms polling, in favour of subscribing to events that change table visibility or size. That triggers a render on the next event loop and re-measures row height. In our case I subcribed to events from the plugin, but as a standalone library it would just need to expose a "re-render" function and let the caller handle event notifications. I also appended 'overlay' to the scrollable element detection ('auto', 'scroll'). |
To continue our discussion, let's spec out what scenarios should work. The implementation should support the following table:
1
list-container:
<template>
<div style="height: 500px; overflow-y: auto">
<slot></slot>
</div>
</template>
Usage:
<list-container>
<div virtual-repeat.for="item of items">${item.name}</div>
</list-container> 2
list-container:
<template>
<div style="height: 500px; overflow-y: auto">
<slot></slot>
</div>
</template>
Usage:
1.
<list-container>
<div>
<div virtual-repeat.for="item of items">${item.name}</div>
</div>
</list-container>
2.
<list-container>
<table>
<tr virtual-repeat.for="item of items"><td>${item.name}</td></tr>
</table>
</list-container>
3.
<list-container>
<table>
<tbody virtual-repeat.for="item of items">
<tr><td>${item.name}</td></tr>
</tbody>
</table>
</list-container>
4.
<list-container>
<ul>
<li virtual-repeat.for="item of items">
${item.name}
</li>
</ul>
</list-container> 3
list-container:
<template>
<div style="height: 500px; overflow-y: auto">
<slot></slot>
</div>
</template>
panel:
<template>
<list-container>
<slot></slot>
</list-container>
</template>
Usage:
<panel>
<div virtual-repeat.for="item of items">${item.name}</div>
</panel> .... more examples to be updated |
It seems you're trying to squeeze every bit of performance by calculating exactly how many rows need to be rendered. You've discovered lots of edge cases, since it's a hard problem. However, I suspect you're over-complicating things for minimal performance gain. Let's take your example of a bunch of static content (header rows, etc.) followed by 3 virtualized lists of table rows in a single container. We render the static content even if it's scrolled out of view. I never needed to use more than one virtual repeat per table, but it doesn't change the problem complexity. Let's consider the code I showed above. For each virtual list, we calculate the most rows potentially visible by taking the size of the container divided by the row height. That's usually 30-50 rows per virtual list for a typical table. Scrolled to the very top with 3 virtualized lists, that might render a hundred or so extra rows in overflow, because it fills the container once for each virtual list. Yes it's a bit wasteful, but the whole point of virtualizing a table is to scale to thousands of rows with the performance of <100 rendered in the DOM. It's not a big deal if a few extras rows are rendered in overflow, as it still clamps the performance hit. If you have a small number of rows, then there's no reason to virtualize in the first place. With multiple virtual lists, scroll position becomes harder. You could register each virtual-repeat to an internal list of repeats per container, allowing them to communicate. Maybe expose total virtual height of each repeater. I don't know if it's worth the complexity of implementing support for multiple repeaters. With my approach, Shadow/Light DOM is irrelevant, the algorithm is extremely simple to implement, there are no edge cases, and the performance is acceptable. I haven't encountered any issues since rolling it out. However, I only implemented it for TR elements. |
@SabineWren If I understood your comment right, I should say it's less about performance, but more about correctness. This scenario: <div class="scroll-container" style="height: 500px;">
<h1 style="height: 50px">Static content</h1>
... more static content 50px
... more static content 50px
<div virtual-repeat.for="item of items"></div>
... some static content for sumarizing
</div> Whenever there's a scroll event in scroll container, it's incorrect to just show based on current scroll top of the scroll container. If the scroll top is 200px:
My previous comment about multiple repeater is a bit off the point, as it's just an extreme case of this example. |
Then if you have a single virtual-repeater, would the exact start be (scrolltop) - (height of previous elements), and the total virtual height be (height of previous elements) + (rowHeight * numRows) + (height of subsequent elements)? I'd have to look at the code I used for scroll position to see if it's similar. The original bug I reported was somehow the anchor position of the container's host element affected things, which makes no sense to me. |
@SabineWren yes it is. Except when your repeat is not on the same level with previous elements: <div class="scroll-container" style="height: 500px;">
<h1 style="height: 50px">Static content</h1>
... more static content 50px
... more static content 50px
<div>
... more static content 50px
<div virtual-repeat.for="item of items"></div>
... some static content for sumarizing
</div>
</div> This scenario is just an exaggeration of table: <table>
<thead>
<tr>...content of this header row takes some px, and is not on the same level with the repeated row
</thead>
<tbody>
<tr virtual-repeat.for="item of items">
</tbody>
</table> |
I'm submitting a bug report
1.0.0-beta.7
Please tell us about your environment:
Operating System:
Win 10
Node Version:
10.16.2
NPM Version:
6.9.0
Webpack Version
webpack 4.41.6
Browser:
Chrome 81.0.4044.113 (Official Build) (64-bit)
Language:
TypeScript 3.6.4
Use Case
I put
virtual-repeat.for
on a tr element inside a table. The table is inside a scroll container which is inside a custom element using Shadow DOM.Current behavior:
Everything works fine with the element anchored to the top of the document. However, scrolling breaks when anchoring the element part way down the page. The farther down the document a table is anchored, and the more a user scrolls down the table, the more rows are hidden and replaced with blank space. Increasing the height of the scroll container proportionally to the anchor distance from the top of the document mitigates the problem.
I tried using fixed row heights with no change in behaviour. Zooming in/out changes the number of rows that become invisible.
Expected/desired behavior:
Rows should scroll into view, even when the table isn't anchored to the top of the page.
Fix
I got the plugin working perfectly for my use case by cloning the ui-virtualization plugin and editing the function
getViewRange
in filearray-virtual-repeat-strategy.ts.
const topBufferDistance = 0;//getDistanceToParent(topBufferEl, scrollerEl);
You can see the helper function that I zeroed out. That function's probably important for something, but replacing it with a zero seems to fix the bug.
The text was updated successfully, but these errors were encountered: