(String: {%- set hs_blog_post_body -%} {%- set in_blog_post_body = true -%} <span id="hs_cos_wrapper_post_body" class="hs_cos_wrapper hs_cos_wrapper_meta_field hs_cos_wrapper_type_rich_text" style="" data-hs-cos-general-type="meta_field" data-hs-cos-type="rich_text"> <div class="blog-post__lead h3"> <p><span>I recently had the opportunity to work on a fantastic research and development project at Netguru. The goal of the project (codename "Wordguru") was to create a card game that anyone can play with their friends.&nbsp;</span></p> </div></span>)

Create a Swipeable Card Stack with Vue.js and interact.js

Photo of Mateusz Rybczonek

Mateusz Rybczonek

Updated Feb 27, 2023 • 12 min read
splasg

I recently had the opportunity to work on a fantastic research and development project at Netguru. The goal of the project (codename "Wordguru") was to create a card game that anyone can play with their friends.

You can check the outcome here.

One element of the development process was to create an inteactive card stack.

The card stack had a set of requirements, including:

  • It should contain a few cards from the collection.

  • The first card should be interactive.

  • The user should be able to swipe the card in different directions that indicate an intent to accept, reject or skip the card.

This article will explain how to create that and make it interactive using Vue.js and interact .js.

I created an example for you to refer to as we go through the process of creating a component that is in charge of displaying that card stack and a second component that is responsible for rendering a single card and managing user interactions on it.

Step 1: Create the GameCard component in Vue

Let’s start by creating a component that will show a card, but without any interactions just yet. We’ll call this file `GameCard.vue`. In the component template, we’ll render a card wrapper and a keyword of a specific card. This is the file we’ll be working in throughout this post.

1

In the script section of the component, we receive the prop `card` that contains our card content, as well as the `isCurrent` prop that gives the card a distinct look when needed.

2

Step 2: Create the GameCardsStack component in Vue

Now that we have a single card, let's create our card stack.

This component will receive an array of cards and render the `GameCard` for each card. It's also going to mark the first card as the current card in the stack, so that special styling is applied to it.

3

Step 3: Add interactivity to the GameCard component

All our interactivity logic will live in the `GameCard` component. Let's start by allowing the user to drag the card. We will use interact to deal with dragging.

We’ll set the interactPosition initial values to 0 in the script section. These are the values that indicate a card’s order in the stack when it’s moved from its original position.

4

Next, we create a computed property that’s responsible for creating a transform value that’s applied to our card element.

5

In the mounted lifecycle hook, we make use of interact and its ` object that carries information about how far the element was dragged from its original position. Each time the user drags the card, we calculate a new position of the card and set it on the `interactPosition` property. That triggers our `transformString` computed property and sets a new value of `transform` on our card.

We use the interact `onend` hook that allows us to listen when the user releases the mouse and finishes the drag. At this point we will reset the position of our card and bring it back to its original position `{ x: 0, y: 0 }`.

We also need to make sure to remove the card element from the Interactable object before it gets destroyed. We do that in the beforeDestroy lifecycle hook by using interact(target).unset(). That removes all event listeners and makes interact.js completely forget about the target.

6

Now we have to bind transformString to the style attribute of our card.

7

With that done, we have created an interaction with our card - we can drag it around!

You may have noticed that the behavior isn’t very natural, specifically when we drag the card and release it. The card immediately returns to its original position, but it would be more natural if the card would go back to the initial position with an animation to smooth the transition.

That’s where transition comes into play! But adding it to our card introduces another issue: there’s a lag as the card follows the cursor because the transition is applied to the element at all times. We only want it applied when the drag ends. We can do that by binding one more class (isAnimating) to the component.

8

We can add and remove the animation class by changing the `isInteractAnimating` property.

The animation effect should be applied initially and we do that by setting our property in the data.

In the mounted hook where we initialize interact.js, we use one more interact hook (onstart) and change the value of `isInteractAnimating` to false so that the animation is disabled during the drag.

We’ll enable the animation again in the onend hook, and that will make our card animate smoothly to its original position when we release it from the drag.

We also need to update the transformString computed property and add a guard to recalculate and return a string only when we are dragging the card.

9

Now things are starting to look nice!

Our card stack is ready for the second set of interactions. We can drag the card around, but nothing is actually happening — the card always comes back to its original place, but there is no way to get to the second card.

This will change when we add logic that allows the user to accept and reject cards.

Step 4: Detect when the card is accepted, rejected, or skipped

The card has three types of interactions:

  • Accept card (on swipe right)

  • Reject card (on swipe left)

  • Skip card (on swipe down)

We need to find a place where we can detect if the card was dragged from its initial position. We also want to be sure that this check will happen only when we finish dragging the card so the interactions do not conflict with the animation we just finished.

We used that place earlier to smooth the transition during animation — it's the onend hook provided by the interact.draggable method.

Let's jump into the code.

First, we need to store our threshold values. Those values are the distances as the card is dragged from its original position and allow us to determine if the card should be accepted, rejected, or skipped. We use the X axis for right (accept) and left (reject), then use the Y axis for downward movement (skip).

We also set the coordinates of where we want to place the card after it gets accepted, rejected or skipped (coordinates out of the user's sight).

Since those values will not change, we will keep them in the static property of our component, which can be accessed with `this.$options.static.interactYThreshold`.

10

We need to check if any of our thresholds were met in our `onend` hook and then fire the appropriate method that happened. If no threshold is met, then we reset the card to its initial position.

11

OK, now we need to create a playCard method that’s responsible for handling those interactive actions.

Step 5: Establish the logic to accept, reject, and skip cards

We will create a method that accepts a parameter telling us the user’s intended action. Depending on that parameter, we will set the final position of the current card and emit the accept, reject, or skip event. Let's go step by step.

First, our playCard method will remove the card element from the Interactable object so that it stops tracking drag events. We do that by using interact(target).unset().

Secondly, we set the final position of the active card depending on the user's intention. That new position allows us to animate the card out and remove it from the user's view.

Next, we emit an event up to the parent component so we can deal with our cards (e.g. change the current card, load more cards, shuffle the cards, etc.). We want to follow the DDAU philosophy which states that a component should refrain from mutating data it doesn't own. Since our cards are passed down to our component, it should emit an event up to the place from where those cards come.

Lastly, we hide the card that was just played and add a timeout that allows the card to animate out of view.

12

And, there we go!

Summary

Let's recap what we just accomplished:

  • First, we created a component for a single card.

  • Second, we created another component that renders the cards in a stack.

  • Third, we implemented interact.js to enable interactive dragging.

  • Then we detected when the user wants when he takes an action with the current card.

  • Finally, we established the logic to handle those actions.

Phew, we covered a lot! Hopefully this gives you both a new trick in your toolbox as well as a hands-on use case for Vue.

Photo of Mateusz Rybczonek

More posts by this author

Mateusz Rybczonek

Even though Mateusz comes from an entirely different business environment (he worked at sea for 10...
How to build products fast?  We've just answered the question in our Digital Acceleration Editorial  Sign up to get access

We're Netguru!

At Netguru we specialize in designing, building, shipping and scaling beautiful, usable products with blazing-fast efficiency
Let's talk business!

Trusted by: