javascripthtmlcss

Moving two rectangles with transition


Using the following code, I can move two rectangles together with a transition, but after moving the two rectangles together, one of the rectangles moves from bottom to top again.

var rectangles = document.getElementsByClassName("rectangle");

for (var i = 0; i < rectangles.length; i++) {
  var rectangle = rectangles[i];
  var upButton = rectangle.getElementsByClassName("up")[0];
  var downButton = rectangle.getElementsByClassName("down")[0];

  upButton.addEventListener("click", function() {
    moveFile(this.parentNode, "up");
  });

  downButton.addEventListener("click", function() {
    moveFile(this.parentNode, "down");
  });
}

function moveFile(currentRectangle, direction) {
  var siblingRectangle = direction === "up" ? currentRectangle.previousElementSibling : currentRectangle.nextElementSibling;

  if (siblingRectangle) {
    currentRectangle.classList.add(direction === "up" ? "move-up" : "move-down");
    siblingRectangle.classList.add(direction === "up" ? "move-down" : "move-up");

    setTimeout(function() {
      if (direction === "up") {
        currentRectangle.parentNode.insertBefore(currentRectangle, siblingRectangle);
      } else {
        currentRectangle.parentNode.insertBefore(siblingRectangle, currentRectangle);
      }
      currentRectangle.classList.remove(direction === "up" ? "move-up" : "move-down");
      siblingRectangle.classList.remove(direction === "up" ? "move-down" : "move-up");
    }, 1000);
  }
}
.container {
  display: flex;
  flex-direction: column;
  align-items: center;
  height: 400px;
}

.rectangle {
  width: 150px;
  height: 100px;
  background-color: #ccc;
  border: solid rgb(255, 126, 126);
  margin: 10px;
  text-align: center;
  font-size: 18px;
  position: relative;
  transition: transform 0.5s ease-in-out;
}

.move-up {
  transform: translateY(-120px);
}

.move-down {
  transform: translateY(120px);
}

.button {
  width: 100px;
  height: 30px;
  background-color: #ddd;
  border: none;
  margin: 5px;
  cursor: pointer;
}
<div class="container">
  <div class="rectangle">
    <div>1</div>
    <button class="button up">Up</button>
    <button class="button down">Down</button>
  </div>
  <div class="rectangle">
    <div>2</div>
    <button class="button up">Up</button>
    <button class="button down">Down</button>
  </div>
  <div class="rectangle">
    <div>3</div>
    <button class="button up">Up</button>
    <button class="button down">Down</button>
  </div>
</div>

You can test the above code. As you will see, after the rectangles are moved, one of the rectangles moves up again. How to remove this extra movement?


Solution

  • const container = document.querySelector(".container");
    container.onclick = function(event) {
      const targetElement = event.target;
      const isButtonElement = targetElement.matches('button');
      const isUpAction = targetElement.classList.contains('up');
      const isDownAction = targetElement.classList.contains('down');
    
      if (isButtonElement) {
        const isFirstElement = !targetElement.parentNode.previousElementSibling;
        const isLastElement = !targetElement.parentNode.nextElementSibling;
    
        // ignore edge cases
        if ((isUpAction && isFirstElement) || (isDownAction && isLastElement)) {
          return;
        }
    
        if (isUpAction) {
          const elementToMove = targetElement.parentNode;
          const elementToReplace = elementToMove.previousElementSibling;
          // listen to animation finish ONCE to move elements
          elementToMove.addEventListener("transitionend", onTransitionFinished(
            'up',
            elementToMove,
            elementToReplace
          ), {
            once: true
          });
    
          // move target up and sibling down
          elementToMove.classList.add('move-up');
          elementToReplace.classList.add('move-down');
        }
    
        if (isDownAction) {
          const elementToMove = targetElement.parentNode;
          const elementToReplace = elementToMove.nextElementSibling;
          // listen to animation finish ONCE to move elements
          elementToMove.addEventListener("transitionend", onTransitionFinished(
            'down',
            elementToMove,
            elementToReplace
          ), {
            once: true
          });
    
          // move target down and sibling up
          elementToMove.classList.add('move-down');
          elementToReplace.classList.add('move-up');
        }
      }
    };
    
    function onTransitionFinished(dir, elementToMove, elementToReplace) {
      return function(event) {
        // clean up all transition classes
        document
          .querySelectorAll('.rectangle')
          .forEach((rectangle) => {
            rectangle.classList.remove('move-up');
            rectangle.classList.remove('move-down');
          });
    
        // insert in the right place based on direction after transition finished
        if (dir === 'up') {
          elementToReplace.parentNode.insertBefore(elementToMove, elementToReplace);
        } else {
          elementToMove.parentNode.insertBefore(elementToReplace, elementToMove);
        }
      };
    }
    .container {
      display: flex;
      flex-direction: column;
      align-items: center;
      height: 400px;
      --rectangle-height: 100px;
      --rectangle-border: 1px;
      --rectangle-margin: 10px;
      --travel-distance: calc(var(--rectangle-height) + 2*var(--rectangle-border) + 2*var(--rectangle-margin));
    }
    
    .rectangle {
      width: 150px;
      height: var(--rectangle-height);
      background-color: #ccc;
      border: var(--rectangle-border) solid rgb(255, 126, 126);
      margin: var(--rectangle-margin);
      text-align: center;
      font-size: 18px;
      position: relative;
    }
    
    .move-up {
      transition: transform 0.5s ease-in-out;
      /* MOVED THIS HERE! */
      transform: translateY(calc(-1*var(--travel-distance)));
    }
    
    .move-down {
      transition: transform 0.5s ease-in-out;
      /* AND HERE! */
      transform: translateY(var(--travel-distance));
    }
    
    .button {
      width: 100px;
      height: 30px;
      background-color: #ddd;
      border: none;
      margin: 5px;
      cursor: pointer;
    }
    <div class="container">
      <div class="rectangle">
        <div>1</div>
        <button class="button up">Up</button>
        <button class="button down">Down</button>
      </div>
      <div class="rectangle">
        <div>2</div>
        <button class="button up">Up</button>
        <button class="button down">Down</button>
      </div>
      <div class="rectangle">
        <div>3</div>
        <button class="button up">Up</button>
        <button class="button down">Down</button>
      </div>
    </div>