You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何正确实现堆叠元素的上下移动动画?

解决堆叠元素快速操作时动画混乱问题

当前实现依赖margin-top结合CSS过渡实现元素移动,但快速操作时,元素的margin-top值处于过渡中间状态,此时读取并修改会导致计算偏差,叠加后动画出现混乱。以下是优化方案:

核心改进思路

改用transform: translateY()实现位移(性能更优,避免频繁重排),同时用自定义数据属性记录每个元素的实际偏移量,不依赖DOM样式读取,确保计算准确;过渡结束后移除过渡类,避免叠加;DOM操作放在动画完成后执行,保证视觉一致性。

修改后的完整代码

HTML

<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>

CSS

.previewDiv {
  width: 100px;
  height: 55px;
  font-size: 13px;
  border: 1px solid black;
  position: absolute;
  top: 0;
}

#previewDivContainer {
  position: relative;
}

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

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

.transitionUp {
  transition: transform 1s ease; 
}

JavaScript

const ITEM_HEIGHT = 55; // 元素固定高度,也可动态获取

// 初始化3个元素
for (var i = 0; i < 3; i++) {
  addMore(i);
}

// 初始化元素偏移量
$('#previewDivContainer').children().each(function(index) {
  const offset = index * ITEM_HEIGHT;
  $(this).data('offset', offset).css('transform', `translateY(${offset}px)`);
});

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

function moveDivToTop(elm) {
  const $elm = $(elm);
  const currentIndex = $elm.index();
  const currentOffset = $elm.data('offset') || 0;

  // 当前元素移到顶部,偏移量归0
  $elm.data('offset', 0).addClass('transitionUp').css('transform', 'translateY(0px)');
  $elm.one('transitionend', () => $elm.removeClass('transitionUp'));

  // 原顶部到当前元素前一个的元素,偏移量增加ITEM_HEIGHT
  $('#previewDivContainer').children().slice(0, currentIndex).each(function() {
    const $item = $(this);
    const newOffset = ($item.data('offset') || 0) + ITEM_HEIGHT;
    $item.data('offset', newOffset).addClass('transitionUp').css('transform', `translateY(${newOffset}px)`);
    $item.one('transitionend', () => $item.removeClass('transitionUp'));
  });

  // 动画完成后调整DOM顺序
  setTimeout(() => {
    $elm.prependTo($('#previewDivContainer'));
  }, 1000);
};

function removeDiv(elm) {
  const $elm = $(elm);
  const currentIndex = $elm.index();

  // 当前元素下方的元素,偏移量减少ITEM_HEIGHT
  $('#previewDivContainer').children().slice(currentIndex + 1).each(function() {
    const $item = $(this);
    const newOffset = ($item.data('offset') || 0) - ITEM_HEIGHT;
    $item.data('offset', newOffset).addClass('transitionUp').css('transform', `translateY(${newOffset}px)`);
    $item.one('transitionend', () => $item.removeClass('transitionUp'));
  });

  // 动画完成后移除元素
  setTimeout(() => {
    $elm.remove();
  }, 1000);
};

function addMore(index) {
  const newDiv = $('<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'))}),
    )
  ).data('offset', 0);

  $('#previewDivContainer').prepend(newDiv);

  // 已有元素偏移量增加ITEM_HEIGHT
  $('#previewDivContainer').children(':gt(0)').each(function() {
    const $item = $(this);
    const newOffset = ($item.data('offset') || 0) + ITEM_HEIGHT;
    $item.data('offset', newOffset).addClass('transitionUp').css('transform', `translateY(${newOffset}px)`);
    $item.one('transitionend', () => $item.removeClass('transitionUp'));
  });
};

关键改进点

  • 用数据属性记录偏移量:通过data-offset存储每个元素的实际位移值,避免从DOM读取过渡中的中间样式值,确保计算精准。
  • 使用transform替代margin-top:transform: translateY()不会触发页面重排,动画更流畅,且不会影响其他元素的布局计算。
  • 过渡结束清理类:每次动画完成后移除transitionUp类,防止后续操作时过渡效果叠加。
  • 延迟DOM操作:在动画完成后再调整DOM顺序或移除元素,避免视觉上的错位。

内容的提问来源于stack exchange,提问作者Carlos M

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.30 08:45:08