How To Make Your Pokémon API Project Interactive

by kleamerkuri
Pokemon deck responsive and interactive project.

Let’s make the Pokémon API project interactive using swipe navigation, mobile responsiveness, and a loading animation.

Why?

Because DM said the futuristic Pokémon deck was “freakin’ cool” but extremely disappointing in that it “sucked” on mobile and took “too damn long” to load 🙄

I value the quality of work – any type of work – so you can imagine this didn’t go down well with me.

That Pokémon deck project post was meant to be simple since its focus is on how to use the JavaScript fetch API.

However, DM does have a teeny point in that simple doesn’t mean less of a user experience 💡

I admit that admitting he’s kinda correct is hard. Yet, it’s the reason why I’m here showing you how to take the “freakin’ cool” futuristic Pokémon deck and transform it into something freakin’ amazing!

Hey!
Real quick: Whereas the original post focuses heavily on how to use JavaScripts’s fetch, this one aims to show how to enhance the Pokémon deck that the first one builds. So definitely check out the first post for how to setup the markup and add functionality. See CodePen for demo.

Our objective

At the end of this post, we’ll have an enhanced version of the existing futuristic Pokémon deck.

We’ll be adding:

  • a loading animation for the fetch action
  • the logic for a responsive mobile display

The loading animation addresses the wait time necessary for the fetch functions to get all original Pokémon and build cards for each one.

Currently, there’s no indicator to inform a user of the action that’s taking place. All they see is an empty screen and, quite frankly, don’t know what the heck is going on.

Is the app broken? Is it working? What’s happening?

Terrible user experience and all 😒

Not that it gets any better should the user jump to mobile.

On smaller screens, the deck’s display is skewed and we lose the arrows. Without the arrows, we’re forever stuck staring at Bulbasaur’s annoyingly cute face.

This means we’ll need to introduce new navigation methods for screens and devices that don’t support our oversized arrows.

Without further adieu, let’s get enhancing!

Loading animation for fetch

I’ll start with adding a loading animation for the fetch action since it’s the easiest.

The loading animation needs to provide visual feedback to users so they know an action is processing and ongoing.

Fetching the Pokémon and building the cards takes time which causes a delay on the user side.

Let’s provide a message to inform the user of the fetch action.

// HTML
<div class="loading-animation">
    <p class="load-mssg"></p>
</div>

// CSS
.loading-animation {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translateX(-50%);
  color: #32C2F0;
  font-size: 1.02rem;
  display: none;
}


Create a loader function to execute on the fetch actions.

const loader = (isLoad = true, mssg = "Loading") => {
  if (!isLoad) {
    document.querySelector(".loading-animation").style.display = "none";
    return;
  } else {
    const loadMod = document.querySelector(".load-mssg");
    document.querySelector(".loading-animation").style.display = "block";
    loadMod.innerText = mssg;
  }
};


The loader function checks the loading state of the fetch action to appropriately set the display of the loading animation and the user message.

When executed on fetch action, loader looks like this:

const fetchPokeData = async (pokemon) => {
  loader((isLoad = true), (mssg = "Fetching Pokémon"));
  const response = await fetch(pokemon.url);
  const data = response.json();
  loader((isLoad = false));
  return data;
};


I’m selectively applying loader to fetchPokeData because it’s the longest wait we get.

Plus, since we execute fetchPokeData in a loop, it gives us a nice futuristic-type of flashing message that fits the theme perfectly 😁

Talk about animation without effort.

Tip 💫
Should you wish to create some cool custom loading animation, place the loader function before the card creation loop!

Responsive mobile display

To make the Pokémon deck and its navigation responsive for mobile screens (I’m targeting screens 600px or smaller), we need to:

  1. collapse the side arrows and replace them with slider dots
  2. provide alternative navigation across all Pokémon cards in the deck

Let’s start with the slider dots indicators 👇

Slider dots indicator

