_redraw()

in js/index.js [203:339]


  _redraw() {
    let invalidateItems = this._processInvalidation();
    const { items, itemTemplate } = this.options;
    const { viewport, itemHeights, $topFiller, $bottomFiller, virtualized } = this;
    let { indexFirst, indexLast, anchor } = this._state;

    if (!invalidateItems && items.length === 0) {
      return;
    }

    /**
     * The event indicates the list will start redraw.
     * @event ListView#willRedraw
     */
    this.trigger('willRedraw');

    whileTrue(() => {
      let isCompleted = true;

      const metricsViewport = viewport.getMetrics();
      const visibleTop = metricsViewport.outer.top;
      const visibleBot = metricsViewport.outer.bottom;
      const listTopCur = this.$topFiller.get(0).getBoundingClientRect().top;
      const scrollRatio = metricsViewport.scroll.ratioY;

      let renderTop = false;
      let renderBot = false;

      whileTrue(() => {
        const listTop = anchor ? anchor.top - itemHeights.read(anchor.index) : listTopCur;
        const targetFirst = virtualized ? itemHeights.lowerBound(visibleTop - listTop) : 0;
        const targetLast = virtualized ? Math.min(itemHeights.upperBound(visibleBot - listTop) + 1, items.length) : items.length;
        const renderFirst = Math.max(targetFirst - 10, 0);
        const renderLast = Math.min(targetLast + 10, items.length);

        let renderMore = false;

        // Clean up
        if (targetFirst >= indexLast || targetLast <= indexFirst || invalidateItems) {
          $topFiller.nextUntil($bottomFiller).remove();
          indexFirst = indexLast = targetFirst;
          if (targetFirst !== targetLast && items.length > 0) {
            renderMore = true;
          }
          if (!anchor) {
            const index = Math.round(targetFirst * (1 - scrollRatio) + targetLast * scrollRatio);
            const top = listTopCur + itemHeights.read(index);
            anchor = { index, top };
          }
          invalidateItems = false;
        } else if (!anchor) {
          const index = Math.round(indexFirst * (1 - scrollRatio) + indexLast * scrollRatio);
          const top = listTopCur + itemHeights.read(index);
          anchor = { index, top };
        }

        // Render top
        if (targetFirst < indexFirst) {
          $topFiller.after(items.slice(renderFirst, indexFirst).map(itemTemplate));
          $topFiller.nextUntil($bottomFiller).slice(0, indexFirst - renderFirst).each((offset, el) => {
            itemHeights.writeSingle(renderFirst + offset, el.getBoundingClientRect().height);
          });
          indexFirst = renderFirst;
          renderMore = renderTop = true;
        } else if (renderBot && !renderTop && renderFirst > indexFirst) {
          const removal = [];
          $topFiller.nextUntil($bottomFiller).slice(0, renderFirst - indexFirst).each((offset, el) => removal.push(el));
          $(removal).remove();
          indexFirst = renderFirst;
          renderMore = true;
        }

        // Render bottom
        if (targetLast > indexLast) {
          $bottomFiller.before(items.slice(indexLast, renderLast).map(itemTemplate));
          $topFiller.nextUntil($bottomFiller).slice(indexLast - indexFirst).each((offset, el) => {
            itemHeights.writeSingle(indexLast + offset, el.getBoundingClientRect().height);
          });
          indexLast = renderLast;
          renderMore = renderBot = true;
        } else if (renderTop && !renderBot && renderLast < indexLast) {
          const removal = [];
          $topFiller.nextUntil($bottomFiller).slice(renderLast - indexFirst).each((offset, el) => removal.push(el));
          $(removal).remove();
          indexLast = renderLast;
          renderMore = true;
        }

        return renderMore;
      });

      // Update the padding
      if (indexFirst !== this.indexFirst || indexLast !== this.indexLast) {
        this._applyPaddings({
          paddingTop: itemHeights.read(indexFirst),
          paddingBottom: itemHeights.read(items.length) - itemHeights.read(indexLast),
        });
      }

      // Adjust the scroll if it's changed significantly
      const listTop = anchor.top - itemHeights.read(anchor.index);
      const innerTop = listTop - (listTopCur - metricsViewport.inner.top);
      const scrollTop = Math.round(visibleTop - innerTop);
      let anchorNew = null;

      // Do a second scroll for a middle anchor after the item is rendered
      if (anchor.isMiddle) {
        const index = anchor.index;
        const itemTop = listTopCur + this.itemHeights.read(index);
        const itemBot = listTopCur + this.itemHeights.read(index + 1);

        anchorNew = {
          index,
          top: (visibleTop + visibleBot + itemTop - itemBot) / 2,
        };
        isCompleted = false;
      }

      if (Math.abs(scrollTop - viewport.getMetrics().scroll.y) >= 1) {
        this.viewport.scrollTo({ y: scrollTop });
        isCompleted = false;
      }

      anchor = anchorNew;

      return !isCompleted;
    });

    // Write back the render state
    _.extend(this._state, { indexFirst, indexLast, anchor: null });

    /**
     * The event indicates the list view have completed redraw.
     * @event ListView#didRedraw
     */
    this.trigger('didRedraw');
  }