Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Slow performance with fixed header and pinned column #3719

Open
lordgreg opened this issue Jun 8, 2015 · 16 comments
Open

Slow performance with fixed header and pinned column #3719

lordgreg opened this issue Jun 8, 2015 · 16 comments

Comments

@lordgreg
Copy link

lordgreg commented Jun 8, 2015

Hi,

I'm using latest version of ng-grid: 3.0.0-RC21 and really small dataset (JSON length of approximately 100 rows) with 20 columns, but did this for showing table (not all fields of array visible):

  this.gridOptions = {
    enableSorting: true,
    excessRows: 100,
    columnDefs: [
      { name:'Number', field: 'atomicNumber', width: 80 },
      { name:'Symbol', field: 'elementSymbol', width: 80, pinnedLeft:true },
      { name:'Designation', field: 'designation.EN', width: 140 },
      { name:'Classification', field: 'classification.EN', width: 140 },
      { name: 'Period', field: 'period', width: 140  },
      { name: 'Group', field: 'group', width: 140  },
      { name: 'anisotopicElement', field: 'anisotopicElement', width: 140  },
      { name: 'artificialElement', field: 'artificialElement', width: 140  },
      { name: 'radioactiveElement', field: 'radioactiveElement', width: 140  },
      { name: 'aggregateState', field: 'aggregateState.EN', width: 140  }
    ],
    data: this.elements
  };

HTML looks like this:

    <div ui-grid="main.gridOptions" class="grid" ui-grid-pinning></div>

Now, when I run this example on iOS simulator or iPad4, the scrolling seems very very unresponsive. When scrolling, the pinned column just doesn't refresh so fast as it should. The fields stay where they are until scrolling slows down a bit, then the pinned column also refreshes the rows to be on the position of non-fixed fields. The excessRows part above is there because we need to have snappy and fast performing ui. At the moment, it looks very bad and totally laggy, not even close to what the example with pinned columns show. In Chrome it looks great, but, as I said, iOS simulator or iPad, it is just laggy as hell.

Why is that so and could I get snappier scrolling with some code optimization?

@PaulL1
Copy link
Contributor

PaulL1 commented Jun 8, 2015

I think a Plummer would help here.

Sent from my iPhone

On 8 Jun 2015, at 6:58 pm, Gregor notifications@github.com wrote:

Hi,

I'm using latest version of ng-grid: 3.0.0-RC21 and really small dataset (JSON length of approximately 100 rows) with 20 columns, but did this for showing table (not all fields of array visible):

this.gridOptions = {
enableSorting: true,
excessRows: 100,
columnDefs: [
{ name:'Number', field: 'atomicNumber', width: 80 },
{ name:'Symbol', field: 'elementSymbol', width: 80, pinnedLeft:true },
{ name:'Designation', field: 'designation.EN', width: 140 },
{ name:'Classification', field: 'classification.EN', width: 140 },
{ name: 'Period', field: 'period', width: 140 },
{ name: 'Group', field: 'group', width: 140 },
{ name: 'anisotopicElement', field: 'anisotopicElement', width: 140 },
{ name: 'artificialElement', field: 'artificialElement', width: 140 },
{ name: 'radioactiveElement', field: 'radioactiveElement', width: 140 },
{ name: 'aggregateState', field: 'aggregateState.EN', width: 140 }
],
data: this.elements
};
HTML looks like this:

<div ui-grid="main.gridOptions" class="grid" ui-grid-pinning></div>

Now, when I run this example on iOS simulator or iPad4, the scrolling seems very very unresponsive. When scrolling, the pinned column just doesn't refresh so fast as it should. The fields stay where they are until scrolling slows down a bit, then the pinned column also refreshes the rows to be on the position of non-fixed fields. The excessRows part above is there because we need to have snappy and fast performing ui. At the moment, it looks very bad and totally laggy, not even close to what the example with pinned columns show. In Chrome it looks great, but, as I said, iOS simulator or iPad, it is just laggy as hell.

Why is that so and could I get snappier scrolling with some code optimization?


Reply to this email directly or view it on GitHub.

