<template>
  <div class="h-screen flex flex-no-wrap items-center mx-20 relative">
    <div class="inline-flex">
      <ul
        class="relative z-20 uppercase font-extrabold list-none flex overflow-x-auto whitespace-no-wrap"
      >
        <li
          v-for="category in categories"
          :id="`category${category.name}`"
          :key="category.url"
          class="inline-flex mr-3 last:m-0 justify-center items-center text-center align-middle px-6 py-3 cursor-pointer rounded-full"
          :class="{
            'text-white': selectedCategory.name === category.name,
            'hover:bg-gray-200 text-blue-900':
              selectedCategory.name !== category.name
          }"
          @click="selectedCategoryChanged(category)"
        >
          <span>{{ category.name }}</span>
        </li>
      </ul>
      <span
        id="categoryBackground"
        role="presentation"
        class="transition-all duration-300 ease-in-out z-0 absolute rounded-full bg-red-700"
      />
    </div>
  </div>
</template>

<script>
import { ref, computed } from "vue";

export default {
  setup() {
    const categories = computed(() => {
      return [
        {
          name: "bulbasaur",
          url: "https://pokeapi.co/api/v2/pokemon/1/"
        },
        {
          name: "charmander",
          url: "https://pokeapi.co/api/v2/pokemon/4/"
        },
        {
          name: "squirtle",
          url: "https://pokeapi.co/api/v2/pokemon/7/"
        },
        {
          name: "jigglypuff",
          url: "https://pokeapi.co/api/v2/pokemon/39/"
        },
        {
          name: "vulpix",
          url: "https://pokeapi.co/api/v2/pokemon/37/"
        },
        {
          name: "meowth",
          url: "https://pokeapi.co/api/v2/pokemon/52/"
        },
        {
          name: "rapidash",
          url: "https://pokeapi.co/api/v2/pokemon/78/"
        },
        {
          name: "dewgong",
          url: "https://pokeapi.co/api/v2/pokemon/87/"
        },
        {
          name: "jolteon",
          url: "https://pokeapi.co/api/v2/pokemon/135/"
        },
        {
          name: "spearow",
          url: "https://pokeapi.co/api/v2/pokemon/21/"
        },
        {
          name: "ekans",
          url: "https://pokeapi.co/api/v2/pokemon/23/"
        },
        {
          name: "pikachu",
          url: "https://pokeapi.co/api/v2/pokemon/25/"
        },
        {
          name: "sandshrew",
          url: "https://pokeapi.co/api/v2/pokemon/27/"
        },
        {
          name: "clefairy",
          url: "https://pokeapi.co/api/v2/pokemon/35/"
        }
      ];
    });

    let selectedCategoryName = ref("bulbasaur");
    const selectedCategory = computed(() => {
      // Here, we're using nextTick() to check that Vue has had time to render the categories in the DOM
      Vue.nextTick(() => {
        updateCategoryBackground(
          categories.value.find(
            (cat) => cat.name === selectedCategoryName.value
          )
        );
      });
      return categories.value.find(
        (cat) => cat.name === selectedCategoryName.value
      );
    });

    const categoryBackground = computed(() =>
      document.querySelector(`#categoryBackground`)
    );

    let selectedCategoryElement = document.querySelector(
      `#category${selectedCategory.value.name}`
    );

    function selectedCategoryChanged(category) {
      // This function could also contain a store dispatch to hold details of the selected category
      selectedCategoryName.value = category.name;
      updateCategoryBackground(category);
    }

    function updateCategoryBackground(category) {
      selectedCategoryElement = document.querySelector(
        `#category${category.name}`
      );
      if (selectedCategoryElement && categoryBackground.value) {
        categoryBackground.value.style.width =
          selectedCategoryElement.scrollWidth + "px";
        categoryBackground.value.style.height =
          selectedCategoryElement.scrollHeight + "px";
        categoryBackground.value.style.left = selectedCategoryElement.offsetLeft + "px";
      }
    }

    return { categories, selectedCategory, selectedCategoryChanged };
  }
};
</script>

External CSS

  1. https://cdnjs.cloudflare.com/ajax/libs/tailwindcss/2.0.3/tailwind.min.css

External JavaScript

This Pen doesn't use any external JavaScript resources.