Skip to content

Commit

Permalink
Improve performance
Browse files Browse the repository at this point in the history
  • Loading branch information
Emmanuel Garcia committed Sep 1, 2016
1 parent db89558 commit da016a4
Showing 1 changed file with 54 additions and 86 deletions.
140 changes: 54 additions & 86 deletions iron-list.html
Original file line number Diff line number Diff line change
Expand Up @@ -513,11 +513,6 @@
*/
_itemsRendered: false,

/**
* The page that is currently rendered.
*/
_lastPage: null,

/**
* The max number of pages to render. One page is equivalent to the height of the list.
*/
Expand Down Expand Up @@ -560,6 +555,9 @@
*/
_rowHeight: 0,


_templateCost: 0,

/**
* The bottom of the physical content.
*/
Expand Down Expand Up @@ -736,6 +734,7 @@
get _defaultScrollTarget() {
return this;
},

get _virtualRowCount() {
return Math.ceil(this._virtualCount / this._itemsPerRow);
},
Expand All @@ -754,14 +753,15 @@

attached: function() {
this.updateViewportBoundaries();
this._render();
if (this._physicalCount === 0) {
this._debounceTemplate(this._render);
}
// `iron-resize` is fired when the list is attached if the event is added
// before attached causing unnecessary work.
this.listen(this, 'iron-resize', '_resizeHandler');
},

detached: function() {
this._itemsRendered = false;
this.unlisten(this, 'iron-resize', '_resizeHandler');
},

Expand Down Expand Up @@ -886,7 +886,7 @@
}