@lordgreg
Copy link
Author

lordgreg commented Jun 8, 2015

@PaulL1 - Plummer?

@PaulL1
Copy link
Contributor

PaulL1 commented Jun 8, 2015

Stupid iPhone. Plunker.

@JLLeitschuh
Copy link
Contributor

+1 For getting a plumber to help you out on this one!

@mattrothenberg
Copy link

I'm experiencing (what I think is) the same issue. GIF illustrating the problem below. Touch scrolling works beautifully in Chrome & Mobile Safari -- when I deploy to iOS using Cordova, funny things happen.

http://gfycat.com/KindDrearyBuckeyebutterfly

I'll try to get my code into a Plunkr ASAP.

@lordgreg
Copy link
Author

Thank you for saving me @mattrothenberg. This was exactly the problem. I haven't solved it using ng-grid but creating 3 different ionic-scrolls elements (1 header, 1 column1, 1 content). when using on-scroll, i've read the X and Y position then used .css(transform...) to move the other two also. That way, I've got the fastest grid, it took me about a day to finalize it, but its finally done.

@mattrothenberg
Copy link

Haha, that's exactly what I was doing prior to using ng-grid! If only I didn't have to support sorting of columns...
And to think I had the unique implementation there :-)

@lordgreg
Copy link
Author

I am using the sorting also. Where's the problem Matt? Just use ng-click="setSort('byID')" for example, then in do your things, and in ng-repeat, just use sortby 👍

@mattrothenberg
Copy link

I'd love to see your implementation in a Plunker or Codepen, @lordgreg!

In the meantime, I've used the following SCSS Mixin on the ui-grid-viewport class to make scrolling palatable.

@mixin overflow-scroll {
    overflow: auto;
    -webkit-overflow-scrolling: touch;
    -moz-overflow-scrolling: touch;
    -o-overflow-scrolling: touch;
    -ms-overflow-scrolling: touch;
    overflow-scrolling: touch;
}

@lordgreg
Copy link
Author

I will make a pseudocode tomorrow for you to see :) But, take notice, CSS styling took a while. Also, for rows and cols, I've used ionicframeworks rows and cols grid system. Will post more tomorrow. Unitl then, good luck :ž

@ghost
Copy link

ghost commented Oct 27, 2015

Was this issue solved?

@lordgreg
Copy link
Author

Hi. I've never bothered to check if the development of ui-grid got any further. Sadly, I didn't even create a Plunker as @mattrothenberg asked. I'll try to show you what we've did:

template

