How to Synchronize the Scroll Position of Two Divs

How do I synchronize the scroll position of two divs?

$('#bottom').on('scroll', function () {
$('#top').scrollTop($(this).scrollTop());
});

Here we are using .scrollTop() for all it's worth, getting the scrollTop value from the element with scroll-bars, and setting the scrollTop for the other element to sync their scroll positions: http://api.jquery.com/scrollTop

This assumes that your bottom element has an ID of bottom and your top element has an ID of top.

You can hide the scroll-bars for the top element using CSS:

#top {
overflow : hidden;
}

Here is a demo: http://jsfiddle.net/sgcer/1884/

I suppose I've never really had a reason to do this, but it looks pretty cool in action.

How to make Syncing two Divs scroll positions smoother

relying on the scroll events (OPs method 1) is fine for a desktop implementation. the scroll event fires before the screen is updated. on mobile devices, especially iOS this is not the case. due to limited resources the scroll event only fires after the user completed (lifted his finger) the scroll operation.

implementing manual scrolling

to have a scroll event while the user scrolls on iOS requires to implement the scrolling manually.

  1. register the touchstart event. and get the first touch:

    var element1 = document.getElementById('content1');
    var element2 = document.getElementById('content2');

    var activeTouch = null;
    var touchStartY = 0;
    var element1StartScrollTop = 0;
    var element2scrollSyncFactor = 0;

    document.addEventListener('touchstart', function(event) {
    event.preventDefault();

    var touch = event.changedTouches[0];

    if ( activeTouch == null ) {
    // implement check if touch started on an element you want to be scrollable
    // save a reference to the scrolling element for the other functions
    activeTouch = touch;
    touchStartY = touch.screenY;
    // if scroll content does not change do this calculation only once to safe compute and dom access time while animating
    calcSyncFactor();
    }
    });

    function calcSyncFactor()
    {
    // calculate a factor for scroll areas with different height
    element2scrollSyncFactor = (element2.scrollHeight - element2.clientHeight) / (element1.scrollHeight - element1.clientHeight);
    }
  2. update your scroll position on finger movement:

    document.addEventListener('touchmove', function() {
    for ( var i = 0; i < event.changedTouches.length; i++ ) {
    var touch = event.changedTouches[i];

    if ( touch === activeTouch ) {
    var yOffset = touch.screenY - touchStartY;
    element1.scrollTop = element1StartScrollTop + (0 - yOffset);
    syncScroll();
    break;
    }
    }
    });

    function syncScroll()
    {
    element2.scrollTop = Math.round(element1.scrollTop * element2scrollSyncFactor);
    }

    it is possible to add a check that starts the scrolling only after the user has moved his finger some pixels. this way if the user clicks an element the document will not scroll some pixels.

  3. cleanup after the user lifts the finger:

    document.addEventListener('touchend', touchEnd);
    document.addEventListener('touchcancel', touchEnd);

    function touchEnd(event)
    {
    for ( var i = 0; i < event.changedTouches.length; i++ ) {
    var touch = event.changedTouches[i];
    if ( touch === activeTouch ) {
    // calculate inertia and apply animation
    activeTouch = null;
    break;
    }
    }
    }

    to have the scrolling feel more natuaral apply the iOS rubber band effect and inertia. calculate the velocity of the scroll by comparing the last touchMove yOffset with the one before. from this velocity apply an animation (for example css transition) that slowly stops the scrolling

see FIDDLE. see result on iOS. the fiddle only implements the solution for touch devices. for desktop devices use OP's method 1. implement a condition which checks which method to use depending on device.

how to apply inertia with css transitions

