基于CSS和jQuery实现类Vue <transition-group>的列表洗牌动画
实现类似Vue的列表洗牌动画(CSS + jQuery版本)
嗨,我明白你想实现那种Vue风格的平滑洗牌动画的痛点——直接移动DOM只会让元素跳变,完全没有过渡效果。其实Vue背后用的是FLIP动画技术(First, Last, Invert, Play),核心是先记录元素的初始位置,计算目标位置的偏移,用transform过渡这个偏移后再更新DOM,视觉上就会有平滑的动画。针对你提到的两个元素的场景,我给你写个具体的实现方案:
核心思路拆解
- First:记录每个元素在洗牌前的初始位置
- Last:模拟洗牌后的DOM结构,记录每个元素的目标位置
- Invert:计算初始位置到目标位置的偏移量
- Play:用CSS过渡让元素从初始位置平滑移动到目标位置,最后重置DOM结构和样式
完整代码实现
HTML结构
<div class="list-container"> <div class="list-item">Item 1</div> <div class="list-item">Item 2</div> </div> <button id="shuffle-btn">Shuffle Items</button>
CSS样式
.list-container { position: relative; width: 200px; margin: 2rem; min-height: 80px; /* 避免容器高度跳变 */ } .list-item { padding: 1rem; margin: 0.5rem 0; background: #f0f0f0; border-radius: 4px; width: 100%; transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1); /* 用Vue常用的缓动曲线 */ position: absolute; /* 脱离文档流,方便自由移动 */ box-sizing: border-box; }
jQuery逻辑
$('#shuffle-btn').click(function() { const $container = $('.list-container'); const $items = $container.find('.list-item'); const itemCount = $items.length; // 1. 记录每个元素的初始位置(相对于容器) const initialPositions = $items.map(function() { const itemRect = this.getBoundingClientRect(); const containerRect = $container[0].getBoundingClientRect(); return { top: itemRect.top - containerRect.top, left: itemRect.left - containerRect.left }; }).get(); // 2. 模拟洗牌后的顺序(这里直接交换两个元素) const shuffledOrder = $items.toArray().reverse(); // 3. 先临时渲染洗牌后的结构,记录目标位置 $container.empty(); const targetPositions = []; shuffledOrder.forEach(item => { $container.append(item); const itemRect = item.getBoundingClientRect(); const containerRect = $container[0].getBoundingClientRect(); targetPositions.push({ top: itemRect.top - containerRect.top, left: itemRect.left - containerRect.left }); }); // 4. 把元素放回初始位置,准备执行过渡 $container.empty(); $items.each(function(index) { const $item = $(this); // 设置初始定位 $item.css({ top: `${initialPositions[index].top}px`, left: `${initialPositions[index].left}px` }); $container.append($item); // 强制浏览器重绘,否则过渡会直接跳到终点 void $item[0].offsetWidth; // 计算偏移量,设置transform触发过渡 const offsetTop = targetPositions[index].top - initialPositions[index].top; const offsetLeft = targetPositions[index].left - initialPositions[index].left; $item.css('transform', `translate(${offsetLeft}px, ${offsetTop}px)`); }); // 5. 过渡完成后,重置样式,让元素回到正常文档流 setTimeout(() => { $items.each(function() { $(this).css({ position: 'static', transform: 'none', top: 'auto', left: 'auto' }); }); }, 300); // 这个时间要和CSS的transition-duration一致 });
关键细节说明
- 绝对定位的必要性:让元素脱离文档流,这样移动时不会影响其他元素的布局,保证动画过程中容器高度稳定
- 强制重绘:
void $item[0].offsetWidth这一步是关键,它会迫使浏览器立即计算元素的布局,避免样式修改被合并,导致过渡不生效 - FLIP的简化应用:我们没有直接操作DOM的实时变化,而是通过"先记录位置→过渡→再更新DOM"的方式,模拟了Vue内部的过渡逻辑
- 缓动曲线:用了
cubic-bezier(0.4, 0, 0.2, 1),这是Vue默认的过渡缓动,能让动画更自然
内容的提问来源于stack exchange,提问作者Стас Пишевский
相关产品推荐
相关产品推荐