<ion-content>

  <!-- header -->
  <div class="row row-header">
    <div class="col col-symbol table-symbol">
        Symbol
    </div>
    <div class="col col-header col-center">
      <ion-scroll on-scroll="propertyRanking.scrollElements(event)" direction="x" id="scroll-header" delegate-handle="scroll-header" has-bouncing="false" scrollbar-x="false">
        <div class="row table-header">
          <div class="col" ng-click="propertyRanking.setSortType('atomicNumber')">
            {{'NUM'|translate}}
            <i ng-class="{'sort-asc': propertyRanking.sortType === 'atomicNumber' &&
            !propertyRanking.sortReverse, 'sort-desc': propertyRanking.sortType === 'atomicNumber' &&
            propertyRanking.sortReverse}">
            </i>
          </div>
          <div class="col" ng-click="propertyRanking.setSortType('designation.EN')">
            {{'ELEMENT_NAME'|translate}}
            <i ng-class="{'sort-asc': propertyRanking.sortType === 'designation.EN' &&
            !propertyRanking.sortReverse, 'sort-desc': propertyRanking.sortType === 'designation.EN' &&
            propertyRanking.sortReverse}">
            </i>
          </div>
          <div class="col" ng-click="propertyRanking.setSortType('relativeAtomicMass')">
            {{'RELATIVE_ATOMIC_MASS'|translate}}
            <i ng-class="{'sort-asc': propertyRanking.sortType === 'relativeAtomicMass' &&
            !propertyRanking.sortReverse, 'sort-desc': propertyRanking.sortType === 'relativeAtomicMass' &&
            propertyRanking.sortReverse}">
            </i>
          </div>
          <div class="col" ng-click="propertyRanking.setSortType('density')">
            {{'DENSITY'|translate}}<i ng-class="{'sort-asc': propertyRanking.sortType === 'density' &&
            !propertyRanking.sortReverse, 'sort-desc': propertyRanking.sortType === 'density' &&
            propertyRanking.sortReverse}">
            </i>
          </div>
        </div>
      </ion-scroll>
      </div>
  </div>

  <!-- content -->
  <div class="row row-content">
    <div class="col col-column">
      <ion-scroll on-scroll="propertyRanking.scrollElements(event)" direction="y" id="scroll-column" delegate-handle="scroll-column" has-bouncing="false" scrollbar-x="false" scrollbar-y="false">
        <div class="table-column">
          <div class="row" ng-repeat="single in propertyRanking.data.propertyRanking | orderBy : propertyRanking.sortType : propertyRanking.sortReverse track by single.atomicNumber">
            <div class="col">
              {{single.elementSymbol}}
            </div>
          </div>
        </div>
      </ion-scroll>
    </div>
    <div class="col col-content">
      <ion-scroll on-scroll="propertyRanking.scrollElements(event)" direction="xy" id="scroll-content" delegate-handle="scroll-content" has-bouncing="false">
        <div class="table-content">
          <div class="row" ng-repeat="single in propertyRanking.data.propertyRanking | orderBy : propertyRanking.sortType : propertyRanking.sortReverse track by single.atomicNumber">
              <div class="col">{{single.atomicNumber}}</div>
              <div class="col">{{single.designation.EN}}</div>
              <div class="col">{{single.relativeAtomicMass}}</div>
          </div>
        </div>
      </ion-scroll>
    </div>
  </div>
  <button class="button button-positive icon ion-navicon menu-button"
    ng-if="!propertyRanking.menu.open"
    ng-click="propertyRanking.toggleMenu($event)"></button>
</ion-content>
<!-- </ion-view> -->

To explain the code above a-bit- We have splitted the view in 3 different parts- header, content and first column (which should be static). For each of those, we've used ion-scroll element. each <ion-scroll> has the also on-scroll event which calls our scrollElements method which 1st parameter bein event.

controller

  var contentQ = null;
  var headerQ = null;
  var columnQ = null;

  /*globals ionic*/
  var transformCssString = (ionic.Platform.isWebView() ? '-webkit-transform' : 'transform');

  // set height of table-content and table-column
  $scope.$on('$stateChangeSuccess', function () {
    if ($stateParams.viewState === 'propertyRanking') {
      var headerHeight = angular.element(document.querySelector('.table-header'))[0].offsetHeight;
      var contentHeight = $window.innerHeight - headerHeight;

      angular.element(document.querySelector('.table-content')).css({height: contentHeight + 'px'});
      angular.element(document.querySelector('.table-column')).css({height: contentHeight + 'px'});

      contentQ = angular.element(document.querySelector('#scroll-content .scroll'));
      headerQ = angular.element(document.querySelector('#scroll-header .scroll'));
      columnQ = angular.element(document.querySelector('#scroll-column .scroll'));

      $timeout(function () {
        self.closeMenu();
      }, 1000);
    }
  });

  this.scrollElements = function (event) {
    var id = event.target.id;

    // before scrolling our scrollviews, we have to also show&hide our ionic header
    // var ionicHeader = angular.element(document.querySelector('.scroll-ionicheader'));
    var scrollPosition = $ionicScrollDelegate.$getByHandle('scroll-content').getScrollPosition();
    var startHeight = 44;
    var startPadding = 5;

    // make ionic header smaller, text opacty fadeout etc
    if (scrollPosition.top > 0) {
      var newHeight = Math.max(0, startHeight - scrollPosition.top);
      var newPadding = Math.round(newHeight / 10);

      this.transformIonicHeader({
        height: newHeight,
        padding: newPadding,
        opacity: (newHeight / startHeight)
      });

    }
    // make ionic header as it was.
    else {
      this.transformIonicHeader({
        height: startHeight,
        padding: startPadding,
        opacity: 1.0
      });

    }

    switch (id) {
      case 'scroll-header':

        // when scrolling in header, scroll only content, not column
        contentQ.css(transformCssString, headerQ.css(transformCssString));
        // columnQ.css('transform', headerQ.css('transform'));

        break;
      case 'scroll-content':

        var transform2d = '';
        // when transforming column, move it ONLY BY Y
        transform2d = contentQ.css(transformCssString).match(/(-)?([0-9]+)(\.[0-9]+)?px/gi)[1];
        transform2d = 'translateY(' + transform2d + ')';
        columnQ.css(transformCssString, transform2d);

        // when transforming header, move it ONLY BY X
        transform2d = contentQ.css(transformCssString).match(/(-)?([0-9]+)(\.[0-9]+)?px/gi)[0];
        transform2d = 'translateX(' + transform2d + ')';
        headerQ.css(transformCssString, transform2d);

        break;

      case 'scroll-column':

        // when scrolling column, scroll only content, not header
        // headerQ.css(transformCssString, columnQ.css(transformCssString));
        contentQ.css(transformCssString, columnQ.css(transformCssString));

        break;
      default:
        break;
    }
  };

