Reusable Scroll Sync w/o JQuery #2

by thomastthai

HTML

<!--
Answer from Gregor y in a comment at:

https://stackoverflow.com/questions/9236314/how-do-i-synchronize-the-scroll-position-of-two-divs
-->

<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>

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;
}

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');