Search code examples
javascriptwebscrolldragvertical-scrolling

Vertical drag-scrolling with inertia


I have gotten vertical scrolling by dragging up/down with the mouse to work on my webpage. However, the problem is that it feels pretty abrupt because the scrolling stops instantly when you stop dragging. I would like to add some inertia so that it continues to scroll for a bit after you stop dragging until it comes to a stop.

This is somewhat complicated to implement so I would like some help. This is my all of my current code:

let newScrollY = 0
let element = null

const dragScroll = (enabled) => {
        element?.removeEventListener("mousedown", element?.mouseDownFunc, false)
        window.removeEventListener("mouseup", element?.mouseUpFunc, false)
        window.removeEventListener("mousemove", element?.mouseMoveFunc, false)

        element = document.querySelector(".drag")
        if (!element || !enabled) return
        let lastClientY = 0
        let mouseDown = false

        element.addEventListener("mousedown", element.mouseDownFunc = (event) => {
                event.preventDefault()
                mouseDown = true
                lastClientY = event.clientY
        }, false)

        window.addEventListener("mouseup", element.mouseUpFunc = (event) => {
            mouseDown = false
        }, false)

        window.addEventListener("mousemove", element.mouseMoveFunc = (event) => {
            if (!mouseDown) return
            let scrollElement = element
            if (element == document.body) scrollElement = document.documentElement
            newScrollY = event.clientY - lastClientY
            lastClientY = event.clientY
            scrollElement.scrollTop -= newScrollY
        }, false)
}

Solution

  • I have figured out a solution. The key was to use the absolute value of the speed and either add/subtract to the scrollTop depending on the sign of the speed (indicating whether they were scrolling up or down).

    let newScrollY = 0
    let lastScrollTop = 0
    let element = null as any
    let inertia = false
    let mouseDown = false
    
    const dragScroll = (enabled) => {
            if (inertia || mouseDown) return
            element?.removeEventListener("mousedown", element?.mouseDownFunc, false)
            window.removeEventListener("mouseup", element?.mouseUpFunc, false)
            window.removeEventListener("mousemove", element?.mouseMoveFunc, false)
    
            element = document.querySelector(".drag")
            if (!element || !enabled) return
            let lastClientY = 0
            mouseDown = false
            let time = null
            let id = 0
    
            element.addEventListener("mousedown", element.mouseDownFunc = (event) => {
                    event.preventDefault()
                    mouseDown = true
                    inertia = false
                    time = new Date()
                    lastClientY = event.clientY
                    let scrollElement = element
                    if (element == document.body) scrollElement = document.documentElement
                    lastScrollTop = scrollElement.scrollTop
                    cancelAnimationFrame(id)
            }, false)
    
            window.addEventListener("mouseup", element.mouseUpFunc = (event) => {
                mouseDown = false
                const timeDiff = (new Date() as any - time)
                let scrollElement = element
                if (element == document.body) scrollElement = document.documentElement
                let speedY = (scrollElement.scrollTop - lastScrollTop) / timeDiff * 20
                let speedYAbsolute = Math.abs(speedY)
    
                const draw = () => {
                    let scrollElement = element
                    if (element == document.body) scrollElement = document.documentElement
                    if (speedYAbsolute > 0) {
                        if (speedY > 0) {
                            scrollElement.scrollTop += speedYAbsolute--
                        } else {
                            scrollElement.scrollTop -= speedYAbsolute--
                        }
                    } else {
                        inertia = false
                    }
                    id = requestAnimationFrame(draw)
                }
                inertia = true
                draw()
            }, false)
    
            window.addEventListener("mousemove", element.mouseMoveFunc = (event) => {
                if (!mouseDown) return
                let scrollElement = element
                if (element == document.body) scrollElement = document.documentElement
                newScrollY = event.clientY - lastClientY
                lastClientY = event.clientY
                scrollElement.scrollTop -= newScrollY
            }, false)
        }