Tuesday, November 19, 2013

Infinite scroll performance with lots of elements

The problem when using infinite scroll with LOTS of elements is the browser gets bogged down.

This guy eloquently gives a great explanation of a solution.

We had to deal with a similar problem on FoldingText. As the document grew larger, more line elements and associated span elements were created. The browser engine just seemed to choke, and so a better solution needed to be found.
Here's what we did, may or may not be useful for your purposes:
Visualize the entire page as a long document, and the browser viewport as the lens for a specific part of the long document. You really only have to show the part within the lens.

So the first part is to calculate the visible view port. (This depends on how your elements are placed, absolute / fixed / default)

var top = document.scrollTop;
var width = window.innerWidth;
var height = window.innerHeight;
Some more resources to find a more cross-browser based viewport:

Second, you need a data structure to know which elements are visible in that area

We already had a balanced binary search tree in place for text editing, so we extended it to manage line heights too, so this part for us was relatively easy. I don't think you'll need a complex data structure for managing your element heights; a simple array or object might do fine. Just make sure you can query heights and dimensions easily on it. Now, how would you get the height data for all your elements. A very simple (but computationally expensive for large amounts of elements!)
var boundingRect = element.getBoundingClientRect()
I'm talking in terms of pure javascript, but if you're using jQuery $.offset$.position, and methods listed here would be quite helpful.
Again, using a data structure is important only as a cache, but if you want, you could do it on the fly (though as I've stated these operations are expensive). Also, beware of changing css styles and calling these methods. These functions force redraw, so you'll see a performance issue.

Lastly, just replace the elements offscreen with a single, say <div> element with calculated height

  • Now, you have heights for all the elements stored in your Data structure, query all the elements that lie before the visible viewport.
  • Create a <div> with css height set (in pixels) to the sum of the element heights
  • Mark it with a class name so that you know its a filler div
  • Remove all the elements from the dom that this div covers
  • insert this newly created div instead
Repeat for elements that lie after the visible viewport.
Look for scroll and resize events. On each scroll, you will need to go back to your data structure, remove the filler divs, create elements that were previously removed from screen, and accordingly add new filler divs.
:) It's a long, complex method, but for large documents it increased our performance by a large margin.

tl;dr

I'm not sure I explained it properly, but the gist of this method is:
  • Know the vertical dimensions of your elements
  • Know the scrolled view port
  • Represent all off-screen elements with a single div (height equal to the sum of all element heights it covers for)
  • You will need two divs in total at any given time, one for elements above the visible viewport, one for elements below.
  • Keep track of the view port by listening for scroll and resize events. Recreate the divs and visible elements accordingly

Great Linkedin demonstation of the problem and solution.

Mobile devices have less memory and CPU power compared to Desktop computers. If you render a very long list in the HTML, you run the risk of crashing the device. This makes it challenging to build large, interactive HTML5 apps for mobile devices. Native technologies provide UITableViewController to build long, infinite scrolling lists. UITableView contains reusable UITableViewCells which are optimized for memory, performance and responsiveness . For HTML5, we did not have any solution. So we set out to build one!

Technique #1: unloading images

Technique #2: hiding pages

Technique #3: removing pages

Technique #4: avoid scaling and box-shadow

Technique #5: minimizing DOM nodes



http://stackoverflow.com/questions/12613113/performance-with-infinite-scroll-or-a-lot-of-dom-elements
http://engineering.linkedin.com/linkedin-ipad-5-techniques-smooth-infinite-scrolling-html5
http://luis-almeida.github.io/unveil/
http://jsfiddle.net/zp4t9/4/
http://jsfiddle.net/ymyx8/1/

Grid of elements with variable heights and a sorted order

Assumptions
You have a grid of elements each with a fixed width and a variable height. Example. Images that have fixed width and a variable height because you want to preserve the aspect ratio.

The problem you want to align them into a grid but simple floating them leaves large gaps in vertical space between elements.

Great further explanation of the problem, taken from link below.

"To my knowledge, there's no way to fix this problem with pure CSS (that works in all common browsers):
  • Floats don't work.
  • display: inline-block doesn't work.
  • position: relative with position: absolute requires manual pixel tuning. If you're using a server-side language, and you're working with images (or something with predictable height), you can handle the pixel tuning "automatically" with server-side code.
Instead, use jQuery Masonry."

http://stackoverflow.com/questions/5234749/css-floating-divs-at-variable-heights

Solutions

1. This sorts the elements by height.

 $('.arrange').each(function(){
            arrange.elements.push(new Array($(this).height(), $(this).clone()));
         
        });
 for(var i = 0; i < arrange.elements.length; i ++)
        {
          if(arrange.elements[i][0] > current)
          {
              current = arrange.elements[i][0];
              elem = arrange.elements[i][1];
              index = i;
          }
        }

This is pretty slow by the way.
http://jsfiddle.net/hMmLd/1/

2. Use jquery masonry or isotope

http://masonry.desandro.com/
http://isotope.metafizzy.co/

http://jsfiddle.net/RXDL4/406/

3. Absolute positioning

Jquery Waterfall Plugin
http://wlog.cn/waterfall/

4. Solution we settled on after playing with all of the above techniques.

We organize the elements into their respective sections one for each column, this way we can both insert sorted data into them and preserve reasonable vertical spacing.

So for n columns you would have markup like the following.

<section class="mediaItems overview" data-bind="foreach: columns">
<div class="column-div" data-bind="unveil: $root.everyNthMediaItems($index())">
<img data-bind=" attr: { src: url}" />
                </div>
</section>

We precompute the items that will be in each column to make it faster loading on the dom aka (everyNthMediaItems). That way we start with sorted data and then run everyNthMediaItems to put that data into columns thereby preserving the sorting and keeping vertical spacing correct.