DEV Community

loading...
Cover image for How to replicate the Zelda BOTW interface with React, Tailwind and Framer-motion: Part 2

How to replicate the Zelda BOTW interface with React, Tailwind and Framer-motion: Part 2

Florent Lagrede
Front-end Developper from πŸ‡«πŸ‡· Working mostly with React, NextJS, Tailwind, Typescript. Making GameUI tutorials on http://gameuionweb.com react-three-fiber and threejs enthusiast ✨
・7 min read

Introduction and notes on first part

Welcome back for the second part of the Zelda series.
I hope that you manage to complete the first part without much trouble. If not or if you've missed the first part I created a checkpoint branch with all the steps completed.
Also there were some errors in the solutions provided in part 1 that should all be fixed now.

After the first part, our interface should looks like this:

Checkpoint part1

In this second part we'll focus first on the right column of our layout and then we're going to handle items pagination and ways to navigate between them.

For providing solutions I decided to stop using gist files, they were hard to maintain and even for reading it was not convenient either to jump between files for the same solution.
Instead I created a checkpoint commit at each step of the second part, it should make reading easier hopefully.

Displaying additional items information

Right column layout

We're going to start by creating the base blocks of the right column.

Layout

We need an ItemInformation component that should be displayed at the bottom of the column. For now the component should be an empty box, but its width should match the ItemGrid width when we are in column display.

Once the ItemInformation component is set, we'll need to position the Link image below and hide it for breakpoints below XL. We can use an absolute here for the positioning.

Notes

The color for the ItemInformation background is missing in the starter tailwind configuration file. We can add it now:

bgBlackTransparent: "rgba(0, 0, 0, 0.5)",

Solution

Items information

Now we're going to display more Item information. This step is mostly passing down props and implementing the design of the ItemInformation component.

Item information

We want to display the name, value, category and description of the item. The category will be displayed as an Icon. In the first part we've displayed only one category among the 3 available in the dataset but we'll handle all 3 cases here. We need to create a CategoryIcon component which displays the appropriate icon based on a type props for example.

But first we have to convert our SVGs (Armor, Shield, Sword) inside src/assets/items into React components. We will use SVGR for that.

On this step we will also add the calamity font since this is the first time we will display text. It is available in the font folder under assets. We have to move it to create-react-app public folder and add font-face declarations in a separate css file.

Notes

We can reuse the Armor icon for helm and greave category.

Ressources

Solution

Adding a component to animate text

Now we are going to animate the item description by adding a typewriter effect animation.

Windups

To achieve this effect we are going to use the windups library from @sgwil. The library is really well written, has a nice API and great documentation. We can even trigger effects, like sounds, during the animation. For this demo we are just going to use the base functionality of windups with the useWindupString hook provided for React.
This hook renders a lot of time so we should avoid using it at a high level in our app structure otherwise it would trigger a lot of unneeded render on children components and seriously lower the performance of our interface.
Instead we will create a TypeWriter component that we will use inside ItemInformation.

Notes

Have a look at the interactive guides to get a feeling of what you can do with windups or even the sources if you have some time they are really interesting.

Ressources

Solution

Adding pagination

Handling pagination logic

In the first part we create a getItems function to retrieve one category of items and fill the rest of the grid cells with an empty item object.

Now we have to tweak this function a bit to return all the categories of items by page.

The function should have the following ItemsPage return type


export enum ItemsMainCategoriesType {
  WEAPONS = "weapons",
  SHIELDS = "shields",
  ARMORS = "armors",
}

export type ItemsPage = {
  items: ItemType[];
  mainCategory: ItemsMainCategoriesType;
  page: number;
};

Enter fullscreen mode Exit fullscreen mode

Once that is done we need to create two states using useState react hook inside our App component:

  • One that should be initialized with the result of our getItems function
  • Another one to store the current page, that we initialize to 0 for now
  • Then we need to create our new items variable based on the active page

At the end of this step we won't be able to navigate between pages yet but we'll have the base logic ready.