Slider dots are usually a series of small dots placed horizontally or vertically with each dot representing a specific slide or page.

The active dot is usually highlighted or displayed differently to indicate the currently visible slide.

In our case, the number of dots is not going to enumerate the cards in the deck for obvious reasons (they’re 151 cards…scary number of dots).

Instead, they’ll act as a visual aid to suggest the navigation aspect for a user.

Start by constructing the slider dots HTML:

<div class="dot-slider">
    <span class="dot"></span>
    <span class="dot"></span>
    <span class="dot"></span>
    <span class="dot"></span>
    <span class="dot"></span>
</div>


Tip: Feel free to use as many dots as you’d like. Whether that’s increasing the number and making them smaller or decreasing and making them larger.

Continue by setting mobile-specific styles using a media query:

@media (max-width: 600px) {
  svg {
    display: none;
  }
  
  .dot-slider {
    display: block;
    position: absolute;
    top: calc(15% + 435px);
    left: 50%;
    transform: translateX(-50%);
    width: 100px;
    height: 50px;
    text-align: center;
  }
  
  .dot {
    display: inline-block;
    width: 10px;
    height: 10px;
    margin: 2px;
    border-radius: 50%;
    background: var(--hover-blue); 
  }
  
  .active {
    background: var(--bright-blue);
  }
  
}


On mobile screens, I’m replacing the display of the svg arrow icons with the slider dots. I, also, define the active state for the dots.

Adding slider dots on mobile screens for navigation.
Slider dots instead of arrows on mobile

Mobile swipe navigation

Our dots are looking wicked but they’re not doing much of anything 🙄

Without the arrows, we have no means to navigate across the cards in the deck.

Next, let’s make the dots actually slide using a combination of mouse and touch event listeners.

Interested in navigation? Checkout other ways to navigate with scroll by creating a one-page scroll effect!

1) Determine what elements move

This is rather simple since two things need to change: (1) the Pokémon card in display and (2) the active dot on the slider.

Further, I’m defining a few variables we’ll need for the navigation (don’t worry, you’ll see how we use them further below).

if (window.innerWidth < 600) {
    let scrollTimeout,
      isScrolling = false,
      startX = 0,
      endX = 0,
      dotIndex = 1;

	// rest of code...


2) Set the active dot

Lucky for us, we already have something similar that we’re using to navigate across the deck using the arrows on larger screens.

Simply refactor the logic and adjust the navigation of the slider dots.

const activeIndicator = (n) => {
      // n = dotIndex plus increment
      let dots = document.querySelectorAll(".dot");

      n > dots.length && (dotIndex = 1);
      n < 1 && (dotIndex = dots.length);

      for (let i = 0; i < dots.length; i++) {
        dots[i].classList.remove("active");
      }

      dots[dotIndex - 1].classList.add("active");
    };

    const nextIndicator = (inc) => {
      activeIndicator((dotIndex += inc));
    };

  • activeIndicator: Accepts a new dotIndex value that includes the increment and determines the active dot.
  • nextIndicator: Takes an increment value and executes activeIndicator with the new dot increment.

Again, refactored logic from the arrow navigation of the Pokémon deck so I’m not going into granular details (check out the previous post for ‘em).

The last step is to initialize the active dot on load like so: activeIndicator(dotIndex).

3) Define the swipe navigation action

The key to creating the swipe navigation is direction!

We need to figure out the direction of the swipe to know whether to show the next or previous card and active dot.

To figure out what direction a user swiped on a screen, we’ll take the difference between the moment they clicked/touched the screen and when they released it.

