← Back toDrag and drop in React

Scroll by dragging

Written byPhuoc Nguyen
Created
03 Nov, 2023
Last updated
10 Nov, 2023
Tags
drag-to-scroll
When scrolling through a container, most users rely on their mouse or trackpad. But have you considered drag-to-scroll? This method offers several advantages over traditional scrolling. For one, it allows for more precise control over scrolling speed and direction, which is especially useful when working with intricate designs in applications like Figma.
Drag-to-scroll is also less taxing on your hands and wrists, as it allows for a more natural hand movement that doesn't require repetitive motions like those used with a mouse. And for touchscreens or other devices without a traditional mouse, drag-to-scroll offers a more intuitive and user-friendly experience overall.
Many popular websites and applications, including Figma and Spotify's web player, are adopting drag-to-scroll. Even games like Minecraft are using it. As more interfaces adopt this feature, it will likely become even more prevalent.
In this post, we'll explore how to implement drag-to-scroll functionality with React, so you can take advantage of this powerful feature in your own projects.

Scrolling by dragging

Let's say we have a container that can be scrolled, like the one below:
tsx
<div className="container">...</div>
To enable scrolling within a container, you can set the `overflow` property to `auto`.
css
.container {
overflow: auto;
}
Let's take a moment to revisit the post where we learned how to create a draggable element. In that post, we used the `useRef()` hook to create a reference to the target element, which we then attached to the element using the `ref` attribute. We also handled the `mousedown` and `touchstart` events to make the target element draggable on both desktop and touchscreen devices.
Just in case you need a refresher, here's a quick code snippet to remind you of what we did:
tsx
const containerRef = React.useRef();

// Render
<div
className="container"
ref={containerRef}
onMouseDown={handleMouseDown}
onTouchStart={handleTouchStart}
/>
Previously, we used to position draggable elements by setting their position absolutely within their container and updating their position using the `transform` property. However, we've adopted a different approach for our drag-to-scroll functionality. We no longer use internal states like `dx` and `dy`. Instead, we now store the starting point and the current scroll position when users start dragging the element.
This is how we handle the `mousedown` event:
ts
const handleMouseDown = (e) => {
const ele = containerRef.current;
const startPos = {
left: ele.scrollLeft,
top: ele.scrollTop,
x: e.clientX,
y: e.clientY,
};
// ...
};
The starting point has four properties: `left` and `top`, which indicate the scroll position, and `x` and `y`, which represent the mouse position.
Let's calculate the horizontal and vertical distance the mouse moves while in motion. Instead of updating internal states like we did before, we can simply update the scroll position of the target element.
ts
const handleMouseMove = (e) => {
const dx = e.clientX - startPos.x;
const dy = e.clientY - startPos.y;
ele.scrollTop = startPos.top - dy;
ele.scrollLeft = startPos.left - dx;
updateCursor(ele);
};
The `handleMouseMove()` function updates the scroll position of the container element while the user is dragging it.
To do this, it calculates how far the mouse has moved horizontally and vertically since the drag started, using the `clientX` and `clientY` properties of the event object. Then, it subtracts these distances from the initial values of the `scrollTop` and `scrollLeft` properties of the container element.
To give users a more intuitive and user-friendly experience, the function also calls a helper function called `updateCursor()`.
ts
const updateCursor = (ele) => {
ele.style.cursor = 'grabbing';
ele.style.userSelect = 'none';
};
When the user starts dragging the scrollable container, the `updateCursor()` function sets the cursor to `grabbing` and disables text selection using CSS. This provides visual feedback to the user that they are currently dragging an element.
Similarly, it's important to remember to reset the cursor when the user releases the mouse.
ts
const resetCursor = (ele) => {
ele.style.cursor = 'grab';
ele.style.removeProperty('user-select');
};
Why not give it a try in the demo below? Simply drag any element inside the scrollable container and move it around. You'll see that the container automatically scrolls to the target position.

Creating a custom hook for drag-to-scroll functionality

The drag-to-scroll feature is useful in many real-life situations, so let's package it up and reuse it across multiple components. In React, we can achieve this by creating custom hooks. Custom hooks save time, help us write cleaner, more organized code, and allow us to take complex functionality and turn it into simple hooks that can be shared with other developers or reused in future projects.
Creating a custom hook for drag-to-scroll functionality is similar to creating one for making an element draggable, which we covered previously. We won't go into the details of creating the custom hook here, but you can find it in our previous post.
Check out the demo below to see the drag-to-scroll functionality in action!

Conclusion

To sum up, drag-to-scroll is a nifty feature that can enhance the user experience across various contexts. It offers benefits such as more precise control over scrolling speed and direction, reduced hand and wrist fatigue, and easy use on touchscreens or other devices without a traditional mouse.
We've shown you how to implement drag-to-scroll functionality with React by creating a custom hook. With this hook, you can easily add drag-to-scroll functionality to any component that requires it.
Overall, drag-to-scroll is gaining popularity and will likely continue to be adopted by more websites and applications in the future. As designers and developers, it's essential to stay up-to-date with these trends and consider integrating them into our work where appropriate.

See also

If you found this post helpful, please consider giving the repository a star on GitHub or sharing the post on your favorite social networks 😍. Your support would mean a lot to me!

Questions? 🙋

Do you have any questions about front-end development? If so, feel free to create a new issue on GitHub using the button below. I'm happy to help with any topic you'd like to learn more about, even beyond what's covered in this post.
While I have a long list of upcoming topics, I'm always eager to prioritize your questions and ideas for future content. Let's learn and grow together! Sharing knowledge is the best way to elevate ourselves 🥷.
Ask me questions

Recent posts ⚡

Newsletter 🔔

If you're into front-end technologies and you want to see more of the content I'm creating, then you might want to consider subscribing to my newsletter.
By subscribing, you'll be the first to know about new articles, products, and exclusive promotions.
Don't worry, I won't spam you. And if you ever change your mind, you can unsubscribe at any time.
Phước Nguyễn