if (recycledTiles === 0) {
// Try to increase the pool if the list's client height isn't filled up with physical items
// Try to increase the pool if the list's client isn't filled up with physical items
if (physicalBottom < scrollBottom || this._physicalTop > scrollTop) {
this._increasePoolIfNeeded();
}
Expand All @@ -903,29 +903,25 @@
* @param {!Array<number>=} movingUp
*/
_update: function(itemSet, movingUp) {
// manage focus
this._manageFocus();
// update models
this._assignModels(itemSet);
// measure heights
this._updateMetrics(itemSet);
// adjust offset after measuring
// Adjust offset after measuring.
if (movingUp) {
while (movingUp.length) {
var idx = movingUp.pop();
this._physicalTop -= this._getPhysicalSizeIncrement(idx);
}
}
// update the position of the items
this._positionItems();
// set the scroller size
this._updateScrollerSize();
// increase the pool of physical items
this._increasePoolIfNeeded();
},

/**
* Creates a pool of DOM elements and attaches them to the local dom.
*
* @param {number} size Size of the pool
*/
_createPool: function(size) {
var physicalItems = new Array(size);
Expand All @@ -935,7 +931,7 @@
for (var i = 0; i < size; i++) {
var inst = this.stamp(null);
// First element child is item; Safari doesn't support children[0]
// on a doc fragment
// on a doc fragment.
physicalItems[i] = inst.root.querySelector('*');
Polymer.dom(this).appendChild(inst.root);
}
Expand All @@ -952,29 +948,25 @@
if (this._viewportHeight === 0) {
return false;
}
// Base case 2: If the physical size is optimal and the list's client height is full
var self = this;
var isClientFull = this._physicalBottom >= this._scrollBottom &&
this._physicalTop <= this._scrollPosition;

// Base case 2: if the physical size is optimal and the list's client height is full
// with physical items, don't increase the pool.
var isClientHeightFull = this._physicalBottom >= this._scrollBottom && this._physicalTop <= this._scrollPosition;
if (this._physicalSize >= this._optPhysicalSize && isClientHeightFull) {
if (this._physicalSize >= this._optPhysicalSize && isClientFull) {
return false;
}
// this value should range between [0 <= `currentPage` <= `_maxPages`]
var currentPage = Math.floor(this._physicalSize / this._viewportHeight);

if (currentPage === 0) {
// fill the first page
this._debounceTemplate(this._increasePool.bind(this, Math.round(this._physicalCount * 0.5)));
} else if (this._lastPage !== currentPage && isClientHeightFull) {
// paint the page and defer the next increase
// wait 16ms which is rough enough to get paint cycle.
Polymer.dom.addDebouncer(this.debounce('_debounceTemplate', this._increasePool.bind(this, this._itemsPerRow), 16));
} else {
// fill the rest of the pages
this._debounceTemplate(this._increasePool.bind(this, this._itemsPerRow));
var maxPoolSize = Math.round(this._physicalCount * 0.5);
// Increase the pool synchronously until the client is filled.
if (!isClientFull) {
this._increasePool(maxPoolSize);
return true;
}

this._lastPage = currentPage;

Polymer.dom.addDebouncer(this.debounce('_debounceTemplate', function() {
self._increasePool(Math.min(maxPoolSize,
Math.max(1, Math.round(50 / self._templateCost))));
}, 16));
return true;
},

Expand All @@ -989,17 +981,16 @@
);
var prevPhysicalCount = this._physicalCount;
var delta = nextPhysicalCount - prevPhysicalCount;
var ts = performance.now();

if (delta <= 0) {
return;
}

// Concat arrays in place.
[].push.apply(this._physicalItems, this._createPool(delta));
[].push.apply(this._physicalSizes, new Array(delta));

this._physicalCount = prevPhysicalCount + delta;

// update the physical start if we need to preserve the model of the focused item.
// Update the physical start if it needs to preserve the model of the focused item.
// In this situation, the focused item is currently rendered and its model would
// have changed after increasing the pool if the physical start remained unchanged.
if (this._physicalStart > this._physicalEnd &&
Expand All @@ -1008,19 +999,19 @@
this._physicalStart = this._physicalStart + delta;
}
this._update();
this._templateCost = (performance.now() - ts) / delta;
},

/**
* Render a new list of items. This method does exactly the same as `update`,
* but it also ensures that only one `update` cycle is created.
* Render a new list of items.
*/
_render: function() {
var requiresUpdate = this._virtualCount > 0 || this._physicalCount > 0;

if (this.isAttached && !this._itemsRendered && this._isVisible && requiresUpdate) {
this._lastPage = 0;
this._update();
this._itemsRendered = true;
if (this.isAttached && this._isVisible) {
if (this._physicalCount === 0) {
this._increasePool(DEFAULT_PHYSICAL_COUNT);
} else {
this._update();
}
}
},

Expand All @@ -1036,7 +1027,6 @@
props[this.indexAs] = true;
props[this.selectedAs] = true;
props.tabIndex = true;

this._instanceProps = props;
this._userTemplate = Polymer.dom(this).querySelector('template');

Expand Down Expand Up @@ -1137,39 +1127,29 @@
*/
_itemsChanged: function(change) {
if (change.path === 'items') {
// reset items
this._virtualStart = 0;
this._physicalTop = 0;
this._virtualCount = this.items ? this.items.length : 0;
this._collection = this.items ? Polymer.Collection.get(this.items) : null;
this._physicalIndexForKey = {};
this._firstVisibleIndexVal = null;
this._lastVisibleIndexVal = null;

this._physicalCount = this._physicalCount || 0;
this._physicalItems = this._physicalItems || [];
this._physicalSizes = this._physicalSizes || [];
this._physicalStart = 0;
this._resetScrollPosition(0);
this._removeFocusedItem();
// create the initial physical items
if (!this._physicalItems) {
this._physicalCount = Math.max(1, Math.min(DEFAULT_PHYSICAL_COUNT, this._virtualCount));
this._physicalItems = this._createPool(this._physicalCount);
this._physicalSizes = new Array(this._physicalCount);
}

this._physicalStart = 0;
this._debounceTemplate(this._render);

} else if (change.path === 'items.splices') {

this._adjustVirtualIndex(change.value.indexSplices);
this._virtualCount = this.items ? this.items.length : 0;
this._debounceTemplate(this._render);

} else {
// update a single item
this._forwardItemPath(change.path.split('.').slice(1).join('.'), change.value);
return;
}

this._itemsRendered = false;
this._debounceTemplate(this._render);
},

/**
Expand Down Expand Up @@ -1282,7 +1262,7 @@
*/
_updateMetrics: function(itemSet) {
// Make sure we distributed all the physical items
// so we can measure them
// so we can measure them.
Polymer.dom.flush();

var newPhysicalSize = 0;
Expand All @@ -1307,7 +1287,7 @@
this._physicalSize = this._physicalSize + newPhysicalSize - oldPhysicalSize;
}

// update the average if we measured something
// Update the average if it measured something.
if (this._physicalAverageCount !== prevAvgCount) {
this._physicalAverage = Math.round(
((prevPhysicalAvg * prevAvgCount) + newPhysicalSize) /
Expand Down Expand Up @@ -1338,23 +1318,17 @@
var rowOffset = (this._viewportWidth - totalItemWidth) / 2;

this._iterateItems(function(pidx, vidx) {

var modulus = vidx % this._itemsPerRow;
var x = Math.floor((modulus * this._itemWidth) + rowOffset);

this.translate3d(x + 'px', y + 'px', 0, this._physicalItems[pidx]);

if (this._shouldRenderNextRow(vidx)) {
y += this._rowHeight;
}

});
} else {
this._iterateItems(function(pidx, vidx) {

this.translate3d(0, y + 'px', 0, this._physicalItems[pidx]);
y += this._physicalSizes[pidx];

});
}
},
Expand Down Expand Up @@ -1424,7 +1398,7 @@
forceUpdate = forceUpdate || this._scrollPosition >= this._estScrollHeight - this._physicalSize;
forceUpdate = forceUpdate || this.grid && this.$.items.style.height < this._estScrollHeight;

// amortize height adjustment, so it won't trigger repaints very often
// Amortize height adjustment, so it won't trigger large repaints too often.
if (forceUpdate || Math.abs(this._estScrollHeight - this._scrollHeight) >= this._optPhysicalSize) {
this.$.items.style.height = this._estScrollHeight + 'px';
this._scrollHeight = this._estScrollHeight;
Expand Down Expand Up @@ -1453,44 +1427,38 @@
if (typeof idx !== 'number' || idx < 0 || idx > this.items.length - 1) {
return;
}

Polymer.dom.flush();
// Items should have been rendered prior scrolling to an index.
if (!this._itemsRendered) {
if (this._physicalCount === 0) {
return;
}
idx = Math.min(Math.max(idx, 0), this._virtualCount-1);
// update the virtual start only when needed
// Update the virtual start only when needed.
if (!this._isIndexRendered(idx) || idx >= this._maxVirtualStart) {
this._virtualStart = this.grid ? (idx - this._itemsPerRow * 2) : (idx - 1);
}
// manage focus
this._manageFocus();
// assign new models
this._assignModels();
// measure the new sizes
this._updateMetrics();
// estimate new physical offset
// Estimate new physical offset.
this._physicalTop = Math.floor(this._virtualStart / this._itemsPerRow) * this._physicalAverage;

var currentTopItem = this._physicalStart;
var currentVirtualItem = this._virtualStart;
var targetOffsetTop = 0;
var hiddenContentSize = this._hiddenContentSize;
// scroll to the item as much as we can
// scroll to the item as much as we can.
while (currentVirtualItem < idx && targetOffsetTop <= hiddenContentSize) {
targetOffsetTop = targetOffsetTop + this._getPhysicalSizeIncrement(currentTopItem);
currentTopItem = (currentTopItem + 1) % this._physicalCount;
currentVirtualItem++;
}
// update the scroller size
this._updateScrollerSize(true);
// update the position of the items
this._positionItems();
// set the new scroll position
this._resetScrollPosition(this._physicalTop + this._scrollerPaddingTop + targetOffsetTop);
// increase the pool of physical items if needed
this._increasePoolIfNeeded();
// clear cached visible index
// clear cached visible index.
this._firstVisibleIndexVal = null;
this._lastVisibleIndexVal = null;
},
Expand Down Expand Up @@ -1519,7 +1487,7 @@
this.updateViewportBoundaries();
this._render();

if (this._itemsRendered && this._physicalItems && this._isVisible) {
if (this._physicalCount > 0 && this._isVisible) {
this._resetAverage();
this.scrollToIndex(this.firstVisibleIndex);
}
Expand Down

0 comments on commit da016a4

Please sign in to comment.