synchronise scrolling between 2 divs in native javascript (2020) - javascript

I'm trying to make a fake/duplicated scroll element that is synced with the actual x-scroll of a div in native javaScript. The use case for me is on a long table to have the x-scroll be present on screen when you're not at the bottom of the table.
This solves the situation of having a really wide table with a min-width that exceeds the current page/view-port width, causing the table to side/x-scroll. However, if the table is very long, the scroll can only be set on top or bottom of the table. That means if people are mid-way down the table and want to scroll across to see all of the columns, they would have difficulty in doing it there.
See image:
Yep, it's been done to death IN JQUERY. According to my research, no one on SO knows or has been interested in doing this in native javaScript (esp 2020). My version for reference is also in jQuery, it needs to be converted.
$dupScroll.scroll(function () {
if (scrolling) {
scrolling = false;
return true;
}
scrolling = true;
$tableParent.scrollLeft($dupScroll.scrollLeft());
});
$tableParent.scroll(function () {
if (scrolling) {
scrolling = false;
return true;
}
scrolling = true;
$dupScroll.scrollLeft($tableParent.scrollLeft());
});
All the jQuery solutions:
How to Scroll two div's at the same time?
synchronizing scrolling between 2 divs
synchronizing scrolling between 2 divs with different text size
How to sync the scroll of two divs by ID
synchronise scrolling of two divs
Help is appreciated and will be useful for all the people needing to do the same post-jQuery. I'm currently working on this myself but running into snags here and there, the 1st being attaching a scroll event onto an element. If I make something that works, I'll post it here. Thanks.

Here's the simple way to keep two divs aligned. Javascript doesn't dispatch event on actions from scripts by default, so there's no need to keep track of which div is being scrolled.
const divs = document.querySelectorAll( 'div' );
divs.forEach(div => div.addEventListener( 'scroll', e => {
divs.forEach(d => {
d.scrollTop = div.scrollTop;
d.scrollLeft = div.scrollLeft;
});
}) );
html, body {
height: 100%;
}
body {
display: flex;
padding: 0;
margin: 0;
}
div {
width: 50%;
height: 100%;
overflow: scroll;
}
span {
width: 200vw;
height: 300vh;
display: block;
background: linear-gradient(90deg, transparent, yellow), linear-gradient( 0deg, red, blue, green );
}
#div2 {
margin-top: 30px;
}
<div id="div1"><span></span></div>
<div id="div2"><span></span></div>
With Relative Scroll in Different Sized Containers
If you want to accomplish this with differently sized containers and relative scroll, just normalize the scroll value and multiply it again:
const divs = document.querySelectorAll( 'div' );
divs.forEach(div => div.addEventListener( 'scroll', e => {
const offsetTop = div.scrollTop / (div.scrollHeight - div.clientHeight);
const offsetLeft = div.scrollLeft / (div.scrollWidth - div.clientWidth);
divs.forEach(d => {
d.scrollTop = offsetTop * (d.scrollHeight - d.clientHeight);
d.scrollLeft = offsetLeft * (d.scrollWidth - d.clientWidth);
});
}) );
html, body {
height: 100%;
}
body {
display: flex;
padding: 0;
margin: 0;
}
div {
width: 50%;
height: 100%;
overflow: scroll;
}
span {
width: 200vw;
height: 300vh;
display: block;
background: linear-gradient(90deg, transparent, yellow), linear-gradient( 0deg, red, blue, green );
}
#div2 span {
height: 500vh;
width: 500vw;
}
<div id="div1"><span></span></div>
<div id="div2"><span></span></div>

Related

How to disable background page scroll when secondary view is open, without changing body css

