jQuery:滚动时修复表行 - 性能问题

时间:2016-04-21 16:28:04

标签: javascript jquery css performance scroll

我有一张不同类别的表格。每个类别都有一个标题,我希望在滚动时将该标题的位置固定在表格顶部。

简单示例:如果我在桌子上看到汽车,我希望thead说“汽车”。如果我继续滚动并开始看摩托车,我希望它说“摩托车”。

这是一个代码片段,其代码的简化版本代表上面的示例:

//start scrolling
$('tbody').scroll(function() {
  doStuff();
});

function doStuff() {

  $('.title').each(function() {

    //Set vars
    var $this = $(this);
    var childPos = $this.position();
    var parentPos = $('tbody').position();
    var currentTop = childPos.top - parentPos.top;

    //Listener
    if (currentTop <= 0) {
      $this.addClass('active')
      $('thead').find('.active').remove();
      $('.active').last().clone().appendTo($('thead'));
    } else {
      $this.removeClass('active');

    }
  });


}
.table-wrapper {
  height: 180px;
  overflow: hidden;
}
table {
  font: arial;
  text-align: left;
  width: 300px;
}
tbody {
  display: block;
  height: 80px;
  overflow: auto;
  width: 100%;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="table-wrapper">
  <table>
    <thead>
      <tr>
        <th colspan="2">Vehicles</th>
      </tr>
    </thead>

    <tbody>
      <tr>
        <th colspan="2" class="title">Cars</th>
      </tr>
      <tr>
        <td>Opel</td>
        <td>Corsa</td>
      </tr>
      <tr>
        <td>Renault</td>
        <td>Clio</td>
      </tr>
      <tr>
        <td>Citroen</td>
        <td>AX</td>
      </tr>

      <tr>
        <th colspan="2" class="title">Motorbikes</th>
      </tr>
      <tr>
        <td>Honda</td>
        <td>Hornet</td>
      </tr>
      <tr>
        <td>Yamaha</td>
        <td>Virago</td>
      </tr>
      <tr>
        <td>Suzuki</td>
        <td>GSXR</td>
      </tr>

      <tr>
        <th colspan="2" class="title">Planes</th>
      </tr>
      <tr>
        <td>Boeing</td>
        <td>737</td>
      </tr>
      <tr>
        <td>Boeing</td>
        <td>767</td>
      </tr>
      <tr>
        <td>Boeing</td>
        <td>777</td>
      </tr>
    </tbody>
  </table>
</div>

另外,here's a jsfiddle

当以这种方式做事时,每次滚动移动时我都会调用我的整个函数,因此我预计性能会很差。

如何优化我的代码以使用滚动运行?我见过的所有解决方案都使用setTimeout,但我不希望仅在250ms不滚动后运行我的函数,因为用户可能会滚动一段时间而thead不会是对的。

请记住,我的桌子上可以有数千行。谢谢。

1 个答案:

答案 0 :(得分:1)

如果您不想设置超时以延迟.scroll()来电,则您的选项仅限于优化.scroll()事件。

我在这里写了一个替代方法,但我还没有在大表上测试它:https://jsfiddle.net/sduknuzw/69/

您会注意到我将所有昂贵的计算移到了循环之外并在初始加载时处理它们。这使我只需要.scroll()事件并只处理一次昂贵的计算。

//initialize headers
var headers = [];
$('.title').each(function(index) {
  var header = {
    thead: $(this),
    top: $(this).parent().index() * $(this).outerHeight(),
    bottom: 0
  };
  headers.push(header);
});

//calculate header container heights
for (var i = 0; i < headers.length; ++i) {
    headers[i].bottom = (i+1 < headers.length) ? headers[i+1].top : 100000;
}

在上面的标题代码中,我构建了一个标题对象,其中top / bottom是虚容器的位置值。稍后将使用此容器查找滚动条所在的部分。

我会认为代码的这一部分有点hacky。我不喜欢使用CSS值进行计算,因为它们在浏览器中并不总是相同。此外,此代码不适用具有不同高度的行/标题,因此如果不根据您的需要修改它,它可能无法工作。但它足以证明我正在努力实现的目标。

为了更好地理解数学和我想要完成的事情,想象一下这个简化的场景:

<style>
    table tr td {
        height:20px
    }
</style

<table>
    <tbody>
      <tr><th colspan="2" class="title">Cars</th></tr> //Row index 0
      <tr><td>1</td></tr>
      <tr><th colspan="2" class="title">Motorbikes</th></tr> //Row index 2
      <tr><td>2</td></tr>
      <tr><td>3</td></tr>
      <tr><th colspan="2" class="title">Planes</th></tr> //Row index 5
      <tr><td>4</td></tr>
  </tbody>
</table>

'top'值是行索引乘以每行的高度。 “底部”值基本上只是下一个标题的“顶部”值。

对于我们的小例子,标题容器值将是:

Car { 
    top: 0, 
    bottom: 40 
}
Motorbikes { 
    top: 40,    // Row Index * Row Height
    bottom: 100 // Next Headers Top
}
Planes {
    top: 100, 
    bottom: 140
}

从视觉上看,它看起来像这样:

-------------------  top: 0
|  th   |   Cars   |
|  td   |    1     |
-------------------  top/bottom: 40
|  th   |  Motors  |
|  td   |    2     |
|  td   |    3     |
-------------------  top/bottom: 100
|  th   |  Planes  |
|  td   |    4     |
-------------------  bottom: 140

代码的最后一部分是scroll循环本身。由于我上面做的额外工作,我们不必遍历每个滚动上的所有.title对象。这个解决方案将开销减少到几个相对便宜的数字比较中。

var headerIndex = 0;
function doStuff() {
    var scroll = $('tbody').scrollTop();
    if (scroll >= headers[headerIndex].top && scroll < headers[headerIndex].bottom) {
        return true;
    }
    else {
        //get new headerindex and update header
        headers[headerIndex].thead.removeClass('active');
        headerIndex = (scroll < headers[headerIndex].top) ? headerIndex-1 : headerIndex+1;
        headers[headerIndex].thead.addClass('active');

        $('thead').find('.active').remove();
        $('.active').last().clone().appendTo($('thead'));
    }
}

这比你的代码更多,并且有明确的改进空间,但我会尝试这样的事情,看看你是否看到任何明显的性能提升。