3

I search on my friend Google for some code to do smooth scroll and found this : Smooth vertical scrolling on mouse wheel in vanilla javascript?

It works well but if i scroll once and then try to use my mouse to manually move the scrollbar, it's broken...

SmoothScroll(document, 120, 12);
        function SmoothScroll(target, speed, smooth) {
            if (target === document)
                target = (document.scrollingElement ||
                    document.documentElement ||
                    document.body.parentNode ||
                    document.body) // cross browser support for document scrolling

            var moving = false
            var pos = target.scrollTop
            var frame = target === document.body &&
                document.documentElement ?
                document.documentElement :
                target // safari is the new IE

            target.addEventListener('scroll', scrolled, {
                passive: false
            })
            target.addEventListener('mousewheel', scrolled, {
                passive: false
            })
            target.addEventListener('DOMMouseScroll', scrolled, {
                passive: false
            })

            function scrolled(e) {
                e.preventDefault(); // disable default scrolling

                var delta = normalizeWheelDelta(e)

                pos += -delta * speed
                pos = Math.max(0, Math.min(pos, target.scrollHeight - frame.clientHeight)) // limit scrolling
                if (!moving) update()
            }

            function normalizeWheelDelta(e) {
                if (e.detail) {
                    if (e.wheelDelta)
                        return e.wheelDelta / e.detail / 40 * (e.detail > 0 ? 1 : -1) // Opera
                    else
                        return -e.detail / 3 // Firefox
                } else
                    return e.wheelDelta / 120 // IE,Safari,Chrome
            }

            function update() {
                moving = true

                var delta = (pos - target.scrollTop) / smooth

                target.scrollTop += delta

                if (Math.abs(delta) > 0.5)
                    requestFrame(update)
                else
                    moving = false
            }

            var requestFrame = function () { // requestAnimationFrame cross browser
                return (
                    window.requestAnimationFrame ||
                    window.webkitRequestAnimationFrame ||
                    window.mozRequestAnimationFrame ||
                    window.oRequestAnimationFrame ||
                    window.msRequestAnimationFrame ||
                    function (func) {
                        window.setTimeout(func, 1000 / 50);
                    }
                );
            }()
        }

So... i want it to work properly when i already scroll once but try to use the mouse to move the scrollbar instead of mousewheel.

Thanks for helping!

1 Answers1

2

Looks like you could fix it by re-adjusting the pos variable to the scrollTop before your scrolling calculations.

Additionally theres a bug where your scroll could get stuck in an infinite render loop causing you to never stop animating. This was due to the delta being .5 < delta < 1 making the request frame get called forever. You cant actually move the scrollTop anything less than 1 so I adjusted the conditions for another render loop and rounded the delta

    function scrolled(e) {
        // if currently not animating make sure our pos is up to date with the current scroll postion
        if(!moving) {
            pos = target.scrollTop;
        }
        e.preventDefault(); // disable default scrolling

        var delta = normalizeWheelDelta(e)

        pos += -delta * speed
        pos = Math.max(0, Math.min(pos, target.scrollHeight - frame.clientHeight)) // limit scrolling
        if (!moving) update()
    }

    function update() {
                moving = true;
        // scrollTop is an integer and moving it by anything less than a whole number wont do anything
        // to prevent a noop and an infinite loop we need to round it
        var delta = absRound((pos - target.scrollTop) / smooth)

        target.scrollTop += delta

        if (Math.abs(delta) >= 1) {
            requestFrame(update)
        } else {
            moving = false
        }
    }

    function absRound(num) {
        if(num < 0) {
            return -1*Math.round(-1*num);
        } else {
            return Math.round(num);
        }
    }

That way when manually adjusting the scroll position if the wheel is used it doesnt jump to the position it was once at, but instead adjust itself to the current scroll position.

ug_
  • 11,267
  • 2
  • 35
  • 52
  • Thanks! It works well except when i try to manually scroll before scroll animation ends. Is there a way to stop animation when trying to manually move the scrollbar? – Kenjee Grondin Aug 06 '19 at 12:41
  • Oh, i speak too fast... when trying to grab scrollbar on top instead of middle, it still broken :-( – Kenjee Grondin Aug 06 '19 at 12:48
  • @KenjeeGrondin fixed – ug_ Aug 06 '19 at 16:54
  • Thanks @ug_ You forget to let "moving = true" in update function, so you can edit your message to add it. – Kenjee Grondin Aug 06 '19 at 18:49
  • I'm facing a new issue with this code... I can't scroll correctly on inside element. Example: I have a mobile menu who i normally can scroll in side if the height is more than the viewport, but when this code is active, all the time i scroll (even when overflow hidden), it scroll the document only. Can this code be active in each element who need scroll and not just the document? @ug_ – Kenjee Grondin Aug 08 '19 at 14:29
  • @KenjeeGrondin try adding “e.stopPropogation();” to the scrolled method. My guess is that the scroll event is being sent to multiple elements – ug_ Aug 12 '19 at 05:10
  • That's not working unfortunately and create a new bug when trying to scroll directly on load @ug_ – Kenjee Grondin Aug 12 '19 at 13:09