javascripthtmlcssweb

How to move a list item up and down with an animation in Javascript?


I am new to JS. I want no have a list with items containing up and down buttons which can be used to change the list items order. If the user clicks on one of the buttons, the respective item should move up/down while its neighbour should move down/up to the place where the item has been before.

While its quite easy to do this basic behaveour (there are a lot of examples online), I also want to have a smooth animation so the items really "move" to their positions. Thats what I struggle with.

How can I do this?


Solution

  • You can use .offsetHeight to find the height of an element.

    This snippet looks at the height of the element and that of either its immediate predecessor or successor sibling element, depending on whether the move is to be up or down.

    It then moves the two elements visually (ie not within the DOM) first to their new positions using transform: translateY and sets the transition (to 1 second for this example).

    It could sense transformend, but as there are two transformations going on it was simpler to just set a timeout of 1 second and when that is up to remove the transition and transforms from the two elements and finally moves the element within the DOM.

    Note: various colors and dimensions are set in the snippet, just as examples. Obviously you will want to set the styles as you require them.

    In particular, the background colors have been given partial opacity so you can see them move one over the other.

    <style>
      li {
        display: flex;
      }
      
      li>div {
        width: 20vw;
        display: flex;
        justify-content: center;
        align-items: center;
      }
      
      li .div1 {
        height: 10vh;
        background: rgba(255, 0, 0, 0.4);
      }
      
      li .div2 {
        height: 20vh;
        background: rgb(0, 255, 0, 0.4);
      }
      
      li .div3 {
        height: 15vh;
        background: rgb(0, 0, 255, 0.4);
      }
      
      li .div4 {
        height: 20vh;
        background: rgb(255, 255, 0, 0.4);
      }
      
      button {
        height: fit-content;
      }
    </style>
    <ul>
      <li>
        <div class="div1">1</div><button class="up">up</button><button class="down">down</button></li>
      <li>
        <div class="div2">2</div><button class="up">up</button><button class="down">down</button></li>
      <li>
        <div class="div3">3</div><button class="up">up</button><button class="down">down</button></li>
      <li>
        <div class="div4">4</div><button class="up">up</button><button class="down">down</button></li>
    
    </ul>
    <script>
      const ul = document.querySelector('ul');
      const lis = document.querySelectorAll('li');
      const ups = document.querySelectorAll('.up');
      const downs = document.querySelectorAll('.down');
    
      let currentlyMovingUp = null;
    
      function goUp(el) {
        function transitionEnded() {
          if (currentlyMovingUp != null) {
            const previousEl = currentlyMovingUp.previousElementSibling;
            currentlyMovingUp.style.transform = '';
            previousEl.style.transform = '';
            currentlyMovingUp.style.transition = '';
            previousEl.style.transition = '';
            ul.insertBefore(currentlyMovingUp, previousEl);
            currentlyMovingUp = null;
          }
        }
        const previousEl = el.previousElementSibling;
        if (previousEl != null && currentlyMovingUp == null) {
          el.style.transform = 'translateY(-' + previousEl.offsetHeight + 'px)';
          el.style.transition = 'transform 1s linear';
          previousEl.style.transform = 'translateY(' + el.offsetHeight + 'px)';
          previousEl.style.transition = 'transform 1s linear';
          currentlyMovingUp = el;
          setTimeout(transitionEnded, 1000);
        }
      }
    
      function goDown(el) {
        if (el.nextElementSibling != null) goUp(el.nextElementSibling);
      }
    
      for (let i = 0; i < lis.length; i++) {
        ups[i].addEventListener('click', function() {
          goUp(lis[i]);
        });
        downs[i].addEventListener('click', function() {
          goDown(lis[i]);
        })
      }
    </script>