it would be possible to animate in javascript with requestAnimationFrame. a probably more performant way on mobile might be the use of css transformations or css animations. although an elements scroll position can not be animated with css.

  1. change the structure of the html to.

    • div: container with overflow: hidden

      • div: content with position: absolute

        depending on content size use css property -webkit-transform: translateZ(0) on content div. this will create a new layer with its own backing surface, which will be composited on the gpu.

  2. implement the functions described above so that they animate the content's top position instend of scrollTop

    var element1 = document.getElementById('content1');
    var element2 = document.getElementById('content2');

    var activeTouch = null;
    var touchStartY = 0;
    var element1StartScrollTop = 0;
    var element2scrollSyncFactor = 0;
    var offsetY = 0;
    var lastOffsetY = 0;

    document.addEventListener('touchstart', function(event) {
    event.preventDefault();

    var touch = event.changedTouches[0];

    if ( activeTouch == null ) {
    activeTouch = touch;
    touchStartY = touch.screenY;
    // use offsetTop instead of scrollTop
    element1StartScrollTop = element1.offsetTop;
    // if scroll content does not change do this calculation only once to safe compute time while animating
    calcSyncFactor();

    // cancel inertia animations
    element1.style.webkitTransition = 'none';
    element2.style.webkitTransition = 'none';
    }
    });

    function calcSyncFactor()
    {
    // calculate a factor for scroll areas with different height
    // use the div's sizes instead of scrollTop
    element2scrollSyncFactor = (element2.clientHeight - element2.parentNode.clientHeight) / (element1.clientHeight - element1.parentNode.clientHeight);
    }

    document.addEventListener('touchmove', function() {
    for ( var i = 0; i < event.changedTouches.length; i++ ) {
    var touch = event.changedTouches[i];

    if ( touch === activeTouch ) {
    lastOffsetY = offsetY;
    offsetY = touch.screenY - touchStartY;
    // use offsetTop instead of scrollTop
    element1.style.top = (element1StartScrollTop + offsetY) + 'px';
    syncScroll();
    break;
    }
    }
    });

    function syncScroll()
    {
    element2.style.top = Math.round(element1.offsetTop * element2scrollSyncFactor) + 'px';
    }

    document.addEventListener('touchend', touchEnd);
    document.addEventListener('touchcancel', touchEnd);

    function touchEnd(event)
    {
    for ( var i = 0; i < event.changedTouches.length; i++ ) {
    var touch = event.changedTouches[i];
    if ( touch === activeTouch ) {
    applyInertia();
    activeTouch = null;
    break;
    }
    }
    }
  3. when the user finishes scrolling and lifts his finger apply the inertia

    function applyInertia()
    {
    var velocity = offsetY - lastOffsetY;
    var time = Math.abs(velocity) / 150;
    var element1EndPosition = element1.offsetTop + velocity;

    element1.style.webkitTransition = 'top ' + time + 's ease-out';
    element1.style.top = element1EndPosition + 'px';

    element2.style.webkitTransition = 'top ' + time + 's ease-out';
    element2.style.top = Math.round(element1EndPosition * element2scrollSyncFactor) + 'px';
    }

    the inertia is calculated from the velocity when the user lifted the finger. fiddle around with the values to get desired results. a rubberband effect could be implemented in this function aswell. to have no javascript involved applying css animations might be the trick. another way would be to register events for when the transitions finish. if the transition finishes and the scroll position is outside the container apply a new transition that animates the content back.

see FIDDLE. see result on iOS.

synchronise scrolling between 2 divs in native javascript (2020)

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>

Synchronize scrolls on 2 divs

So the problem was that when scrolling the first list it updated the second one which also reacted to scroll event and updated the first one. I'm not sure that's what you described in the question, but anyway my idea of fixing it is to check where's cursor right now and update the opposite list. It can be easily done with extra property in the class and updating it on mouseenter event.

This is my solution: https://stackblitz.com/edit/angular-9-0-0-rc-1-sync-scroll-m9jqr7?file=src/app/app.component.ts

How do I synchronize the scroll position of two divs using AngularJS?

Here is my example. It was initially written to combine vertical scrolls for two elements with the same size which are positioned in different blocks on page. I've rewritten it for horizontal scroll.

But it needs elements to be the same width. And it synchronizes the scroll - they are both master and slave at one time. If your elements have different sizes, you can set scroll using a proportion, something like el.scrollLeft = scrollLeft*(el.width/otherEl.width).

synchronizing scrolling between 2 divs

Always check the console - that will cause errors because you are attempting to use jQuery methods on native elements (since you retrieved them via [0]). If you were doing this purely for the sake of the if condition, there's no need - to check the selectors found elements, you can just query the length property.

$(function() {
var bmrDetailDiv = $("div[id$='_bmrDetailDataDiv']");
var residentDetailDiv = $("div[id$='_residentDetailDataDiv']");

if (bmrDetailDiv.length && residentDetailDiv.length) {

bmrDetailDiv.on('scroll', function () {
residentDetailDiv.scrollTop($(this).scrollTop());
});
residentDetailDiv.on('scroll', function () {
bmrDetailDiv.scrollTop($(this).scrollTop());
});

}
});

Other changes:

1) Document ready handler instead of window.onload

2) Use of $(this) inside event callback

How can I synchronize the "scrollLeft" property of 2 divs while scrolling horizontally within a react component?

Eventually, I found the solution myself. It works perfectly if useRef is used instead of useState. When the scroll event fires, the scrollLeft property of div1 is set to the value of the scrollLeft property of div2 using the references created with useRef.

const { useRef } = require('react');

const MainComponent = () => {
const div1 = useRef(null);
const div2 = useRef(null);

const onScroll = () => {
div1.current.scrollLeft = div2.current.scrollLeft;
}

return (
<>
<div ref={div1} style={{ height:'200pt', width:'800pt', overflow:'scroll'}} onScroll={onScroll}>
<div style={{ height:'600pt', width:'1600pt', backgroundColor:'lightgray' }}>
...
</div>
</div>
<div ref={div2} style={{ height:'200pt', width:'800pt', overflow:'scroll'}} onScroll={onScroll}>
<div style={{ height:'600pt', width:'1600pt', backgroundColor:'lightgray' }}>
...
</div>
</div>
</>
)
};

export default MainComponent;


Related Topics



Leave a reply



Submit