DEV Community

andy
andy

Posted on

Building a pager component from scratch*

In this tutorial we'll go through the steps of building a pager component from "scratch" (not entirely accurate -- we'll use a few dependencies to help with handling gestures and animations later on). This first article will capture the core behaviour of the pager component before moving onto things like animations and gestures.

I originally implemented this in react-native, so an example of what the final product will (sort of) look like can be found here: https://github.com/ajsmth/react-native-pager-component#readme

Note: I know that components for this kind of thing already exist -- this was made for my own learning, and hopefully yours too.

Before getting into the code, let's think about what we want to do...

We want a component that, when given a certain index, moves the matching child index into focus. So we'll be thinking in terms of indexes -- when the activeIndex prop changes, we'll want to shift the focus to the child that matches that index:

onChange(activeIndex) -> updateTranslation(activeIndex)

We can achieve this by laying out all of our children horizontally inside a container view, like this:

const absoluteFill = {
  position: "absolute",
  left: 0,
  right: 0,
  bottom: 0,
  top: 0
};

function Pager({ children, activeIndex, size }) {
  return (
    <div
      style={{
        display: "flex",
        alignSelf: "center",

        position: "relative",
        width: size,
        height: size,
        border: "2px solid green"
      }}
    >
      <div
        style={{
          ...absoluteFill
          // we will translate this container view
        }}
      >
        {React.Children.map(children, (element, index) => (
          <PageView index={index} width={size}>
            {element}
          </PageView>
        ))}
      </div>
    </div>
  );
}

function PageView({ children, index }) {
  // position each page side-by-side based on it's index
  const position = `translateX(calc(${100 * index}%))`;

  return (
    <div
      style={{
        ...absoluteFill,
        transform: position,
        border: "thin solid red"
      }}
    >
      {children}
    </div>
  );
}

Now all we have to do is update the translation value of the inner container based on the activeIndex prop:

function Pager({ children, activeIndex, size }) {
  // the total offset of the container div -- based on activeIndex
  const translateX = `translateX(${activeIndex * -100}%)`;

  return (
    <div ...>
      <div
        style={{
          ...absoluteFill,
          transform: translateX
        }}
      >
        {React.Children.map(children, (element, index) => (
          <PageView index={index} width={size}>
            {element}
          </PageView>
        ))}
      </div>
    </div>
  );
}

The inner container now shifts its focus by computing its offset from the activeIndex prop. This is a pretty basic implementation so far, but hopefully you can see that it captures the core behaviour of what the final component will do.

The next steps will be to animate page transitions, and handle gestures to update the activeIndex based on user interaction. We'll also open up the component's API to make it fully controllable for use in other projects.

To see a full example visit https://codesandbox.io/s/modern-moon-o5etr

In the next article, we'll animate the transitions between pages:

https://dev.to/ajsmth/building-a-pager-component-from-scratch-part-2-557l

Top comments (0)