const touchStart = (event) => {
      if (event.type === "touchstart") {
        startX = event.changedTouches[0].clientX;
      } else if (event.type === "mousedown") {
        startX = event.clientX;
      }
    };

    const touchEnd = (event) => {
      if (event.type === "touchend") {
        endX = event.changedTouches[0].clientX;
      } else if (event.type === "mouseup") {
        endX = event.clientX;
      }

      handleSwipe();
    };

    const handleSwipe = () => {
      const swipeThreshold = 10; // Adjust the threshold as needed

      const swipeDistance = endX - startX;

      if (-swipeDistance > swipeThreshold) {
        // Swiped to the right (next card)
        nextCard(1);
        nextIndicator(1);
      } else if (-swipeDistance < swipeThreshold) {
        // Swiped to the left (previous card)
        nextCard(-1);
        nextIndicator(-1);
      }
    };


We execute handleSwipe inside of touchEnd to kick it off as soon as the swiped action completes.

handleSwipe does the direction calculation using a base threshold which is a value we adjust per preference.

Tip: The smaller swipeThreshold is, the faster the swipe occurs so keep that in mind!

Now, we’re not done just yet 👀

Let’s go a step further and consider the scroll on mouse wheel (similar to swiping on a laptop touchpad).

const handleWheel = (e) => {
      if (!isScrolling) {
        isScrolling = true;

        const deltaThreshold = +cardsContainer.offsetWidth / 2;

        let delta = e.deltaX * 100;
        if (delta > deltaThreshold) {
          // Scrolled to the right (next card)
          nextCard(1);
          nextIndicator(1);
        } else if (delta < -deltaThreshold) {
          // Scrolled to the left (previous card)
          nextCard(-1);
          nextIndicator(-1);
        }

        clearTimeout(scrollTimeout);

        scrollTimeout = setTimeout(() => {
          isScrolling = false;
        }, 400); // Adjust as needed
      }

      e.preventDefault();
    };


On scroll, we set a threshold that is half the width of the cardsContainer and use a built-in event property, deltaX, to determine the direction of the scroll.

It’s very similar to what we did with handleSwipe, the main difference being the use of setTimeout to clear the previous interval.

We use setTimeout to create a delay in the scrolling action to prevent continuous execution of the scroll logic.

Without the enforced delay, we can’t control a user’s scroll action making for erratic navigation 😵‍💫

Executing the swipe action

It’s lovely we’ve defined our various swipe actions but we really need to actually execute on the appropriate events.

Or, you know, nothing’s gonna happen 🥲

		cardsContainer.addEventListener("wheel", handleWheel, { passive: false });
    cardsContainer.addEventListener("mousedown", touchStart, {
      passive: false
    });
    cardsContainer.addEventListener("touchstart", touchStart, {
      passive: false
    });
    cardsContainer.addEventListener("mouseup", touchEnd, { passive: false });
    cardsContainer.addEventListener("touchend", touchEnd, { passive: false });


We’re focusing our action on the wheel, touchstart/mousedown, and touchend/mouseup events.

It’s important to get each of the events right because the corresponding callbacks use certain properties available on particular events (like deltaX or changedTouches).

Note the use of the third options parameter in the addEventListener methods.

By setting passive: false, we prevent the default browser scrolling behavior so we can handle the touch and scroll action with our code.

And we prevent errors like this:

Passive event error JavaScript event listener.

Note ‼️
Setting passive to false serves as an indication to the browser that it should prevent default behavior. To actually prevent default behavior, you still need to call preventDefault() inside of the callback function!

For example, in CodePen, if I don’t call e.preventDefault() inside of handleWheel, my scroll enables the default browser arrows 👇

So make sure you use both passive: false and preventDefault() when necessary.

Wrapping up

That’s it, we’re done. One heck of a responsive and interactive futuristic Pokémon deck is ready.

As a little recap, in this post, we took the Pokémon deck built earlier that fetched various Pokémon info and transformed it using:

  • a crafty loading animation to indicate the fetch action
  • responsive logic for mobile compatibility
  • mouse and touch navigation

If you’re wanting a more in-depth focus on fetching data, definitely check the first part of this post. Otherwise, you can find all source code for your reference on GitHub.

Gotta catch ‘em all y’all 😉

Related Posts