Search code examples
performanceknockout.jsviewportknockout-3.0

KnockoutJS Performance: Only Render Elements that are Currently in View (Clean/Destroy Others)


I am developing an application using KnockoutJS which intends to show pages/slides in a long, scrollable list.

Each of these pages has some advanced WYSIWYG capabilities as well as a ton of other tools & data attached.

However, as the number of pages/slides grows, I am noticing huge performance hits.

Therefore, I would like to change to a way that only renders the slides/pages that are in the viewport. Once you scroll past an item, I want KnockoutJS to destroy whatever it rendered before and, instead, render whatever has come into the viewport. Items that are destroyed should be replaced with a placeholder li-tag (until you scroll back to them).

What works so far

So far so good, I have put together a JSFiddle which nicely shows how only the "visible" items have their observable "inView" set to true. Take a look at the demo showing you how the viewmodel responds to scrolling: https://jsfiddle.net/1xxy6q83/

What's missing

The issue, however, is to get the conditional display working. Whenever I try to mix/match foreach and if, it stops working. What I am looking for is that Knockout only renders what's seen in the viewport. As an item moves out of view, Knockout should clean it up, remove any events and instead render a very simple placeholder li-tag which ensures the list doesn't jump about.


Solution

  • You can use template bindings where the template name is based on the value of inView:

    function Page(page) {
      var self = this;
      this.page = ko.observable(page);
      this.inView = ko.observable(false);
      this.templateName = function() {
        return self.inView() ? 'complex' : 'placeholder';
      };
    }
    

    The binding looks like:

        <ul data-bind="foreach: pages">
          <li data-bind="inview: $data">
            <div data-bind="template: templateName()">
    
            </div>
          </li>
        </ul>
    

    And here's an updated fiddle. I put a time delay on the setting of inView so you can see the template-swapping happening as you scroll.