Incorporate lazyrender code from element
Signed-off-by: RMidhunSuresh <rmidhunsuresh@gmail.com>
This commit is contained in:
parent
ee072343f5
commit
452eee6767
2 changed files with 103 additions and 56 deletions
|
@ -1,5 +1,12 @@
|
||||||
.RightPanelView{
|
.RightPanelView{
|
||||||
grid-area: right;
|
grid-area: right;
|
||||||
|
min-height: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.LazyListParent {
|
||||||
|
overflow:scroll;
|
||||||
|
overflow-x:hidden;
|
||||||
|
height: 100vh;
|
||||||
}
|
}
|
||||||
|
|
||||||
.RoomDetailsView {
|
.RoomDetailsView {
|
||||||
|
@ -34,11 +41,6 @@
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
ul.MemberListView {
|
|
||||||
overflow: scroll;
|
|
||||||
height: 100vh;
|
|
||||||
}
|
|
||||||
|
|
||||||
.MemberTileView {
|
.MemberTileView {
|
||||||
display: flex;
|
display: flex;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,55 +1,88 @@
|
||||||
|
import {el} from "./html.js";
|
||||||
import {mountView} from "./utils.js";
|
import {mountView} from "./utils.js";
|
||||||
import {insertAt, ListView} from "./ListView.js";
|
import {insertAt, ListView} from "./ListView.js";
|
||||||
|
|
||||||
class Range {
|
class ItemRange {
|
||||||
constructor(start = 0, end = 0) {
|
constructor(topCount, renderCount, bottomCount) {
|
||||||
this.start = start;
|
this.topCount = topCount;
|
||||||
this.end = end;
|
this.renderCount = renderCount;
|
||||||
this._expanded = false;
|
this.bottomCount = bottomCount;
|
||||||
}
|
}
|
||||||
|
|
||||||
_onInitialExpand() {
|
contains(range) {
|
||||||
if (this._expanded) { return; }
|
// don't contain empty ranges
|
||||||
this._initialStart = this.start;
|
// as it will prevent clearing the list
|
||||||
this._expanded = true;
|
// once it is scrolled far enough out of view
|
||||||
|
if (!range.renderCount && this.renderCount) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return range.topCount >= this.topCount &&
|
||||||
|
(range.topCount + range.renderCount) <= (this.topCount + this.renderCount);
|
||||||
}
|
}
|
||||||
|
|
||||||
expandFromEnd(units) {
|
containsIndex(idx) {
|
||||||
this._onInitialExpand();
|
return idx >= this.topCount && idx <= (this.topCount + this.renderCount);
|
||||||
this.start = this.end;
|
|
||||||
this.end += units;
|
|
||||||
this._expanded = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
contains(idx) {
|
expand(amount) {
|
||||||
const start = this._expanded ? this._initialStart : this.start;
|
// don't expand ranges that won't render anything
|
||||||
return idx >= start && idx <= this.end;
|
if (this.renderCount === 0) {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
const topGrow = Math.min(amount, this.topCount);
|
||||||
|
const bottomGrow = Math.min(amount, this.bottomCount);
|
||||||
|
return new ItemRange(
|
||||||
|
this.topCount - topGrow,
|
||||||
|
this.renderCount + topGrow + bottomGrow,
|
||||||
|
this.bottomCount - bottomGrow,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
totalSize() {
|
||||||
|
return this.topCount + this.renderCount + this.bottomCount;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class LazyListView extends ListView {
|
export class LazyListView extends ListView {
|
||||||
constructor({itemHeight, height, appendCount = 5, ...options}, childCreator) {
|
constructor({itemHeight, height, ...options}, childCreator) {
|
||||||
super(options, childCreator);
|
super(options, childCreator);
|
||||||
this._itemHeight = itemHeight;
|
this._itemHeight = itemHeight;
|
||||||
this._height = height;
|
this._height = height;
|
||||||
this._appendCount = appendCount;
|
this._overflowMargin = 5;
|
||||||
this._range = new Range();
|
this._overflowItems = 20;
|
||||||
}
|
}
|
||||||
|
|
||||||
_isFullyScrolled() {
|
_getVisibleRange() {
|
||||||
return this._root.scrollHeight - Math.abs(this._root.scrollTop) === this._root.clientHeight;
|
const length = this._list ? this._list.length : 0;
|
||||||
|
const scrollTop = this._parent.scrollTop;
|
||||||
|
const topCount = Math.min(Math.max(0, Math.floor(scrollTop / this._itemHeight)), length);
|
||||||
|
const itemsAfterTop = length - topCount;
|
||||||
|
const visibleItems = this._height !== 0 ? Math.ceil(this._height / this._itemHeight) : 0;
|
||||||
|
const renderCount = Math.min(visibleItems, itemsAfterTop);
|
||||||
|
const bottomCount = itemsAfterTop - renderCount;
|
||||||
|
return new ItemRange(topCount, renderCount, bottomCount);
|
||||||
}
|
}
|
||||||
|
|
||||||
_renderMoreIfNeeded() {
|
_renderMoreIfNeeded() {
|
||||||
if (!this._isFullyScrolled()) {
|
const range = this._getVisibleRange();
|
||||||
return;
|
const intersectRange = range.expand(this._overflowMargin);
|
||||||
|
const renderRange = range.expand(this._overflowItems);
|
||||||
|
const listHasChangedSize = !!this._renderRange && this._list.length !== this._renderRange.totalSize();
|
||||||
|
console.log("currentRange", range);
|
||||||
|
console.log("renderRange", renderRange);
|
||||||
|
console.log("intersectRange", intersectRange);
|
||||||
|
console.log("LastRenderedRange", this._renderRange);
|
||||||
|
// only update render Range if the list has shrunk/grown and we need to adjust padding OR
|
||||||
|
// if the new range + overflowMargin isn't contained by the old anymore
|
||||||
|
if (listHasChangedSize || !this._renderRange || !this._renderRange.contains(intersectRange)) {
|
||||||
|
console.log("New render change");
|
||||||
|
this._renderRange = renderRange;
|
||||||
|
this._renderElementsInRange();
|
||||||
}
|
}
|
||||||
this._range.expandFromEnd(this._appendCount);
|
|
||||||
this._renderElementsInRange();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_renderElementsInRange() {
|
_renderItems(items) {
|
||||||
const items = this._list.slice(this._range.start, this._range.end);
|
|
||||||
const fragment = document.createDocumentFragment();
|
const fragment = document.createDocumentFragment();
|
||||||
for (const item of items) {
|
for (const item of items) {
|
||||||
const view = this._childCreator(item.value);
|
const view = this._childCreator(item.value);
|
||||||
|
@ -59,54 +92,66 @@ export class LazyListView extends ListView {
|
||||||
this._root.appendChild(fragment);
|
this._root.appendChild(fragment);
|
||||||
}
|
}
|
||||||
|
|
||||||
_calculateInitialRenderCount() {
|
_renderElementsInRange() {
|
||||||
return Math.ceil(this._height / this._itemHeight);
|
const { topCount, renderCount, bottomCount } = this._renderRange;
|
||||||
|
const paddingTop = topCount * this._itemHeight;
|
||||||
|
const paddingBottom = bottomCount * this._itemHeight;
|
||||||
|
const renderedItems = (this._list || []).slice(
|
||||||
|
topCount,
|
||||||
|
topCount + renderCount,
|
||||||
|
);
|
||||||
|
this._root.style.paddingTop = `${paddingTop}px`;
|
||||||
|
this._root.style.paddingBottom = `${paddingBottom}px`;
|
||||||
|
this._root.innerHTML = "";
|
||||||
|
this._renderItems(renderedItems);
|
||||||
}
|
}
|
||||||
|
|
||||||
loadList() {
|
mount() {
|
||||||
if (!this._list) {
|
const root = super.mount();
|
||||||
return;
|
this._parent = el("div", {className: "LazyListParent"}, root);
|
||||||
}
|
|
||||||
this._subscription = this._list.subscribe(this);
|
|
||||||
this._range.end = this._calculateInitialRenderCount() + this._appendCount;
|
|
||||||
this._childInstances = [];
|
|
||||||
this._renderElementsInRange();
|
|
||||||
/*
|
/*
|
||||||
Hooking to scroll events can be expensive.
|
Hooking to scroll events can be expensive.
|
||||||
But in most of these scroll events, we return early.
|
But in most of these scroll events, we return early.
|
||||||
Do we need to do more (like event throttling)?
|
Do we need to do more (like event throttling)?
|
||||||
*/
|
*/
|
||||||
this._root.addEventListener("scroll", () => this._renderMoreIfNeeded());
|
this._parent.addEventListener("scroll", () => this._renderMoreIfNeeded());
|
||||||
|
this._renderMoreIfNeeded();
|
||||||
|
return this._parent;
|
||||||
|
}
|
||||||
|
|
||||||
|
loadList() {
|
||||||
|
if (!this._list) { return; }
|
||||||
|
this._subscription = this._list.subscribe(this);
|
||||||
|
this._childInstances = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
// onAdd, onRemove, ... should be called only if the element is already rendered
|
// onAdd, onRemove, ... should be called only if the element is already rendered
|
||||||
onAdd(idx, value) {
|
onAdd() {
|
||||||
if (this._range.contains(idx)) {
|
this._renderMoreIfNeeded();
|
||||||
super.onAdd(idx, value);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
onRemove(idx, value) {
|
onRemove() {
|
||||||
if (this._range.contains(idx)) {
|
this._renderMoreIfNeeded();
|
||||||
super.onRemove(idx, value);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
onUpdate(idx, value, params) {
|
onUpdate(idx, value, params) {
|
||||||
if (this._range.contains(idx)) {
|
console.log("onUpdate");
|
||||||
|
if (this._renderRange.containsIndex(idx)) {
|
||||||
super.onUpdate(idx, value, params);
|
super.onUpdate(idx, value, params);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
recreateItem(idx, value) {
|
recreateItem(idx, value) {
|
||||||
if (this._range.contains(idx)) {
|
console.log("recreateItem");
|
||||||
|
if (this._renderRange.containsIndex(idx)) {
|
||||||
super.recreateItem(idx, value)
|
super.recreateItem(idx, value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onMove(fromIdx, toIdx, value) {
|
onMove(fromIdx, toIdx, value) {
|
||||||
const fromInRange = this._range.contains(fromIdx);
|
console.log("onMove");
|
||||||
const toInRange = this._range.contains(toIdx);
|
const fromInRange = this._renderRange.containsIndex(fromIdx);
|
||||||
|
const toInRange = this._renderRange.containsIndex(toIdx);
|
||||||
if (fromInRange && toInRange) {
|
if (fromInRange && toInRange) {
|
||||||
super.onMove(fromIdx, toIdx, value);
|
super.onMove(fromIdx, toIdx, value);
|
||||||
}
|
}
|
||||||
|
|
Reference in a new issue