Here is a solution for mobile, with some optional enhancements. If the dragged item gets within 100px of the top or bottom it will start scrolling. It is not specific to react-dnd. You just need to have a global isDragging state (mine is stored in Redux). (e.g. set isDragging = true on beginDrag and isDragging = false on endDrag.)
I found the suggestions for react-dnd to be confusing and vary a lot between different versions of the library. None of the solutions that used the dragover browser event work on mobile, i.e. when using the TouchBackend.
Useful discussion: https://github.com/react-dnd/react-dnd/issues/553
Basic Example
// number of pixels near the top or bottom edge when auto scroll will begin
const DISTANCE_FROM_EDGE = 100
// number of pixels that are scrolled on each touchmove event
// this could be replaced with an easing function
const SCROLL_RATE = 1
/** Handles auto scroll on drag near the edge of the screen on mobile. */
const onTouchMove = e => {
// global isDragging state that must be set to true on beginDrag and false on endDrag
if (isDragging()) {
// distance of touch from top of screen
const y = e.touches[0].clientY
// scroll down
if (y < DISTANCE_FROM_EDGE) {
window.scrollTo(0, document.documentElement.scrollTop + SCROLL_RATE)
}
// scroll up
else if (y > window.innerHeight - DISTANCE_FROM_EDGE) {
window.scrollTo(0, document.documentElement.scrollTop - SCROLL_RATE)
}
}
}
window.addEventListener('touchmove', onTouchMove)
Example with easing
You may want it to scroll faster the closer you get to the edge. A cubic easing function works well.
// number of pixels near the top or bottom edge when auto scroll will begin
const DISTANCE_FROM_EDGE = 100
// cubic easing function for a more natural-feeling scroll speed
const ease = n => Math.pow(n, 3)
/** Handles auto scroll on drag near the edge of the screen on mobile. */
const onTouchMove = e => {
// global isDragging state that must be set to true on beginDrag and false on endDrag
if (isDragging()) {
// distance of touch from top of screen
const y = e.touches[0].clientY
// scroll down
if (y < DISTANCE_FROM_EDGE) {
const rate = ease(1 + (DISTANCE_FROM_EDGE - y) / DISTANCE_FROM_EDGE)
window.scrollTo(0, document.documentElement.scrollTop - rate)
}
// scroll up
else if (y > window.innerHeight - DISTANCE_FROM_EDGE) {
const rate = ease(1 + (y - window.innerHeight + DISTANCE_FROM_EDGE) / DISTANCE_FROM_EDGE)
window.scrollTo(0, document.documentElement.scrollTop + rate)
}
}
}
window.addEventListener('touchmove', onTouchMove)
Example with continuous scroll
A limitation with the above solution is that it only auto scrolls when it is receiving touchmove events. If the user pauses their movement, but they are still dragging, the scroll stops. An improvement would be to scroll continuously when the user is dragging near the edge. I abstracted out an autoscroll function that handles starting, stopping, and updating the rate.
/** An autoscroll function that will continue scrolling smoothly in a given direction until autoscroll.stop is called. Takes a number of pixels to scroll each iteration. */
const autoscroll = (() => {
/** Cubic easing function. */
const ease = (n: number) => Math.pow(n, 3)
// if true, the window will continue to be scrolled at the current rate without user interaction
let autoscrolling = false
// scroll speed (-1 to 1)
let rate = 1
/** Scroll vertically in the direction given by rate until stop is called. */
const scroll = () => {
window.scrollTo(0, document.documentElement.scrollTop + rate)
window.requestAnimationFrame(() => {
if (autoscrolling) {
scroll()
}
})
}
/** Starts the autoscroll or, if already scrolling, updates the scroll rate (-1 to 1). */
const startOrUpdate = (rateNew: number) => {
// update the scroll rate
rate = ease(rateNew)
// if we are already autoscrolling, do nothing
if (autoscrolling) return
// otherwise kick off the autoscroll
autoscrolling = true
scroll()
}
/** Stops scrolling. */
startOrUpdate.stop = () => {
autoscrolling = false
}
return startOrUpdate
})()
// number of pixels near the top or bottom edge when auto scroll will begin
const DISTANCE_FROM_EDGE = 100
/** Handles auto scroll on drag near the edge of the screen on mobile. */
const onTouchMove = e => {
// global isDragging state that must be set to true on beginDrag and false on endDrag
if (isDragging()) {
// distance of touch from top of screen
const y = e.touches[0].clientY
// scroll down
if (y < DISTANCE_FROM_EDGE) {
const rate = 1 + (DISTANCE_FROM_EDGE - y) / DISTANCE_FROM_EDGE
autoscroll(-rate)
}
// scroll up
else if (y > window.innerHeight - DISTANCE_FROM_EDGE) {
const rate = 1 + (y - window.innerHeight + DISTANCE_FROM_EDGE) / DISTANCE_FROM_EDGE
autoscroll(rate)
}
// stop scrolling when not near the edge of the screen
else {
autoscroll.stop()
}
}
}
window.addEventListener('touchmove', onTouchMove)