如何实现多内容长度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
相关产品推荐
相关产品推荐

