Skip to content

Commit d7c3ca4

Browse files
authored
refactor: disable virtualizer height placeholder in grid and combo-box (#10155)
1 parent 9720a00 commit d7c3ca4

File tree

4 files changed

+85
-29
lines changed

4 files changed

+85
-29
lines changed

packages/combo-box/src/vaadin-combo-box-scroller-mixin.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -234,6 +234,11 @@ export const ComboBoxScrollerMixin = (superClass) =>
234234
scrollTarget: this,
235235
scrollContainer: this.$.selector,
236236
reorderElements: true,
237+
// Combo-box items have a CSS-defined minimum height, so the virtualizer's
238+
// height placeholder logic can be disabled. This helps save reflows which
239+
// might otherwise be triggered by this logic because it reads the row height
240+
// right after updating the rows' content.
241+
__disableHeightPlaceholder: true,
237242
});
238243
}
239244

packages/component-base/src/virtualizer-iron-list-adapter.js

Lines changed: 45 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -14,15 +14,29 @@ const MAX_VIRTUAL_COUNT = 100000;
1414
const OFFSET_ADJUST_MIN_THRESHOLD = 1000;
1515

1616
export class IronListAdapter {
17-
constructor({ createElements, updateElement, scrollTarget, scrollContainer, elementsContainer, reorderElements }) {
17+
constructor({
18+
createElements,
19+
updateElement,
20+
scrollTarget,
21+
scrollContainer,
22+
reorderElements,
23+
elementsContainer,
24+
__disableHeightPlaceholder,
25+
}) {
1826
this.isAttached = true;
1927
this._vidxOffset = 0;
2028
this.createElements = createElements;
2129
this.updateElement = updateElement;
2230
this.scrollTarget = scrollTarget;
2331
this.scrollContainer = scrollContainer;
24-
this.elementsContainer = elementsContainer || scrollContainer;
2532
this.reorderElements = reorderElements;
33+
this.elementsContainer = elementsContainer || scrollContainer;
34+
35+
// Internal option that disables the heavy height placeholder calculation
36+
// (see __afterElementsUpdated) for components that always render virtual
37+
// elements with a non-zero height. Not for public use.
38+
this.__disableHeightPlaceholder = __disableHeightPlaceholder ?? false;
39+
2640
// Iron-list uses this value to determine how many pages of elements to render
2741
this._maxPages = 1.3;
2842

@@ -270,33 +284,35 @@ export class IronListAdapter {
270284
* @param {!Array<!HTMLElement>} updatedElements
271285
*/
272286
__afterElementsUpdated(updatedElements) {
273-
updatedElements.forEach((el) => {
274-
const elementHeight = el.offsetHeight;
275-
if (elementHeight === 0) {
276-
// If the elements have 0 height after update (for example due to lazy rendering),
277-
// it results in iron-list requesting to create an unlimited count of elements.
278-
// Assign a temporary placeholder sizing to elements that would otherwise end up having
279-
// no height.
280-
el.style.paddingTop = `${this.__placeholderHeight}px`;
281-
el.style.opacity = '0';
282-
el.__virtualizerPlaceholder = true;
283-
284-
// Manually schedule the resize handler to make sure the placeholder padding is
285-
// cleared in case the resize observer never triggers.
286-
this.__placeholderClearDebouncer = Debouncer.debounce(this.__placeholderClearDebouncer, animationFrame, () =>
287-
this._resizeHandler(),
288-
);
289-
} else {
290-
// Add element height to the queue
291-
this.__elementHeightQueue.push(elementHeight);
292-
this.__elementHeightQueue.shift();
293-
294-
// Calculate new placeholder height based on the average of the defined values in the
295-
// element height queue
296-
const filteredHeights = this.__elementHeightQueue.filter((h) => h !== undefined);
297-
this.__placeholderHeight = Math.round(filteredHeights.reduce((a, b) => a + b, 0) / filteredHeights.length);
298-
}
299-
});
287+
if (!this.__disableHeightPlaceholder) {
288+
updatedElements.forEach((el) => {
289+
const elementHeight = el.offsetHeight;
290+
if (elementHeight === 0) {
291+
// If the elements have 0 height after update (for example due to lazy rendering),
292+
// it results in iron-list requesting to create an unlimited count of elements.
293+
// Assign a temporary placeholder sizing to elements that would otherwise end up having
294+
// no height.
295+
el.style.paddingTop = `${this.__placeholderHeight}px`;
296+
el.style.opacity = '0';
297+
el.__virtualizerPlaceholder = true;
298+
299+
// Manually schedule the resize handler to make sure the placeholder padding is
300+
// cleared in case the resize observer never triggers.
301+
this.__placeholderClearDebouncer = Debouncer.debounce(this.__placeholderClearDebouncer, animationFrame, () =>
302+
this._resizeHandler(),
303+
);
304+
} else {
305+
// Add element height to the queue
306+
this.__elementHeightQueue.push(elementHeight);
307+
this.__elementHeightQueue.shift();
308+
309+
// Calculate new placeholder height based on the average of the defined values in the
310+
// element height queue
311+
const filteredHeights = this.__elementHeightQueue.filter((h) => h !== undefined);
312+
this.__placeholderHeight = Math.round(filteredHeights.reduce((a, b) => a + b, 0) / filteredHeights.length);
313+
}
314+
});
315+
}
300316

301317
if (this.__pendingScrollToIndex !== undefined && !this.__hasPlaceholders()) {
302318
this.scrollToIndex(this.__pendingScrollToIndex);

packages/component-base/test/virtualizer-item-height.test.js

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -412,3 +412,33 @@ describe('virtualizer - item height - lazy rendering', () => {
412412
});
413413
});
414414
});
415+
416+
describe('virtualizer - item height - placeholders are disabled', () => {
417+
let virtualizer;
418+
let scrollTarget;
419+
420+
beforeEach(() => {
421+
scrollTarget = fixtureSync(`
422+
<div style="height: 200px;">
423+
<div class="container"></div>
424+
</div>
425+
`);
426+
427+
virtualizer = new Virtualizer({
428+
createElements: (count) => Array.from({ length: count }, () => document.createElement('div')),
429+
updateElement: (el, index) => {
430+
el.id = `item-${index}`;
431+
},
432+
scrollTarget,
433+
scrollContainer: scrollTarget.firstElementChild,
434+
__disableHeightPlaceholder: true,
435+
});
436+
437+
virtualizer.size = 1;
438+
});
439+
440+
it('should not add placeholder padding to items with zero height', () => {
441+
const item = document.querySelector('#item-0');
442+
expect(item.offsetHeight).to.equal(0);
443+
});
444+
});

packages/grid/src/vaadin-grid-mixin.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -256,6 +256,11 @@ export const GridMixin = (superClass) =>
256256
scrollContainer: this.$.items,
257257
scrollTarget: this.$.table,
258258
reorderElements: true,
259+
// Grid rows have a CSS-defined minimum height, so the virtualizer's height
260+
// placeholder logic can be disabled. This helps save reflows which might
261+
// otherwise be triggered by this logic because it reads the row height
262+
// right after updating the rows' content.
263+
__disableHeightPlaceholder: true,
259264
});
260265

261266
new ResizeObserver(() =>

0 commit comments

Comments
 (0)