Create an animated underline for two CSS tabs

I'm interested in learning how to create a similar animation using JS, jQuery, or Vue Transitions with Bootstrap. I want to achieve the effect of moving the line under TAB1 to the right when TAB2 is clicked. Can anyone guide me on animating the div smoothly from left to right and vice versa?

https://i.sstatic.net/NK8zF.png This is the code I have:

<div class="col-md-12">
  <div class="col-md-6">
        <p>TAB1</p>
  </div>
  <div class="col-md-6">
        <p>TAB2</p>
  </div>
</div>
<div class="col-md-6">
<span style="height:10px;background-color:"></span>
</div>

Answer №1

I have created two distinct versions.

The initial one is Vue-specific and includes a hover effect that causes the bar to move when changing tabs, returning to its original position if the tab was not clicked.

The second example is a basic JavaScript version that only moves the bar when a new tab is clicked.

/* Vue Section */
new Vue({
  el: '#app',
  computed: {
    lineStyles() {
      return this.lineTemp == null ? this.line : this.lineTemp;
    }
  },
  mounted() {
    const tab = document.getElementById('nav-tab-1');
    if (tab) {
      let styles = {
        left: tab.offsetLeft,
        width: tab.clientWidth
      }
      this.line = styles;
    }
  },
  data() {
    return {
      currentTab: 2,
      line: {
        left: 0,
        width: 69
      },
      lineTemp: null
    }
  },
  methods: {
    onTabClick(evt) {
      const tab = evt.target
      let styles = {
        left: tab.offsetLeft,
        width: tab.clientWidth
      }
      this.line = styles;
    },
    onTabMouseEnter(evt) {
      const tab = evt.target
      let styles = {
        left: tab.offsetLeft,
        width: tab.clientWidth
      }
      this.lineTemp = styles;
    },
    onTabMouseLeave() {
      this.lineTemp = null;
    }
  }
})


/* Plain JS section */
function onTabClick(evt) {
  setLineStyle(evt.target)
}

function setLineStyle(tab) {
  let line = document.querySelector('.tabs2 > .line')
  line.style.left = tab.offsetLeft + "px";
  line.style.width = tab.clientWidth + "px";
}

