如何正确实现堆叠元素的上下移动动画?
解决堆叠元素快速操作时动画混乱问题
当前实现依赖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
相关产品推荐
相关产品推荐

