0

I was looking for a good solution to scroll a div (that is scrollable of course) to its top, with a single click, and in an animated way. I came across this (the #2 solution), which seemed pretty good, but it scrolls the whole window instead of a specific div.

I slightly modified this function so that it can scroll a container instead of the whole window. Here's my version:

function scrollContainerToTop(scrollDuration, container_id) {
    const container = document.getElementById(container_id),
    scrollHeight = container.scrollTop,
    scrollStep = Math.PI / (scrollDuration / 15),
    cosParameter = scrollHeight / 2;
    var scrollCount = 0,
        scrollMargin,
        scrollInterval = setInterval(function () {
            if (container.scrollTop != 0) {
                scrollCount = scrollCount + 1;
                scrollMargin = cosParameter - cosParameter * Math.cos(scrollCount * scrollStep);
                container.scrollTop = (scrollHeight - scrollMargin);
            } else clearInterval(scrollInterval);
        }, 15);
}

And here is a functional example, which you can try out: scroll the section with all the "Hello"s to the bottom, then click the button to scroll it up with javascript.

So my problem is that instead of just scrolling up, it scrolls up and down rapidly two or three times before settling on the top. However, if you call the scroller function with a bigger delay (say 300ms), then everything is fine. I tried it with Safari, Chrome, and an older version of Firefox as well, and every one of them produces the same glitchy result.

So why is this function glitchy if I supply it faster scroll times?

Community
  • 1
  • 1
notadam
  • 2,494
  • 2
  • 16
  • 30
  • Please include the relevant code IN your question, not just a link to an external resource. This is a StackOverflow policy. – jfriend00 Mar 25 '15 at 03:00
  • Your example goes wrong because 15 doesn't divide 100 exactly. – Phylogenesis Mar 25 '15 at 03:05
  • Aah, I see... I'm new to javascript, so this might be a dumb question, but: is changing 15 to 15.0 a possible solution? I'm coming from a C++ background, so I suspect it treats the numbers as integers, or is this not a thing in javascript? – notadam Mar 25 '15 at 03:07
  • It's not due to integer division. It's to do with the way the algorithm steps in multiples of 15 and the fact it only tests for `container.scrollTop != 0`. – Phylogenesis Mar 25 '15 at 03:09
  • Ah, I see. I'll try to correct it then. Thanks! – notadam Mar 25 '15 at 03:10
  • Okay, I simply removed the division by 15, and now it seems to work. The timing is different though, but I can just adjust my values that I call the function with, that's not a problem. – notadam Mar 25 '15 at 03:11
  • Yes, that will make the scroll take 15 times as long. – Phylogenesis Mar 25 '15 at 03:13

1 Answers1

0

A quick examination of the debugger (with a carefully placed console.log) will answer the question:

function scrollContainerToTop(scrollDuration, container_id) {
    const container = document.getElementById(container_id),
    scrollHeight = container.scrollTop,
    scrollStep = Math.PI / (scrollDuration / 15),
    cosParameter = scrollHeight / 2;
    var scrollCount = 0,
        scrollMargin,
        scrollInterval = setInterval(function () {
            if (container.scrollTop != 0) {
                scrollCount = scrollCount + 1;
                scrollMargin = cosParameter - cosParameter * Math.cos(scrollCount * scrollStep);
                container.scrollTop = (scrollHeight - scrollMargin);
                // debug here:
                console.log( 'container.scrollTop = ' + container.scrollTop );
            } else clearInterval(scrollInterval);
        }, 150);
}

Results:

container.scrollTop = 554
container.scrollTop = 465
container.scrollTop = 338
container.scrollTop = 202
container.scrollTop = 85
container.scrollTop = 14
container.scrollTop = 3
container.scrollTop = 55
container.scrollTop = 159
container.scrollTop = 293
container.scrollTop = 426
container.scrollTop = 530
container.scrollTop = 582
container.scrollTop = 571
container.scrollTop = 500
container.scrollTop = 383
container.scrollTop = 247
container.scrollTop = 120
container.scrollTop = 31
container.scrollTop = 0

For a more complete explanation:

When you fail to reach 0, the next iteration will actually produce a negative scrollMargin (because that's how cosine works). The iteration continues back and forth until you finally reach 0.

See what happens when we output the actual values of the variables:

console.log( 'scrollMargin = ' + cosParameter + ' - ' + cosParameter + ' * ' + Math.cos(scrollCount * scrollStep) );

scrollMargin = 293 - 293 * 0.8910065241883679
scrollMargin = 293 - 293 * 0.5877852522924731
scrollMargin = 293 - 293 * 0.15643446504023092
scrollMargin = 293 - 293 * -0.30901699437494734
scrollMargin = 293 - 293 * -0.7071067811865475
scrollMargin = 293 - 293 * -0.9510565162951535
scrollMargin = 293 - 293 * -0.9876883405951378
scrollMargin = 293 - 293 * -0.8090169943749475
scrollMargin = 293 - 293 * -0.4539904997395469
scrollMargin = 293 - 293 * -1.8369701987210297e-16
scrollMargin = 293 - 293 * 0.45399049973954664
scrollMargin = 293 - 293 * 0.8090169943749472
scrollMargin = 293 - 293 * 0.9876883405951377
scrollMargin = 293 - 293 * 0.9510565162951536
scrollMargin = 293 - 293 * 0.7071067811865477
scrollMargin = 293 - 293 * 0.30901699437494773
scrollMargin = 293 - 293 * -0.15643446504022968
scrollMargin = 293 - 293 * -0.5877852522924729
scrollMargin = 293 - 293 * -0.8910065241883681
scrollMargin = 293 - 293 * -1

One solution would be to simply check the resulting container.scrollTop. If you are within the scrollMargin, then just go ahead and complete the scroll:

if (container.scrollTop < scrollMargin) container.scrollTop = 0;
JDB still remembers Monica
  • 21,669
  • 4
  • 66
  • 107
  • I can see that the distance oscillates up and down, but I already knew this. How does this help? It doesn't reveal the cause of it really. – notadam Mar 25 '15 at 03:09
  • Okay now, I see. I wasn't aware that it checked for 0, but used a bigger "step", so it stepped over it, and that's why it oscillated around zero until it got there. That's a fault in the original algorithm then. But thanks for clarifying! – notadam Mar 25 '15 at 03:15
  • Brilliant, or simply change this: if (container.scrollTop != 0) { to this : if (container.scrollTop > 1) { Original artist: [link](https://stackoverflow.com/questions/21474678/scrolltop-animation-without-jquery) – Y.K. May 05 '20 at 23:00