If you missed the first part of this lesson, it can be found here: https://dev.to/ajsmth/building-a-pager-component-from-scratch-4nlh
In this part, we'll add on to the pager component we've already created by handling animations and gestures to page between child views
The first thing we'll add is spring animations when the activeIndex
prop changes. In order to do so, let's bring in react-spring
and import some of it's functions:
yarn add react-spring
import {animated, useSpring} from 'react-spring'
Adding spring page transitions:
function Pager({ children, activeIndex, size }) {
// the total offset of the container div -- based on activeIndex
const translateX = `translateX(calc(${activeIndex * -100}%))`;
// this will animate changes in activeIndex between pages:
const animatedStyle = useSpring({ transform: translateX })
return (
<div ...>
{/* Update to animated.div */}
<animated.div
style={{
...absoluteFill,
// we will translate this container view to bring children into focus
...animatedStyle
}}
>
{React.Children.map(children, (element, index) => (
<PageView index={index} width={size}>
{element}
</PageView>
))}
</animated.div>
</div>
);
}
Now we have a spring animation that transitions between page changes
Next, we'll want to add support for handling swipe gestures. Again, we'll need a helper library
yarn add react-use-gesture
import {useDrag} from 'react-use-gesture'
This will help us track the drag value on the container view:
function Pager({ children, activeIndex, size }) {
// ...
const [{ dx }, set] = useSpring(() => ({ dx: 0 }));
const bind = useDrag(({ delta }) => {
const [dx] = delta;
set({ dx: dx });
});
const dragX = dx.interpolate(dx => `translateX(${dx}px)`);
{/* Change this container to animated.div */}
return (
<animated.div
{...bind()}
style={{
...
transform: dragX
}}
>
{...}
</animated.div>
);
}
You'll notice that after releasing, the translation value needs to reset in order to recenter the view. To achieve this let's update the useDrag() callback we just wrote:
const bind = useDrag(({ delta, last }) => {
const [dx] = delta;
set({ dx: dx });
// last means they've released from dragging
if (last) {
set({ dx: 0 });
}
});
Now the view re-centers after release.
So far, so good. What we need to consider now is how far the user has dragged, and if it's beyond a certain threshold, let's update the activeIndex so the next / previous view becomes focused.
The first thing we'll want to do is determine the threshold for when we should change -- in our case I'll set it to an arbitrary value of +/- 100:
const bind = useDrag(({ delta, last }) => {
const [dx] = delta;
set({ dx: dx });
// last means they've released from dragging
if (last) {
if (dx > DX_THRESHOLD) {
// transition to previous view
}
if (dx < -DX_THRESHOLD) {
// transition to next view
}
set({ dx: 0 });
}
});
Now we can use a callback prop to update the activeIndex
prop and properly focus the previous / next page:
// add an onChange prop:
function Pager({ ..., onChange }) {
...
// callback to onChange prop with new active value:
const bind = useDrag(({ delta, last }) => {
const [dx] = delta;
set({ dx: dx });
// last means they've released from dragging
if (last) {
if (dx > DX_THRESHOLD) {
// transition to previous view
onChange(activeIndex - 1)
}
if (dx < -DX_THRESHOLD) {
// transition to next view
onChange(activeIndex + 1)
}
set({ dx: 0 });
}
});
...
}
The last thing that we can do is to remove the border around our container view (if you still have it in your styles) and add an overflow: hidden
style, if you'd like to hide the unfocused views.
One last note -- in practice, we might want to compute the threshold as a percentage of the total width, or whatever value you think works best.
The source for this can be viewed here: https://codesandbox.io/s/intelligent-cache-5f366
We now have a serviceable pager component that handles gestures and animates page transitions
What we'll look at next is opening up the pager API to work as a controlled and an uncontrolled component, as well as a psuedo-virtualization for child views which might help with with your app's performance. We'll also take a look at some jank that occurs in our existing implementation
Top comments (0)