I'm creating a secondary view that will pop-up on clicking on a link. Content within this secondary view is vertically scrollable and horizontally fixed. The issue is: when scroll reach to the top/bottom of the secondary view, the background page will scroll as well, also if attempt to scroll left/right, the background page will also scroll.
I did some search online but mostly suggest to modify the body css. Due to project constraint, we are not allowed to modify any attribute on body. So I'm trying to find a solution without making css change.
I was trying to achieve using this approach:
secondaryView.addEventListener('wheel', (event) => {
if (//mouse scroll up and secondaryView is at top
|| //mouse scroll down and secondaryView is at bottom
|| //mouse scroll left or right) {
event.preventDefault();
}
});
However, the problem is I don't know how to detect whether mouse scroll is horizontal, not able to rely on event.deltaX since it will be non-zero sometimes when scrolling up or down as well.
This problem has a lot of solutions (see this huge Q&A for options), one of which is listening for scroll event instead of the wheel one (after all, you do not want to be limited to mouse wheel, do you?).
The easiest version (given that for some reason you cannot modify styling) is to keep track of the last known scroll position of the primary view (or the window) and as soon as the secondary view is shown (a boolean flag should suffice), start snapping the primary to it with scrollTo.
The rest depends on your exact requirements - if you need the primary to be scrollable while the secondary is open, things become complex (a solution for mouse-based devices may be to track cursor position to determine what is scrolled).
(() => {
const p = document.getElementById("primary");
const s = document.getElementById("secondary");
const b = document.getElementById("show");
let secondaryShown = false;
b.addEventListener("click", () => {
if (secondaryShown) {
s.classList.remove("shown");
secondaryShown = false;
return;
}
s.classList.add("shown");
secondaryShown = true;
});
let lknownYPos = window.scrollY,
lknownXPos = window.scrollX;
window.addEventListener("scroll", ({
target,
currentTarget
}) => {
if (secondaryShown) {
window.scrollTo(lknownXPos, lknownYPos)
}
lknownWindowPos = window.scrollY;
}, true);
})();
body {
margin: 0;
}
.subcontent {
height: 300vh;
width: 300vw;
}
#primary {
width: 200vw;
height: 200vh;
}
#secondary {
overflow-x: scroll;
width: 50vw;
height: 50vh;
background-color: rgba(0, 0, 0, 0.25);
z-index: 9999;
color: white;
display: none;
font-size: 2rem;
text-align: center;
}
.shown {
display: block !important;
}
<div class="content">
<div id="primary">
<button id="show">Switch secondary</button>
<div id="secondary">
<p>Secondary</p>
<div class="subcontent"></div>
</div>
</div>
</div>

How to scroll MULTIPLE scrollbars at the same time

When I scroll the wrapper div the 2 images should also scroll together with it but it seems only the 2 images sync together but not the wrapper
The 2 images should sync with the body's scrollbar
$(function(){
$('.linked').scroll(function(){
$('.linked').scrollTop($(this).scrollTop());
})
})
#left { width: 300px; height: 400px; overflow: scroll; float: left; }
#right { width: 300px; height: 400px; overflow: scroll; float: left; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/1.5.1/jquery.min.js"></script>
<div id="left" class="linked">
<img src="http://upload.wikimedia.org/wikipedia/commons/5/51/Eiffel_Tower_(72_names).jpg">
</div>
<div id="right" class="linked">
<img src="http://upload.wikimedia.org/wikipedia/commons/5/51/Eiffel_Tower_(72_names).jpg">
</div>
This might help
let l = document.getElementById(".left");
let r = document.getElementById(".right");
l.addEventListener("scroll", function () {
r.scrollTop = l.scrollTop;
});
l.addEventListener("scroll", function () {
r.scrollTop = l.scrollTop;
});
If I understood correctly you're question is how to sync a set of scroll bars including the window scroll bar? This is possible and I will demonstrate for education purposes only however this is not something I would personally recommend.
Manipulating the browser window scroll bar can create a poor user experience as it interferes with the expected behavior of the scroll bar. Additionally, scrollbar manipulation can cause unexpected behavior and may not work consistently across different browsers and devices, leading to a fragmented user experience.
This example does not take into consideration the dimensions of the browser window and elements with scroll bars so be warned that it might not work as expected.
// Pseudo code
// 1. Query the scroll bars you want to sync
// 2. Add event listener to each of them
// 3. In the event listener, set the scroll position of the other scroll bar(s) to the same value
// Step 1
const scrollBarContainers = [
...document.querySelectorAll('.scrollable'),
document // Include document for window scrolling
];
// Step 2
scrollBarContainers.forEach(container => {
container.addEventListener('scroll', syncScrolls);
});
// Step 3
function syncScrolls(e) {
scrollBarContainers.forEach(container => {
if (container !== e.target) {
// The document element doesn't directly have a scrollLeft property.
// It's on the scrollingElement. The other elements have it directly.
if (container.scrollingElement) {
container.scrollingElement.scrollLeft = e.target.scrollLeft;
} else if (e.target.scrollingElement) {
container.scrollLeft = e.target.scrollingElement.scrollLeft;
} else {
container.scrollLeft = e.target.scrollLeft;
}
}
});
}
.container {
height: 100px;
border: 1px solid gray;
width: 900px;
margin-bottom: 1em;
overflow: auto;
}
span {
display: inline-block;
width: 1200px;
height: 50px;
background-color: red;
}
<div class="container scrollable"><span></span></div>
<div class="container scrollable"><span></span></div>

How to implement this sticky-header scroll-down component?

