Matt Angelosanto
Posted on December 8, 2022
Written by Emmanuel Odioko✏️
Many of us have probably gotten sucked into particularly captivating videos and posts on social media and don’t want to stop scrolling. When the media algorithm starts recommending less fascinating posts, your best option is likely to swipe to pull down the screen, which initiates a gesture that retrieves new suggested data or posts. If this doesn't capture your attention, you may repeat the process in some cases until the algorithm meets your satisfaction.
The gesture that initiates the retrieval of your next entertaining post is pull-to-refresh, which we will be discussing throughout the article.
Jump ahead:
- What is pull-to-refresh?
- What is overscroll behavior?
- Project overview
- Setting up our development area
- Implementing pull-to-refresh with React
- Styling with Tailwind CSS and writing logic
What is pull-to-refresh?
When the user drags from the top of the page, a loader should appear and should only be visible a bit below the top screen at this point. This entire action indicates to the user that new data is being retrieved.
When the user lets go of the drag triggered by their touch, the loader disappears and at the same time, new data should appear in the feed. At the end of the article, we should have something resembling the GIF below:
This action is carried out by dragging the page's top, and if there is a fresh update, it displays just immediately. Swiping or pulling makes it easy to retrieve info; this action was created by Loren Brichter in the Tweetie app in 2008, and it quickly became popular like anything excellent would.
Benefits of pull-to-refresh
Numerous smartphone apps use the pull-to-refresh gesture, which isn’t relatively new. They do so for the following reasons:
- This action reduces screen clutter and frees up space
- It provides room to view fresh data from the server
What is overscroll behavior?
The overscroll
CSS attribute determines what a browser does when it approaches either the bottom or the top edge of a scrolling zone. By default, the mobile browser tends to refresh a page when the top of the page is reached. In most cases, as a developer intending to implement a custom pull-to-refresh, this default behavior is not desirable, so you can employ the use of overscroll behaviors to override the browser's default pull-to-refresh gesture.
The code below is a general implementation of overscroll behavior from the MDN docs:
/* Keyword values */
overscroll-behavior: auto; /* default */
overscroll-behavior: contain;
overscroll-behavior: none;
/* Two values */
overscroll-behavior: auto contain;
/* Global values */
overscroll-behavior: inherit;
overscroll-behavior: initial;
overscroll-behavior: revert;
overscroll-behavior: revert-layer;
overscroll-behavior: unset;
To override the browser's inbuilt pull-to-refresh gesture in our quest to build our own, we'll use the overscroll-behavior: contain
; property. However, because we are aiming for the vertical edge of our scrolling region, the overscroll-behavior
in our case will be overscroll-behavior-y: contain
.
To use this, we will give the universal selector the overscroll-behavior-y: contain
property in our index.css
file, and that will be all:
* {
overscroll-behavior-y: contain;
}
Project overview
The reason we invest a lot of time in development is to ensure that our end users are completely satisfied. The pull-to-refresh gesture is intended to remain consistent throughout. Although there may be a demand for uniqueness in the animation and SVG style, the pull-to-refresh gesture itself should allow users to refresh whenever they feel the need to do so.
This article will focus on the implementation of custom pull-to-refresh gestures with React and overscroll behavior in CSS. The custom pull-to-refresh gesture should be something that appears at the top of our mobile browser and should only be visible when the user scrolls past the page borders.
Prerequisites
You must satisfy these requirements to follow this article properly:
- A copy of Node.js installed
- Fundamental knowledge of JavaScript syntax
- A working grasp of React and React Hooks
- Familiarity with the CLI and its commands
- Familiarity with Tailwind CSS
Setting up our development area
To get our development area up and running, open up your terminal and run the commands:
cd Desktop
npx create-react-app pull-to-refresh
npm start
To install Tailwind, follow these steps from the documentation. At this point, our application should be up and running, and you should clear the app.js
file for a clean start.
Implementing pull-to-refresh with React
To be able to implement the pull-to-refresh gesture, we must first acknowledge that this gesture will not be complete until there is first a touch; then a swipe downward, which is the drag; then an end to the drag. These actions are implemented with what we’ll refer to as EventListeners
, which will be called whenever an action is delivered to the browser window.
There are a couple of different EventListeners
, but only three will get the job done. Those three are:
-
touchstart
-
touchmove
-
touchend
touchstart
is only going to trigger when it senses the first touch. touchmove
will trigger when the touch is followed by a move or drag. Lastly, touchend
will trigger when there are no more touches registered in the window/on screen.
The useEffect
Hook will be responsible for adding and removing our event listeners. After importing useEffect()
, we can copy and paste the code below into our App.js
file:
// add and remove event listeners
useEffect(() => {
window.addEventListener("touchstart", pullStart);
window.addEventListener("touchmove", pull);
window.addEventListener("touchend", endPull);
return () => {
window.removeEventListener("touchstart", pullStart);
window.removeEventListener("touchmove", pull);
window.removeEventListener("touchend", endPull);
};
});
Since we are dealing with a pull gesture, there will inevitably be a touch to trigger the pull gesture. Therefore, our first concern is to have a state to store the user’s first touch towards the top of the screen.
Setting the startPoint
and pullChange
First things first, we will set the startPoint
, which is a state to hold the startPoint
’s screenY
value when the user’s initial touch is registered.
To create the state, we will need to first import useState()
and declare a state using the Hook.
/**
state to hold the start point
*/
const [startPoint, setStartPoint] = useState(0);
The next thing we'll do is create a state that will calculate how far the user has pulled the screen down — this will be the state that holds the change from the start point to the current point. The pull change will be equivalent to current point - start point
; hence, we will refer to this state as the pullChange
. The current point is where the user has been able to drag from the startPoint
.
/**
*
state to hold the change in the start point and current point
the pull change is equivalent to current point - start point
*/
const [pullChange, setPullChange] = useState();
We will then need a ref
to target the .Refresh-container
element in the DOM. We can implement this by first importing useRef()
from React and declaring it, as seen below:
/**
ref to target the `.refresh-container` element in the DOM
*/
const refreshCont = useRef(0);
Forcing a refresh
We also need a function to initialize loading and force the page to refresh within a second. This is the right time to create the function called initLoading
, which adds the .loading
class to the refresh container element to signify the loading state. We’ll also need a setTimeout
function, which will reload the page after 1000ms.
const initLoading = () => {
refreshCont.current.classList.add("loading");
setTimeout(() => {
window.location.reload();
}, 1000);
};
Now, we need a function to listen to the start point state and handle our touchstart
event. We will call this function pullStart
. This function gets the start point of the user touch gesture and the pull start, which only runs the first time you touch the screen — so the function is very useful for getting the start position.
const pullStart = (e) => {
const { screenY } = e.targetTouches[0];
setStartPoint(screenY);
};
As seen in the useEffect()
Hook above, the pull function will run when your finger moves on the screen. As you move from the top down to the middle of the screen, it calculates the difference between the current position and the starting position and saves it to our pullChange
function.
const pull = (e) => {
/**
* get the current user touch event data
*/
const touch = e.targetTouches[0];
/**
* get the touch position on the screen's Y axis
*/
const { screenY } = touch;
/**
* The length of the pull
*
* if the start touch position is lesser than the current touch position, calculate the difference, which gives the `pullLength`
*
* This tells us how much the user has pulled
*/
let pullLength = startPoint < screenY ? Math.abs(screenY - startPoint) : 0;
setPullChange(pullLength);
console.log({ screenY, startPoint, pullLength, pullChange });
};
As seen in useEffect
above, endPull
is a function that runs at the end of the touch gesture.
, endPull = (e) => {
setStartPoint(0);
setPullChange(0);
if (pullChange > 220) initLoading();
};
// function to reset the refresh button and start the loading by running the `initLoading()` function when `pullChange` has passed a certain threshold
Styling with Tailwind CSS and writing logic
Below, we styled the DOM with Tailwind. We have our ref in the div
container, our margin-top
will be assigned to pullChange()
, and it is divided by an arbitrary value, which makes the drag slower than normal.
The margin-top
is responsible for pushing our refresh container down. In our SVG, we also have our spinner. After styling our spinner, we see that the rotate
function goes along with pullChange
: the spinner rotates as it is being pulled, and by the time it is released, it will add the .loading
class.
//Before Tailwind
<div
ref={refreshCont}
className=""
style={{ marginTop: pullChange / 3.118 || "" }}
>
<div className="">
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
strokeWidth={1.5}
stroke="currentColor"
className=""
style={{ transform: `rotate(${pullChange}deg)` }}
>
<path
strokeLinecap="round"
strokeLinejoin="round"
d="M16.023 9.348h4.992v-.001M2.985 19.644v-4.992m0 0h4.992m-4.993 0l3.181 3.183a8.25 8.25 0 0013.803-3.7M4.031 9.865a8.25 8.25 0 0113.803-3.7l3.181 3.182m0-4.991v4.99"
/>
</svg>
</div>
</div>
<div className="">
<header className="">
<h1 className="">Welcome to my app!</h1>
<p>Pull down to refresh</p>
</header>
</div>
//After Tailwind
<div
ref={refreshCont}
className="refresh-container w-fit -mt-10 m-auto"
style={{ marginTop: pullChange / 3.118 || "" }}
>
<div className="refresh-icon p-2 rounded-full">
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
strokeWidth={1.5}
stroke="currentColor"
className="w-6 h-6"
style={{ transform: `rotate(${pullChange}deg)` }}
>
<path
strokeLinecap="round"
strokeLinejoin="round"
d="M16.023 9.348h4.992v-.001M2.985 19.644v-4.992m0 0h4.992m-4.993 0l3.181 3.183a8.25 8.25 0 0013.803-3.7M4.031 9.865a8.25 8.25 0 0113.803-3.7l3.181 3.182m0-4.991v4.99"
/>
</svg>
</div>
</div>
<div className="body flex justify-center items-center min-h-screen">
<header className="flex flex-col text-center">
<h1 className="text-4xl font-bold">Welcome to my app!</h1>
<p>Pull down to refresh</p>
</header>
</div>
If every step was carefully and consciously followed, we should arrive at a result that looks just like the GIF below:
Conclusion
The gesture is crucial because it adds to the long list of features that let users interact fully with an application. In this tutorial, we went over how to implement pull-to-refresh step by step; it was a quick, seamless process that resulted in a job well done. I'd like to take this opportunity to express my gratitude for sticking with me this far.
Cut through the noise of traditional React error reporting with LogRocket
LogRocket is a React analytics solution that shields you from the hundreds of false-positive errors alerts to just a few truly important items. LogRocket tells you the most impactful bugs and UX issues actually impacting users in your React applications.
LogRocket automatically aggregates client side errors, React error boundaries, Redux state, slow component load times, JS exceptions, frontend performance metrics, and user interactions. Then LogRocket uses machine learning to notify you of the most impactful problems affecting the most users and provides the context you need to fix it.
Focus on the React bugs that matter — try LogRocket today.
Posted on December 8, 2022
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.