Using plain Javascript or Typescript and no other dependencies, how would the following be implemented?
The dozens of implementations of simultaneous multiple DIVs scrolls aren't able to do the above because:
One previous discussion on a similar topic had some solutions that came close, especially a comment by 'Gregor y'. Unfortunately, that solution had both DIVs move at different speeds. The simplified Fiddle for their implementation is available.
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');
/* 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;
}
<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>
Update:
@Roko's fiddle pretty much answered my question as was stated. He did make a note about fast scrolling. Thanks, Roko!
Based on further discussion with @Roko about Twitter's scroll implementation and his mention of using CSS sticky, a solution by @rootShiv that uses CSS sticky was found that met the concept I was after.
Edit: The closest I got to a solution is this jsFiddle example.
To give you a starting point (no manual scrollbars scroll implemented yet)
by using the "wheel"
event, preventing the default scroll behavior by using Event.preventDefault()
and by applying a delta and by using Element.scrollBy()
const elsDIV = document.querySelectorAll(".scrollSyncV");
elsDIV.forEach(el => {
const elOther = [...elsDIV].filter(e => e !== el)[0];
el.addEventListener('wheel', evt => {
evt.preventDefault();
const delta = Math.sign(evt.deltaY);
elsDIV.forEach(el => el.scrollBy({top: 90.909 * delta}))
});
});
body {
display: flex;
}
.scrollSyncV {
width: 50%;
height: 10rem;
overflow: auto;
border: 1px solid black;
box-sizing: border-box;
font-size: 2.5rem;
}
.scrollSyncV div {
border: 10px dashed #aaa;
}
<div class="scrollSyncV">
Lorem ipsum dolor sit amet, consectetur adipisicing elit.
Consequuntur rerum rem quae iste earum aspernatur.
Laudantium eum, animi maiores unde itaque,
repellat sed magni dicta earum alias, ipsa ad labore!
Deserunt recusandae modi. Earum autem provident eum in officia.
Lorem ipsum dolor sit amet, consectetur adipisicing elit.
Iure possimus doloribus veritatis dolores voluptate quae
fuga eaque mollitia magni nam exercitationem ratione itaque
est debitis dolor, nemo repudiandae voluptas sequi!
<br>
End.
</div>
<div class="scrollSyncV">
Lorem ipsum dolor sit amet, consectetur adipisicing elit.
Consequuntur rerum rem quae iste earum aspernatur.
Laudantium eum, animi maiores unde itaque,
repellat sed magni dicta earum alias, ipsa ad labore!
<br>End.
</div>