I have a div container that is about 70% of the height of the page. The 30% which is outside the div is dim lighted (greyed out).
I am trying to implement a functionality where scrolling down within this container div causes the container div to fill up more of the page (less vertical space is greyed out) and eventually all of it (so 100% height).
Vica versa, when scrolling upward within the container and reaching the top should cause of the greyed out space to become bigger. What is the easiest way to implement this, possibly with the help of a library?
Using the scroll event and a combination of scrollTop, scrollHeight and clientHeight properties, we could get something that resembles your need:
let elem = document.querySelector('div');
elem.addEventListener('scroll', () => {
if(elem.scrollTop == 0) {
elem.style.height = `${elem.clientHeight + 10}px`;
}
if(elem.scrollHeight - elem.clientHeight == elem.scrollTop) {
elem.style.height = `${elem.clientHeight + 1}px`;
}
});
body {
background: #555;
height: 100vh;
margin: 0;
display: flex;
align-items: center;
overflow-y: hidden;
}
div {
background: #ccc;
height: 70%;
width: 100%;
overflow-y: scroll;
}
<body>
<div>
<br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br>
</div>
</body>

Is there any way to change a div size on scroll using intersectionObserver?

I'm trying to change the size (or scale) of a div while scrolling.
This div has a .8 scale attached to it css. I'd like to reach a scale of 1 progressively while scrolling.
IntersectionObserver seems to be a good choice to work with instead of scroll event but i don't know if i can change the state of an element using it.
You can change the scale of a div using.
document.getElementById("scaledDiv").style.transform = "scale(1)";
The scroll event should do what you want it to do. You can continue to add more if statements and check how many pixels they are scrolling to change it gradually to 1 or even back to 0.8 when they scroll back up. The 50 below represents 50 pixels from the top of the page.
window.onscroll = function() {
if (document.body.scrollTop > 50 || document.documentElement.scrollTop > 50) {
// They are scrolling past a certain position
document.getElementById("scaledDiv").style.transform = "scale(1)";
} else {
// They are scrolling back
}
};
I hope this will help you:
const container = document.querySelector('.container');
const containerHeight = container.scrollHeight;
const iWillExpand = document.querySelector('.iWillExpand');
container.onscroll = function(e) {
iWillExpand.style.transform = `scale(${0.8 + 0.2 * container.scrollTop / (containerHeight - 300)})`;
};
.container {
height: 300px;
width: 100%;
overflow-y: scroll;
}
.scrollMe {
height: 1500px;
width: 100%;
}
.iWillExpand {
position: fixed;
width: 200px;
height: 200px;
top: 10px;
left: 10px;
background-color: aqua;
transform: scale(0.8);
}
<div class='container'>
<div class='scrollMe' />
<div class='iWillExpand' />
</div>

How to trigger the layout to change?

I've a sticked element which gets the top-alignment from current scroll-offset. Problem is, that the layout is not "retriggerd" if the space from it is free. So there stays a ghost-gap where the sticked element was...
http://fiddle.jshell.net/pPc4V/
The markup is pretty simple:
...
as well as the js:
var $win = $(this);
var sticked = document.querySelector('a.sticked');
$win.on('scroll', function () {
var scrollTop = $win.scrollTop();
sticked.style.top = scrollTop + 'px';
// $win.resize();
});
...and the css looks good so far:
a {
display: inline-block;
width: 90px;
height: 90px;
background: deepskyblue;
}
.sticked {
position: relative;
top: 0;
left: 0;
background: tomato;
}
I tried to trigger the resize-event on scroll (as you see above uncommented), but no success! Any ideas, how to retrigger the layout so that the free-gap is filled with the next floated element?
Update
To clarify what I mean I made a simple image-timelime:
Step 1
Step 2
Step 3
The issue is that you are setting position fixed on an element which is displayed inline. That will cause that space to occur. I have redid your jsFiddle with proper alignment.
To fix it, I added the class "stuck" only when the document's scrollTop position is greater than the scrollTop position of your target element.
jsFiddle: http://fiddle.jshell.net/pPc4V/44/
HMTL:
<div id="grid">
etc...
</div>
CSS:
#grid {
height:1000px;
overflow:hidden;
float:left
}
#grid > a {
display: inline-block;
width: 90px;
height: 90px;
background: deepskyblue;
}
.stuck {
position: fixed;
background: navy !important;
}
JS:
$(window).on('scroll', function () {
var $doc = $(document),
parentElement = $('#grid'),
childToGetStuck = parentElement.find('a:nth-child(5)');
if ($doc.scrollTop() > childToGetStuck.scrollTop()) {
childToGetStuck.addClass('stuck');
//console.log($('.stuck').scrollTop())
} else {
childToGetStuck.removeClass('stuck');
}
});

Categories