如何使用defineSetter在两个对象中正确设置scrollLeft?

时间:2016-07-14 21:13:22

标签: javascript performance scroll

在我的页面中,我有两个相同宽度的表,它们都进行水平滚动。我需要在每个表滚动时将两个表设置为相同的位置。

实际上,我的代码是:

var scrollA = $('#scrollA'),
    scrollB = $('#scrollB');

scrollA.on('scroll', function() {
    scrollB[0].scrollLeft = scrollA[0].scrollLeft;
});

有效。问题是,在某些情况下,加载的数据足以使浏览器和滚动事件变慢。

然后,我正在尝试改善那些情况下的用户体验。

我在此片段中尝试使用__defineSetter__的{​​{1}}函数:

Object

但是,正如您可以看到运行代码段一样,它会将var elementA = { scrollLeft: 30 }, elementB = { scrollLeft: 30 }; elementA.__defineSetter__('scrollLeft', val => elementB.scrollLeft = val); elementA.scrollLeft += 5; console.log(elementA.scrollLeft + ' == ' + elementB.scrollLeft);设置为undefined,将elementA.scrollLeft设置为NaN

我想在这里给你一些指导,谢谢你的时间。

3 个答案:

答案 0 :(得分:2)

您对__defineSetter__ 的调用会将 scrollLeft 属性替换为 scrollLeft ,但这意味着您无法再访问原始属性。致电__defineSetter__后,任何对该属性的阅读都会产生undefined

console.log(scrollA[0].scrollLeft); // some number, like 10
scrollA[0].__defineSetter__('scrollLeft', val => scrollB[0].scrollLeft = val)
console.log(scrollA[0].scrollLeft); // undefined

此外,浏览器不会像在JavaScript中那样通过赋值更改 scrollLeft 值,因此在滚动事件期间不会调用setter函数。您可以通过在setter函数中放置console.log来验证这一点:滚动时不显示任何内容:

scrollA[0].__defineSetter__('scrollLeft', val => console.log(val))

由于上述两个原因,您无法使用__defineSetter__捕获滚动更改。您需要像先前一样收听scroll事件。

如果异步执行同步,可能会获得更好的结果,并在发生许多滚动事件时跳过某些更新:

var scrollA = $('#scrollA'),
    scrollB = $('#scrollB'),
    tmr = -1;

scrollA.on('scroll', function() {
    clearTimeout(tmr);
    tmr = setTimeout(_ => scrollB[0].scrollLeft = scrollA[0].scrollLeft, 0);
});

现在,当浏览器立即生成多个滚动事件时,第二个将预先清空您scrollB[0].scrollLeft的预定更新:clearTimeout将阻止更新发生,下一个是预定而来。

你可以使用setTimeout的延迟参数来找到合适的平衡点:

增加延迟会阻止事件堆叠,因此 scrollB 将以更具响应性的方式进行同步,但对 scrollB的 的更新将会更少scrollLeft 属性,可以使它滚动得不那么流畅。

答案 1 :(得分:2)

滚动事件可以以高速率发射,因此您可以通过限制滚动事件处理程序来获得更顺畅的滚动,正如trincot在他的回答中所说的那样。您可以使用setTimeout限制功能。但是,当在屏幕上移动内容或处理其他可见效果时,我更喜欢requestAnimationFrame

requestAnimationFrame在下次重绘之前运行回调,因此浏览器可以进行一些优化。

此代码可让您更接近解决方案,它基于this example from MDN

var scrollA = $('#scrollA'),
    scrollB = $('#scrollB'),
    running = false;

scrollA.on('scroll', function() {
  // if we already requested an animation frame, do nothing
  if (!running) {
    window.requestAnimationFrame(function() {
      // synchronize table B and table A
      scrollB[0].scrollLeft = scrollA[0].scrollLeft
      // we're done here and can request a new frame
      running = false;
    });
  }

  running = true;
});

您可以使用传递给回调的时间戳来进一步去抖动同步,但如果您放置太多延迟,则可能会在第二个表格中以某种缓慢的滚动结束。

答案 2 :(得分:0)

不要这样做。把它做成一张桌子。把事情简单化。见例。

<!DOCTYPE html>
<html>
<head>
<style>
table {
    font-family: arial, sans-serif;
    border-collapse: collapse;
    width: 2000px;
}

td, th {
    border: 1px solid #dddddd;
    text-align: left;
    padding: 8px;
}

tr:nth-child(even) {
    background-color: #dddddd;
}
</style>
</head>
<body>

<table>
  <tr>
    <th>Company</th>
    <th>Contact</th>
    <th>Country</th>
  </tr>
  <tr>
    <td>Alfreds Futterkiste</td>
    <td>Maria Anders</td>
    <td>Germany</td>
  </tr>
  <tr>
    <td>Centro comercial Moctezuma</td>
    <td>Francisco Chang</td>
    <td>Mexico</td>
  </tr>
  <tr>
    <th>Ernst Handel</th>
    <th>Roland Mendel</th>
    <th>Austria</th>
  </tr>
  <tr>
    <td>Island Trading</td>
    <td>Helen Bennett</td>
    <td>UK</td>
  </tr>
  <tr>
    <td>Laughing Bacchus Winecellars</td>
    <td>Yoshi Tannamuri</td>
    <td>Canada</td>
  </tr>
  <tr>
    <td>Magazzini Alimentari Riuniti</td>
    <td>Giovanni Rovelli</td>
    <td>Italy</td>
  </tr>
</table>

</body>
</html>