Jump to content
Sign in to follow this  
lynkfs

scrolling big numbers

Recommended Posts

Got challenged the other day by trying to scroll large numbers of rows in a listbox.

Browsers are very good at scrolling, but when row numbers become large and the scroll context becomes complex, scroll behaviour deteriorates.

A simple un-optimised scrolling div is, rule of thumb, able to scroll comfortably up to a couple of hundred rows. Depending on browser and complexity.

In the native framework I tweaked that a bit by setting all rows outside the visible viewport to 'display:none'. That extends comfortable scrolling somewhat, say to numbers in the low thousands. 

A better way is to completely separate display from content. Meaning that the content needs to be held in some kind of memory structure, not in visual components, and that scrolling is redefined to re-using a small set of visual rows confined to the scroll-window.

A structure to make that happen would be a <div> like component (TW3Panel) on a form with NativeScrolling set to true. Set up 2 child-panels on this component, where the first child-panel has dimensions equal to the maximum scrollable area, and a second childpanel dimensioned equal to this parent. The first child-panel is the scroller, which will always be completely empty. The second child-panel contains the visible rows, which will be refreshed on scroll-events. To keep this panel always in view, set the position attribute to '-webkit-sticky'.

This demo shows that Chrome can readily handle 500,000 rows, even on mobile. FF a bit less.

The good thing is that the number of rows doesn't really matter, scrolling behaviour stays the same regardless.

Would be interesting to see if performance gets even better by pushing scrolling to the GPU, pretty sure Chrome does that by default.

Infinite scrolling will be easy to implement using this structure too.

Code

procedure TForm1.W3Button1Click(Sender: TObject);
begin
//data
  var column1 := TVariant.CreateArray;
  for var i := 0 to 500000 do begin
    column1.push(inttostr(i));
  end;

  var column2 := TVariant.CreateArray;
  for var i := 1 to 500001 do begin
    column2.push(inttostr(i));
  end;
  //column2.sort();                                       //sorting works too

//scrolling setup
  var rowHeight := 30;

  W3Panel.NativeScrolling := true;                        //Parent panel (in visual designer : width 408, height 266)

  var Panel1 : TW3Panel := TW3Panel.Create(W3Panel);      //scroller (always empty!)
  Panel1.SetBounds (0,0,380,column1.length*rowHeight);    //but with large height
  Panel1.BorderRadius := 0;

//set up viewport
  var Panel2 : TW3Panel := TW3Panel.Create(W3Panel);      //viewport, only the visual rows
  Panel2.SetBounds(0,0,380,262);                          //dimensions same as grid-parent
  Panel2.BorderRadius := 0;

  Panel2.handle.style.position := '-webkit-sticky';
  Panel2.handle.style.position := '-moz-sticky';
  Panel2.handle.style.position := '-ms-sticky';
  Panel2.handle.style.position := '-o-sticky';
  Panel2.handle.style.position := 'sticky';
  Panel2.handle.style.top := '0px';

//set up columns
  Var CPanel1 : TW3Panel := TW3Panel.Create(Panel2);     //column 1
  CPanel1.SetBounds(-2,-2,200,264);
  CPanel1.BorderRadius := 0;

  Var CPanel2 : TW3Panel := TW3Panel.Create(Panel2);     //column 2
  CPanel2.SetBounds(200,-2,200,264);
  CPanel2.BorderRadius := 0;

  //initial fill viewport prior to first onscroll event
  for var j := 0 to 8 do begin                           //only first couple of rows
    var x : TW3Panel := TW3Panel.Create(CPanel1);        //column 1
    x.SetBounds(-2,j*rowHeight-2,200,rowHeight);
    x.BorderRadius := 0;
    x.innerHTML := column1[j];
    x.onclick := procedure(sender:TObject) begin showmessage((sender as TW3Panel).innerHTML); end;  
  //
    var y : TW3Panel := TW3Panel.Create(CPanel2);        //column 2
    y.SetBounds(-2,j*rowHeight-2,190,rowHeight);
    y.BorderRadius := 0;
    y.innerHTML := column2[j];
    y.onclick := procedure(sender:TObject) begin showmessage((sender as TW3Panel).innerHTML); end;
  end;

//fill viewport while scrolling
  var c1 := CPanel1.handle.children;
  var c2 := CPanel2.handle.children;

  W3Panel.onscroll := procedure(sender:tobject)
  begin
    var d : integer := trunc(W3Panel.handle.scrollTop/rowHeight);
    for var k := 0 to 8 do begin
      c1[k].innerHTML := column1[d+k];
      c2[k].innerHTML := column2[d+k];
    end;

    //fall back : ide chrome browser and iOS don't handle 'sticky' very well,
    //in that case set viewport position manually
    if Panel2.handle.style.position <> 'sticky' then
      if not w3_getIsSafari then
        Panel2.top := W3Panel.handle.scrollTop;
  end;

end;

 

infinity scroll :

add to end of W3Panel.onscroll procedure

    //infinity scroll :
    if W3Panel.handle.scrollHeight - W3Panel.handle.scrollTop = W3Panel.handle.clientHeight then
      W3Panel.handle.scrollTop := 0;

 

 

 

 

 

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
Sign in to follow this  

×