/* bind events on load */
window.onload = function() {
  const tabs = document.querySelectorAll('.tabs2 > .nav > .nav-item')
  tabs.forEach((tab, index) => {
    tab.onclick = onTabClick;
    
    if(index == 0) setLineStyle(tab);
  })
}
.line {
  position: absolute;
  bottom: 0;
  transition: left 0.5s ease-in-out, width 0.5s 0.10s;
  height: 10px;
  background-color: blue;
  left: 0;
  width: 69px;
  pointer-events: none;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.6.11/vue.js"></script>
<link href="https://unpkg.com/<a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="d8bab7b7acbbaeb8abadace88278332">[email protected]</a>/dist/css/bootstrap.min.css" rel="stylesheet" />

<!-- Vue Section -->
<div id="app">
  <h4>Vue Option with hover effect</h4>
  <div class="position-relative">
    <ul class="nav">
      <li v-for="i in 3" :id="`nav-tab-${i}`" class="nav-item" @click="onTabClick" @mouseenter="onTabMouseEnter" @mouseleave="onTabMouseLeave">
        <a class="nav-link" href="#">
          Tab {{ i }}
        </a>
      </li>
    </ul>
    <div class="line" :style="{ left: `${lineStyles.left}px`, width: `${lineStyles.width}px` }"></div>
  </div>
</div>
<hr />

<!-- Plain Javascript Section -->
<div>
  <h4>Plain javascript option with only click</h4>
  <div class="tabs2 position-relative">
    <ul class="nav">
      <li class="nav-item">
        <a class="nav-link" href="#">
          Tab 1
        </a>
      </li>
      <li class="nav-item">
        <a class="nav-link" href="#">
          Long tab 2
        </a>
      </li>
      <li class="nav-item">
        <a class="nav-link" href="#">
          Tab 3
        </a>
      </li>
    </ul>
    <div class="line"></div>
  </div>
</div>

Answer №2

Feel free to give this code a try. I'm confident it will provide you with the solution you're looking for, complete with some cool animation effects.

.tab p {
    display: inline-block;
    margin-right: 30px;
    position: relative;
    margin-bottom: 0;
    font-size: 30px;
}
    
.tab p:last-child {
    margin-right: 0px;
}
    
.tab p:before {
    content: '';
    position: absolute;
    left: 0;
    bottom: 0;
    width: 0;
    height: 5px;
    background-color: #8C076E;
    -webkit-transition: .5s;
    transition: .5s
}
    
.tab p:hover:before {
    width: 100%
}
<div class="col-md-12">
    <div class="tab">
        <p>TAB1</p>
        <p>TAB2</p>
    </div>
</div>

Answer №3

Here's a homemade solution that may come in handy. Instead of using bootstrap, I have provided a simple way to create a menu:

<!DOCTYPE html>
<html>
  <head>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <style>
      /* Styling for the menu container */
      .menuwrap {
        margin-top: 0px; 
        padding:0px; 
        background-color: white ; 
        overflow: auto; 
        white-space: nowrap; 
        width: 100%; 
        height: 75px;
      }

      .mylinec { 
        margin-left: 0px; 
        top: 0px; 
        position: relative; 
        background-color: blue; 
        height: 5px; 
        width:100px; 
      } 

      .mybutton {
        width:100px;
        background: transparent; 
        border:0px; 
        color: black; 
        padding: 10px 15px; 
        text-align: center; 
        text-decoration: none; 
        display: inline-block; 
        font-size: 13px; 
        margin: 0px 0px; 
        cursor: pointer; 
        -webkit-transition-duration: 0.4s; 
        transition-duration: 0.4s; 
      }
    </style>
  </head>
  <body style="text-align:left; margin: 0px;">
    <div class="menuwrap" id="menu">
      <input type="button" class="mybutton" id="tab1" value="Tab 1" onClick="req_divmleft(this.offsetLeft), animatethisline()"/>
      <input type="button" class="mybutton" id="tab2" value="Tab 2" onClick="req_divmleft(this.offsetLeft), animatethisline()"/>
      <input type="button" class="mybutton" id="tab3" value="Tab 3" onClick="req_divmleft(this.offsetLeft), animatethisline()"/>
  
      <div id="myline" class ="mylinec"></div>
    </div>

    <!-- You may need storage or variables for tracking click history -->

    <input type="text" class="" id="txtstart" value="0" />
    <input type="text" class="" id="txtdestination" value="0" />

    <script>
      // function to get offset left of clicked element
      function req_divmleft(clicked_offsetLeft) { 
        document.getElementById("txtdestination").value = clicked_offsetLeft; 
      }

      function animatethisline() {
        var getdlm = document.getElementById("txtstart");
        var getlm = document.getElementById("txtdestination");
        var leslide = document.getElementById("myline");

        leslide.animate([ 
          { transform: "translateX("+getdlm.value+"px)" }, 
          { transform: "translateX("+getlm.value+"px)" }
        ], { 
          duration: 1000, 
          easing: "ease-out",
          fill: "both"
        });

        setTimeout(getdelx, 100);

        function getdelx() {
          getdlm.value = getlm.value;
        }
      }
    </script>
  </body>
</html>

Answer №4

.navbar {
            list-style: none;
            margin: 50px auto;
            max-width: 720px;
            padding: 0;
            width: 100%;
        }

        .navbar-item {
            background: #fff;
            display: block;
            float: left;
            margin: 0;
            padding: 0;
            width: 20%;
            text-align: center;
        }

        .navbar-item:first-child {
            border-radius: 3px 0 0 3px;
        }

        .navbar-item:last-child {
            border-radius: 0 3px 3px 0;
        }

        .navbar-item.navbar-item-active a {
            color: #e80000;
        }

        .navbar-item a {
            color: #313131;
            display: block;
            padding-top: 20px;
            padding-bottom: 20px;
            text-decoration: none;
        }

        .navbar-item a:hover {
           ...
<!doctype html>
<html lang="en">

<head>
    <!-- Required meta tags -->
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>css animate border left to right - tabmenu</title>
</head>
<body>
    <ul class="navbar navbar-indicator">
        <li class="navbar-item"><a href="#">Menu 1</a></li>
        <li class="navbar-item"><a href="#">Menu 2</a></li>
        <li class="navbar-item navbar-item-active"><a href="#">Menu 3</a></li>
        <li class="navbar-item"><a href="#">Menu 4</a></li>
        <li class="navbar-item"><a href="#">Menu 5</a></li>
    </ul>
</body>

</html>

Answer №5

Here is a straightforward example that utilizes jQuery for reference.

$(document).on('click', '.slider-tabs .nav-link', function() {
  handleTabChange($(this));
});

handleTabChange($('.slider-tabs .active'));

function handleTabChange(tab) {
  var nav = tab.closest('.nav');
  $('.indicator', nav).css({
    width: tab.outerWidth(),
    left: tab.position().left
  });
  tab.siblings().removeClass('active');
  tab.addClass('active');
}
.slider-tabs.nav {
  position: relative;
  margin: 10px;
}

.slider-tabs.nav .nav-link.active {
  background: transparent !important;
  color: #8b076e;
}

.slider-tabs.nav .indicator {
  position: absolute;
  top: 100%;
  min-width: 0;
  width: 0;
  height: 5px;
  background: red;
  transition: left .3s, width .3s;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css" integrity="sha384-Vkoo8x4CGsO3+Hhxv8T/Q5PaXtkKtu6ug5TOeNV6gBiFeWPGFN9MuhOf23Q9Ifjh" crossorigin="anonymous">

<nav class="nav nav-pills slider-tabs">
  <a class="text-sm-center nav-link active" href="#">Active</a>
  <a class="text-sm-center nav-link" href="#">Products</a>
  <a class="text-sm-center nav-link" href="#">Services</a>
  <a class="text-sm-center nav-link" href="#">Link</a>
  <a class="text-sm-center nav-link disabled" href="#">Disabled</a>
  <div class="indicator"></div>
</nav>

Answer №6

To achieve this, you can utilize a div element with a style attribute of position:relative placed below both buttons. Then, create a child element within that div with your desired width and apply position:absolute. Simply adjust the left and right properties when hovering or focusing on the element, or through your preferred pseudo-class. I hope this explanation makes sense to you.

Answer №7

Utilizing this versatile cross-browser css library has allowed me to create a myriad of dynamic animations that add visual interest to my projects. I believe implementing this tool could be the solution you are seeking. (One suggestion is to place a div element outside of your main content and animate its bottom border for a smooth left to right movement) Give it a try and enhance your animations!

Check out animate.css here

Explore animate.css on github

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Ensuring the validation of multiple ValidationObservers in Vue

In my ChangeInfo screen, I have three components representing ValidationObservers. When all three are valid, I set the variable showModal = true to display a success notification modal. However, if any of the three components have an error, showModal remai ...

Aligning text in the middle of two divs within a header

Screenshot Is there a way to keep the h4 text centered between two divs? I want it to stay static regardless of screen resolution. Currently, the icon and form remain in place but the h4 text moves. How can I ensure that it stays in one spot? <!doctyp ...

The AJAX request is not functioning properly, even on a basic form like this one

Here is the current code I am testing. Due to some issues with AJAX calls, I am unable to make it work. Can someone please help me identify what might be wrong? test.php <html> <body> <form id="myform"> <input type="text" id="fname" ...

enhance the brilliance of elements at different intervals

My latest CSS animation project involves creating a stunning 'shine' effect on elements with a specific class. The shine effect itself is flawless, but having all elements shine simultaneously is making it look somewhat artificial. I've bee ...

Storing data values from a specific object key into an array in Vue: Step-by-step guide

Just dipping my toes into the world of Vue framework here. I managed to create a selectable table that stores data in an object. I want this function to run in the background, so I figured it should be in the computed section. The object structure is as fo ...

Center both vertically and horizontally in Bootstrap UI Modal

I'm attempting to create a Bootstrap UI Modal that is centered both vertically and horizontally. While I found this solution that successfully centers the modal vertically, it loses its horizontal centering when applied to my own template with 800px ...

What is the best way to generate two <div> elements, with one of them being centrally fixed?

When it comes to CSS, I must admit that it's not my strong suit. My current challenge is to create two boxes or divs where one remains fixed in the center of the page, while the other adapts and positions itself right next to the first one. Essentiall ...

What is the importance of setting height: 0 when flex-basis: 0 is already defined in CSS3 flexbox?

I am currently working on creating a content reader with a fixed size and title that remains visible while scrolling. I attempted to achieve this using CSS3 flexbox with the following structure: .flex { display: flex; flex-direction: column; ...

Unlocking the secret to accessing state in a JavaScript file using VUEX

Can anyone help me figure out why I can't access the currentUser property from the state in my vuex store's user.js file? I'm trying to use it in auth.js, but when I write: store.state.currentUser.uid === ... This is the error message I&apo ...

How to prevent CSS styles from being overridden by SASS in a stylesheet

When working with a Sass file, I encountered an issue with the way styles were being output. The original style in the Sass file looked like this: .style{ width: calc(100%); } However, when compiled to its corresponding CSS file, the output was: .style{ ...

tips for repurposing a jquery function

Similar Question: JQuery: Issue with $(window).resize() not triggering on page load In my jQuery code snippet below, I am trying to make a function work both on window resize and page load without duplicating the code. The current implementation works ...

Guide on inserting a new column into an array of objects in Vue

Below is the fetch method I have defined to retrieve recordings from the database. However, I need assistance in adding a new column to each record specifically for frontend purposes. Can someone help me with this problem? <script> export default { ...

Having trouble integrating a Bootstrap-based ecommerce theme into an Angular8 project?

I have a bootstrap-based ecommerce theme with all the necessary files. The HTML theme is loading perfectly. Check out the official theme page I am integrating into my angular8 project: Theme Page Link I have created a repository for my angular8 project ...

choosing a section within a table cell

This seems like a simple task, but I'm encountering some difficulties $("#info-table tbody tr").each(function(){ $(this).find(".label").addClass("black"); }); .black{ font-weight:bold; } <script src="https://ajax.googleapis.com/ajax/libs/j ...

Display a div based on user selection using Ajax and Laravel

There are two div elements on the index page containing a datatable. By default, these two divs should be hidden. When an option is selected from the dropdown menu, the corresponding div should be displayed. The webpage is designed for searching within a ...

How to Create a Custom Callback Function for jQuery's .html

I am currently working with the following code: $.ajax({ type: 'GET', url: 'index.php?route=checkout/onepagecheckout/getpaypaldata', dataType: 'json', success: function(json) { ...

Error in syntax for jQuery's .find() method

Is there a mistake that's preventing the results from showing up? When I delete the code between the "FROM HERE" and "TO HERE" comments, everything seems to work fine (at least it shows up on screen). I have a feeling that the issue lies with the .fi ...

Vuetify's Handy Helper Classes

Hey everyone, I'm working on a vuetify project and I need to convert inline styles to utility classes (if possible) font-size: 24px; font-weight :600 I checked the documentation and noticed that it only provides options for setting size and weight wi ...

Exploring the intersection of Nuxt and TypeScript with the powerful `defineProps`

I am currently utilizing the Nuxt 3 / Vue 3 defineProps in conjunction with TypeScript and I am looking to automatically deduce prop types from TypeScript types. import { User } from '~/types/types' const props = defineProps({ users: { typ ...

Incorporate database table data into a Vue component form within a modal

Within my Laravel 5.3 project, I have implemented a modal component that pops up when a user clicks on an edit button to modify data. The modal window appears using the following code: <a href="#" class="btn btn-sm btn-info" data-toggle="m ...