skip to Main Content

I am creating a set of stacked elements where one element could be added on top and the rest will move down. Likewise, an element can be delete and every element beneath this one will slide up. However, I am struggling a bit with the animation part.

I managed to set the animations and the divs slide up and down. However, when the class ‘transitionUp’ is added (this happens after adding/removing/moving a div), if I add/remove several rows quickly that messes up the animation. If I wait for the transition to finish between actions, then it works fine. I wonder if there is a better way of doing this.

enter image description here

I created a snippet to explain the problems I am having:

for (var i = 0; i < 3; i++) {
  addMore(i);
}

$('#addMoreBtn').click(function() {
  addMore($('#previewDivContainer').children().length)
});

function moveDivToTop(elm) {
  var index = $(elm).index();
  $('#previewDivContainer').children().slice(0, index).each(function () {
    moveDivDown(this);
  });
  $(elm).css('margin-top', "0px");
  $(elm).prependTo($('#previewDivContainer'))
};

function moveDivUp(index) {
  $('#previewDivContainer').children().slice(index, $('#previewDivContainer').children().length).each(function () {
   $(this).addClass("transitionUp");
   $(this).css('margin-top', parseFloat($(this).css('margin-top')) - $($('#previewDivContainer').children()[0]).height() + "px");
  });
};

function moveDivDown(elm) {
 $(elm).addClass('transitionUp');
 $(elm).css('margin-top', parseFloat($(elm).css('margin-top')) + $($('#previewDivContainer').children()[0]).height() + "px");
};

function removeDiv(elm) {
  var index = $(elm).index();
  console.log(index)
  moveDivUp(index);
  elm.remove();
};

function addMore(index) {
  $('#previewDivContainer').prepend(
    $('<div>', { class: "previewDiv"}).append(
      $('<span>', { class: "previewDivName", text: index}),
      $('<span>', { class: "previewDivMesage", text: `This is ${index}`}),
      $('<div>').append(
        $('<button>', {class: "btnUpd", text: "↑"}).click(function () {moveDivToTop($(this).closest('.previewDiv'))}), 
        $('<button>', {class: "btnDown", text: "X"}).click(function () {removeDiv($(this).closest('.previewDiv'))}),
      )
    )
  )
  moveManyDown(0);
};

function moveManyDown (index) {
  $('#previewDivContainer').children(`:gt(${index})`).each(function() {
    $(this).css('margin-top', parseFloat($(this).css('margin-top')) + $($('#previewDivContainer').children()[0]).height() + "px");
  })
}
.previewDiv {
  width: 100px;
  height: 55px;
  font-size: 13px;
  border: 1px solid black;
  position: absolute;
}

#previewDivContainer {
  position: relative;
}

.previewDivMesage {
  display: block;
  white-space: nowrap;
}

#addMoreBtn {
  margin-left: 200px;
  margin-top: 10px;
}

.transitionUp {
  transition: margin-top 1s; 
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.7.1/jquery.min.js"></script>
<div id="previewDivContainer">
</div>

<div id='addMore'>
  <button id='addMoreBtn'>
    Add More
  </button>
</div>

2

Answers


  1. Chosen as BEST ANSWER

    Thanks to @green-21 for pointing me in the right direction. I am only writting this to include a full snippet in case it can be useful to somebody reading this.

    enter image description here

    for (var i = 0; i < 3; i++) {
      addMore(i);
    }
    
    $('#addMoreBtn').click(function() {
      addMore($('#previewDivContainer').children().length)
    });
    
    function moveDivToTop(elm) {
      var index = $(elm).index();
      $(elm).css('margin-top', "0px");
      $(elm).prependTo($('#previewDivContainer'))
      $('#previewDivContainer').children().slice(1, index + 1).each(function() {
        moveDivDown(this);
      });
    }
    
    function moveDivUp(index) {
      $('#previewDivContainer').children().slice(index, $('#previewDivContainer').children().length).each(function() {
        $(this).addClass("transitionUp");
        $(this).css('margin-top', $(this).height() * ($(this).index() - 1) + "px");
      });
    }
    
    function moveDivDown(elm) {
      var index = $(elm).index();
      $(elm).addClass('transitionUp');
      console.log($(elm).height());
      $(elm).css('margin-top', $(elm).height() * (index) + "px");
    }
    
    function removeDiv(elm) {
      var index = $(elm).index();
      console.log(index)
      moveDivUp(index);
      elm.remove();
    }
    
    function addMore(index) {
      $('#previewDivContainer').prepend(
        $('<div>', {
          class: "previewDiv"
        }).append(
          $('<span>', {
            class: "previewDivName",
            text: index
          }),
          $('<span>', {
            class: "previewDivMesage",
            text: `This is ${index}`
          }),
          $('<div>').append(
            $('<button>', {
              class: "btnUpd",
              text: "↑"
            }).click(function() {
              moveDivToTop($(this).closest('.previewDiv'))
            }),
            $('<button>', {
              class: "btnDown",
              text: "X"
            }).click(function() {
              removeDiv($(this).closest('.previewDiv'))
            }),
          )
        )
      )
      moveManyDown(0);
    }
    
    function moveManyDown(index) {
      $('#previewDivContainer').children(`:gt(${index})`).each(function() {
        moveDivDown(this);
      })
    }
    .previewDiv {
      width: 100px;
      height: 55px;
      font-size: 13px;
      border: 1px solid black;
      position: absolute;
    }
    
    #previewDivContainer {
      position: relative;
    }
    
    .previewDivMesage {
      display: block;
      white-space: nowrap;
    }
    
    #addMoreBtn {
      margin-left: 200px;
      margin-top: 10px;
    }
    
    .transitionUp {
      transition: margin-top 1s;
    }
    <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.7.1/jquery.min.js"></script>
    <div id="previewDivContainer">
    </div>
    
    <div id='addMore'>
      <button id='addMoreBtn'>
        Add More
      </button>
    </div>


  2. The reason is that the margin values change during animantion.
    so, solution is to use fixed value when set margin.

    The margin is determined by the position of the element.

    div 0 -> margin-top: 0
    div 1 -> margin-top: (div's height)
    div 2 -> margin-top: (div's height) * 2
    

    rewrite like this,

    function moveDivToTop(elm) {
        var index = $(elm).index();
        // append index
        $('#previewDivContainer').children().slice(0, index).each(function (i) {
            moveDivDown(this, i);
        });
        $(elm).css('margin-top', "0px");
        $(elm).prependTo($('#previewDivContainer'))
    };
    
    function moveDivDown(elm, idx) {
        const height = $($('#previewDivContainer').children()[0]).height();
        $(elm).addClass('transitionUp');
        $(elm).css('margin-top', height *(idx+1) + "px"); // margin-top set fixed value
    };
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search