Solution

Creating a component for page navigation

For the next part we're going to create the component to move between the groups of items, we can call it CategoriesMenu. We'll also need a component for each item inside that menu, we can call it CategoriesMenuItem.

Categories menu

The CategoriesMenuItem will have to display one of the 3 SVG react components (Weapon, Shield, Armor) and call the setPage from earlier on click. In the image, the first icon is selected and has different style applied.

Solution

Adding animated arrow to navigate

We are now going to provide a second way to navigate between the pages with arrows at the edge of the items grid component.

Navigation arrows

This time instead of calling setPage directly, we'll create a navigateToDirection function which should either take 1 or -1 to represent the direction and then call setPage with the appropriate value.

These arrows should:

  • call navigateToDirection
  • be animated on the scale property to create a zoom in/out effect with framer motion
  • be disabled if reaching either max/min page.

We'll also have to update our useState inside App to store both the page and the direction.

Notes

The arrow image is available under the asset folder under the name arrow-no-curve.png. We may have to move it to the public folder to use a static url directly.

We only have one arrow as an image, so we need to rotate the image to create the left arrow.

Solution

Handling keyboard to navigate between page

We are now going to provide a third way to navigate between the pages ! Triforce requirement, we have no choice.

Keyboard navigation

For this step we have to change the handleKeyPressed and goLeft / goRight functions we wrote in part 1.

  • handleKeyPressed: we need to call navigateToDirection inside ArrowLeft and ArrowRight event when we are at the edges of the ItemsGrid
  • goLeft / goRight: we loop over horizontal cells when we reach the edges of the ItemGrid.

Notes

We can optionally refactor the if/else inside the handleKeyPressed function by a switch block.

Solution

Adding animation on page change

For the last part we are going to add an animation to the page transition.

Page animation

The animation will be added to the ItemsGrid component. We want to slide the ItemsGrid from the right or from the left depending on the direction that we stored in our useState.
To create this animation we are going to use the variant api from framer-motion, it allows us to define the different states of our animation in an object. In this object we will have two states:

  • enter: this is the starting position, here we will slide the ItemsGrid to either -100 or 100 on the x axis and apply an opacity to 0.
  • center: here we will move the ItemsGrid back to the center and reveal the ItemsGrid at the same time with opacity to 1.

To sum up we are going to give our motion.div a variant object with two entries enter and center. Each entry will define values for x and opacity properties. It should look like this:

const variants = {
  enter: (direction: number) => {
    return {
      x: ??,
      opacity: ??,
    };
  },
  center: {
    x: ??,
    opacity: ??,
  },
};
Enter fullscreen mode Exit fullscreen mode

We'll also need to define the transition we want for these properties, we can use the values below:

      transition={{
        x: { type: "tween" },
        opacity: { duration: 0.2 },
      }}
Enter fullscreen mode Exit fullscreen mode

In the end our motion.div component inside ItemsGrid should have the following props:

<motion.div
    custom={direction}
    variants={variants}
    initial="enter"
    animate="center"
    transition={{...}}
/>
Enter fullscreen mode Exit fullscreen mode

Since there is no direction involved when we change pages using the CategoriesMenu we should probably just animate the opacity in this case.

Ressources

Solution

What's next

Congratulations for completing part 2 we've made a lot of progress on completing our demo.
In part 3 we will add the final touch to bring this interface to life !
Again feel free to share any feedback and I hope you had fun.

Discussion (5)

Collapse
jhoy1992 profile image
Jhonatan Ascari

Amazing job, I'm learning a lot! Thank you!

Collapse
trostcodes profile image
Alex Trost

Amazing!! Can’t wait to dive into this, Florent! Awesome job and thank you!

Collapse
offirmo profile image
Offirmo

Where is the demo?

Collapse
flagrede profile image
Collapse
offirmo profile image
Offirmo

πŸ”₯πŸ”₯πŸ”₯ amazing!

Forem Open with the Forem app