Search code examples
cssvue.jssasscss-animations

How to go about adding a sliding animation for a toggle button?


I have a toggle bar element I am working on and currently I have it working correctly to style the toggled button, but I want more animation.

The idea is to have the current active button slide to whichever button is newly active. So visually the only thing I would be adding to the current code is the slide animation.

How to do that exactly is where I'm getting stuck. Now the active element isn't just getting a background and some styles, but a floating/sliding box will be needed (I think)

I don't want anyone to implement it for me, but I am having trouble thinking of how to implement it. Do I do something with a pseudo elements?

How do I make sure that the white button background element is as wide as the word it needs to be behind?

This is more or less a general CSS question, but I am not sure how to even start this process.

If anyone has any advice or tricks on how to make this work or just pointing me in the right direction, I would greatly appreciate it! Cheers!

This is what I've built so far in CodeSandbox


Solution

  • You could 'create a gap' for the white block to travel in so it is behind the text but in front of the backgrounds by putting the background colors onto the elements using pseudo elements with lower z index.

    On click of a button you can look at the previously clicked button and work out how far the white block has to move to get to the current button, and also how its width has to change.

    In this snippet the 'white block' is in fact a pseudo element on the currently clicked button. This makes it easy to work out its final resting place which is directly under the currently clicked button.

    const buttons = document.querySelectorAll('button');
    
    function clicked(e) {
      const el = e.target;
      const prevEl = document.querySelector('button.animate');
      const x = (prevEl == null) ? '0%' : (prevEl.getBoundingClientRect().x - el.getBoundingClientRect().x) + 'px';
      const w2 = window.getComputedStyle(el).width;
      const w1 = (prevEl == null) ? 0 : window.getComputedStyle(prevEl).width;
    
      buttons.forEach(button => {
        button.classList.remove('animate');
      });
      el.style.setProperty('--x', x);
      el.style.setProperty('--w1', w1);
      el.style.setProperty('--w2', '100%');
      el.classList.add('animate');
    }
    buttons.forEach(button => {
        button.addEventListener('click', clicked);
      }
    
    );
    nav {
      display: inline-block;
      position: relative;
    }
    
    nav::before {
      content: '';
      position: absolute;
      background-color: gold;
      width: 100%;
      height: 100%;
      left: 0 top: 0;
      z-index: -3;
    }
    
    button {
      margin: 2vmin;
      background-color: transparent;
      position: relative;
    }
    
    button::before {
      content: '';
      position: absolute;
      width: 100%;
      height: 100%;
      top: 0;
      left: 0;
      background-color: brown;
      z-index: -2;
    }
    
    button.animate::after {
      content: '';
      position: absolute;
      background-color: white;
      width: 100%;
      height: 100%;
      animation-name: move;
      animation-duration: 3s;
      animation-fill-mode: forwards;
      top: 0;
      left: 0;
      z-index: -1;
    }
    
    @keyframes move {
      0% {
        transform: translateX(var(--x));
        width: var(--w1);
      }
      100% {
        transform: translateX(0);
        width: var(--w2);
      }
    }
    
    @keyframes animate {
      0% {}
      100% {
        background-color: white;
      }
    }
    <nav>
      <button>AAAA</button>
      <button>AA</button>
      <button>AAAAAA</button>
      <button>A</button>
    </nav>