Reusable Scroll Sync w/o JQuery

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>
       <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>
       <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>
       <tr><td>Right Line 11</td></tr>
       <tr><td>Right Line 12</td></tr>
       <tr><td>Right Line 13</td></tr>
       <tr><td>Right Line 14</td></tr>
       <tr><td>Right Line 15</td></tr>
       <tr><td>Right Line 16</td></tr>
       <tr><td>Right Line 17</td></tr>
       <tr><td>Right Line 18</td></tr>
       <tr><td>Right Line 19</td></tr>
       <tr><td>Right Line 20</td></tr>
   </table>
</div>
<div...

CSS

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

.scrollSyncH {
    width: 100%;
    float: clear;
    height: 100px;
    overflow: auto;
    border: 1px solid black;
    box-sizing: border-box;
}
.scrollSyncH table td{
    min-width: 80px;
}

table {
    width: 100%;
}

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

.syncContainerLocked{
   height: 100px;
   width: 300px;
   overflow: hidden;
}

.syncContainer{
   height: 100px;
   width: 300px;
   overflow: auto;
}

.syncPaneAbs{
  display: block;
  overflow: hidden;
  height: 1000px;
  width: 1000px;
}

#lockedPaneRel{
  display: block;
  overflow: hidden;
  height: 10px;
  width: 10px;
}

#topPaneRel{
  display: block;
  overflow: hidden;
  height: 100px;
  width: 100px;
}

#leftPaneRel{
  display: block;
  overflow: hidden;
  height: 500px;
  width: 500px;
}

#middlePaneRel{
  display: block;
  overflow: hidden;
  height: 100000px;
  width: 100000px;
}

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');
scrollSync(".scrollSyncH",'horizontal');

scrollSync('.syncV','vertical');
scrollSync('.syncH','horizontal');
scrollSync('.syncB');

//********* Build...