To better understand the issue we were having: When scrolling one ion-scroll and calling $ionicScroll with scrollTo(left, top, [shouldAnimate]) method, you will call the scrolling of the event itself again which will show as an endless loop of calling the same function again and again. That's why we've initialized 3 queryselectors (one for each ion-scroll element) and transformed only X or Y CSS part of that element.

So for example:

  • holding and moving HEADER will transform COLUMN over Y and CONTENT over X.
  • holding and moving COLUMN will transform HEADER over X and CONTENT over Y.
  • holding and moving CONTENT (in any direction) will transform HEADER over X and COLUMN over Y.

Using that kind of transforming, we're actually updating only CSS of a specific element of the DOM. With this implementation, we've got the best results regarding performance. The implementation was without a lag or hiccups. Keep in mind header columns equals content columns.

After we were done with the implementation, it was the time to make everything nice using SCSS

.propertyranking {
  z-index: 10;

  .col, .row {
    padding: 0px;
  }
  .row-header {
    background: $stable;
    margin-bottom: 6px;
  }
  .row-content {
    margin-top: -6px;
  }
  .col-content {
    margin-top: 1px;
    border-bottom: 1px solid $dark;
  }
  .col-symbol, .col-column {
    flex: 0 0 70px;
    display: flex;
    justify-content: center;
  }
  .table-header, .table-content {
    width: 1080px;
  }
  // height of content = window.innerHeight - header-height!
  // 375 - 50
  // this value is getting recalculated in controller (beforeEnter)
  .table-content, .table-column {
    height: 325px;
  }
  .table-header .col, .table-content .col {
    display: flex;
  }
  .table-header .col i.icon {
    padding-left: 5px;
  }
  .table-header .col .sort-asc {
    width: 0;
    height: 0;
    border-style: solid;
    border-width: 5px 5px 0 5px;
    border-color: #007bff transparent transparent transparent;
    position: absolute;
    top: 0;
    left: 50%;
  }
  .table-header .col .sort-desc {
    width: 0;
    height: 0;
    border-style: solid;
    border-width: 0 5px 5px 5px;
    border-color: transparent transparent #007bff transparent;
    position: absolute;
    bottom: 0;
    left: 50%;
  }
  .col {
    align-items: center;
  }
  .col-symbol, .col-header, .table-header .col {
    height: 50px;
    line-height: 16px;
  }
  .col-column {
    background: $stable;
  }
  .table-header .col {
    padding-left: 10px;
    position: relative;
    word-break: break-word;
  }
  .table-symbol, .table-header, .col-column {
    border-right: 1px solid $dark;
    border-bottom: 1px solid $dark;
  }
  .table-column {
    margin-top: -1px;
  }
  .table-column .col, .table-content .col {
    line-height: 30px;
    padding: 10px;
  }
  // because our content column has also +1px in height because of border-bottom,
  // we have to set line-height of column div line-height to +1 px
  .table-column .col {
    line-height: 31px;
  }
  .table-content .col {
    border-right: 1px solid $stable;
    border-bottom: 1px solid $stable;
  }

  // header & content column widths
  .table-header .col:nth-child(1), .table-content .col:nth-child(1) {
    flex: 0 0 50px;
    justify-content: center;
  }
  .table-header .col:nth-child(2), .table-content .col:nth-child(2) {
    flex: 0 0 120px;
  }
  .table-header .col:nth-child(3), .table-content .col:nth-child(3) {
    flex: 0 0 110px;
  }
  .table-header .col:nth-child(4), .table-content .col:nth-child(4),
  .table-header .col:nth-child(5), .table-content .col:nth-child(5),
  .table-header .col:nth-child(6), .table-content .col:nth-child(6) {
    flex: 0 0 85px;
    justify-content: center;
  }
  .table-header .col:nth-child(7), .table-content .col:nth-child(7) {
    flex: 0 0 100px;
    justify-content: center;
  }
  .table-header .col:nth-child(8), .table-content .col:nth-child(8) {
    flex: 0 0 120px;
  }
  .table-header .col:nth-child(9), .table-content .col:nth-child(9),
  .table-header .col:nth-child(10), .table-content .col:nth-child(10)
  {
    flex: 0 0 90px;
    justify-content: center;
    padding-left: 0;
  }
  .table-header .col:nth-child(11), .table-content .col:nth-child(11) {
    flex: 0 0 120px;
    justify-content: center;
    padding-left: 0;
  }

  .menu-button {
    position: absolute;
    bottom: 20px;
    left: 20px;
    border-radius: 50%;
    min-width: 40px;
    min-height: 40px;
    height: 40px;
    width: 40px;
    z-index: 10;
    &:before {
      line-height: 38px;
    }
    &.ng-enter {
      transition: transform 200ms ease 200ms;
      transform: translate3d(0, 100px, 0);
    }
    &.ng-enter.ng-enter-active {
      transform: translate3d(0, 0, 0);
    }
    &.ng-leave {
      transition: transform 200ms ease;
      transform: translate3d(0, 0, 0);
    }
    &.ng-leave.ng-leave-active {
      transform: translate3d(0, 100px, 0);
    }
  }

}

