You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何实现多内容长度DIV的同速率同步滚动?

多DIV同步滚动需求实现方案(2023年1月5日更新:推荐方案见文末)

需求说明

如何仅使用原生JavaScript或TypeScript,不依赖其他库实现以下需求?

  • 两个或多个内容长度不同的DIV
  • 所有DIV以相同速率滚动
  • 鼠标可悬停在任意DIV上操作滚动
  • 当短内容DIV(SCD)滚动到内容末尾时停止滚动,其他DIV继续滚动;若反向滚动,SCD则反向滚动至内容开头,其他DIV仍继续滚动。

现有方案的不足

现有多DIV同步滚动的诸多实现无法满足上述需求,原因如下:

  • 部分实现假设内容长度一致。
  • 仅当鼠标悬停在长内容DIV上时有效,若悬停在短内容DIV上,SCD滚动到末尾后两者均停止。
  • 让DIV以不同速度滚动以匹配内容首尾,与需求接近但不符。

相关参考实现

此前有相关讨论,其中'Gregor y'的评论方案接近需求,但该方案中两个DIV滚动速度不同,其简化实现的代码示例如下:

实现代码

JavaScript

function syncOnScroll(selector,SyncFn) {
  if(!SyncFn)return;
  let active = null;
  document.querySelectorAll(selector).forEach((div) => {
    div.addEventListener("mouseenter", (e) => {active = e.target});

    div.addEventListener("scroll", (e) => {
      //ignore inactive-scroll events
      if (e.target !== active) return;

      //push the active-scroll event to synced elements
      document.querySelectorAll(selector).forEach((target) => {
        if (active === target) return;

        SyncFn(target,active);
      });
    });
  });
}

function scrollSync(selector,scrollType) {
    let type = scrollType || 'both';
  function calcPosFn(position,fromRange,fromWidth,toRange,toWidth){ 
    if(fromRange == fromWidth) return 0;
    return Math.floor(
        position   *   ((toRange-toWidth)/
                    (fromRange-fromWidth))
    );
  }
  
  switch(type.toLowerCase()){
    case 'vertical':
        syncOnScroll(selector,function(t,a){
        t.scrollTop=calcPosFn(
            a.scrollTop,
          a.scrollHeight,
          a.clientHeight,
          t.scrollHeight,
          t.clientHeight)});break;
    case 'horizontal':
        syncOnScroll(selector,function(t,a){
        t.scrollLeft=calcPosFn(
            a.scrollLeft,
          a.scrollWidth,
          a.clientWidth,
          t.scrollWidth,
          t.clientWidth)});break;
    case 'both':default:
        syncOnScroll(selector,function(t,a){
        t.scrollTop  = calcPosFn(
            a.scrollTop,
          a.scrollHeight,
          a.clientHeight,
          t.scrollHeight,
          t.clientHeight);
        t.scrollLeft = calcPosFn(
            a.scrollLeft,
          a.scrollWidth,
          a.clientWidth,
          t.scrollWidth,
          t.clientWidth);
      });break;
    }
}

//RWM: Call the function on the elements you need synced.
scrollSync(".scrollSyncV",'vertical');

CSS

/* use whatever method you want to put the tables side-by-side */
:root {
  padding: 1rem;
}
.scrollSyncV {
    float: left;
    width: 50%;
    height: 10rem;
    overflow: auto;
    border: 1px solid black;
    box-sizing: border-box;
}

table {
    width: 100%;
}

tr:nth-child(even) td {
    background-color: #eef;
}

HTML

<div class="scrollSyncV" id="1">
   <table>
   <th>Longer</th>
       <tr><td>Left Line 1</td></tr>
       <tr><td>Left Line 2</td></tr>
       <tr><td>Left Line 3</td></tr>
       <tr><td>Left Line 4</td></tr>
       <tr><td>Left Line 5</td></tr>
       <tr><td>Left Line 6</td></tr>
       <tr><td>Left Line 7</td></tr>
       <tr><td>Left Line 8</td></tr>
       <tr><td>Left Line 9</td></tr>
       <tr><td>Left Line 10</td></tr>
       <tr><td>Left Line 11</td></tr>
       <tr><td>Left Line 12</td></tr>
       <tr><td>Left Line 13</td></tr>
       <tr><td>Left Line 14</td></tr>
       <tr><td>Left Line 15</td></tr>
       <tr><td>Left Line 16</td></tr>
       <tr><td>Left Line 17</td></tr>
       <tr><td>Left Line 18</td></tr>
       <tr><td>Left Line 19</td></tr>
       <tr><td>Left Line 20</td></tr>
       <tr><td>Left Line 21</td></tr>
       <tr><td>Left Line 22</td></tr>
       <tr><td>Left Line 23</td></tr>
       <tr><td>Left Line 24</td></tr>
       <tr><td>Left Line 25</td></tr>
   </table>
</div>
<div class="scrollSyncV" id="2">
    <table>
    <th>Shorter</th>
       <tr><td>Right Line 1</td></tr>
       <tr><td>Right Line 2</td></tr>
       <tr><td>Right Line 3</td></tr>
       <tr><td>Right Line 4</td></tr>
       <tr><td>Right Line 5</td></tr>
       <tr><td>Right Line 6</td></tr>
       <tr><td>Right Line 7</td></tr>
       <tr><td>Right Line 8</td></tr>
       <tr><td>Right Line 9</td></tr>
       <tr><td>Right Line 10</td></tr>
   </table>
</div>

<div id="fiddleBottomPadding" style="height:100px"></div>

更新说明

@Roko的实现基本满足需求,同时提到了快速滚动的注意事项,感谢@Roko!

经讨论Twitter滚动实现及CSS sticky的使用,找到了采用CSS sticky的方案,符合需求概念。


内容的提问来源于stack exchange,提问作者Thomas

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.05 20:40:17