I hope the code snippets are at least a bit helpful for you to better understand what you have to do.

tl;dr

  • 3 ion-scrolls
  • on-scroll on every of them
  • call method
  • method edits CSS tranformX or/and transformY properties of other 2 elements

@SebastianSchirmer
Copy link

@lordgreg I've just come across this issue thread and your code snippets. In one of our apps we are having a "vote" table which looks like this: https://dl.dropboxusercontent.com/u/22622235/vote_table.png

Currently, header row and most left column are not fixed, users can scroll in all directions and zoom in on mobile devices. We are using an ionic ion-scroll element like this:

<ion-scroll class="has-header has-subheader"
                delegate-handle="voteScroller"
                direction="xy"
                zooming="true"
                min-zoom="0.3"
                max-zoom="1.5"
                overflow-scroll="false">

Now we want to make the header row and most left column fixed when a user scrolls and are thinking of trying out ui-grid for that. Your solution using 3 ion-scroll elements however might be more appropriate though.

Therefore I'd like to ask if you are still using this solution and if it has evolved in some way since end of 2015.

Thanks very much for your reply and kind regards,
Sebastian

@lordgreg
Copy link
Author

Hi @SebastianSchirmer,

The planned solution still works and it is still the fastest one, since you are always updating only translateY css property of the element and not the whole html element. There have been no changes or updates.

If customer decides to update the whole Project to Ionic2, then, we'll have to update the whole project, however the utilisation of what you see in the code above will stay the same.

I wish you good luck with the implementation, it will work as it should 👍

@SebastianSchirmer
Copy link

@lordgreg Lovely, we'll try it out! Thanks very much for your super-fast response!

@lordgreg
Copy link
Author

:) You're welcome and thank you.

@mportuga mportuga self-assigned this Feb 20, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

6 participants