如何通过动画实现两个Div的位置与尺寸互换?
解决方案:实现大小Div平滑互换并支持DOM移动
核心思路
- 用CSS
transition统一处理尺寸、位置的平滑过渡 - 点击触发时先将两个元素转为绝对定位,脱离正常流以精准控制位置
- 等待过渡动画完全结束后再交换DOM结构,避免动画中断
- 最后重置定位与行内样式,让元素回到正常流维持布局
修正后的代码实现
HTML(保持原有结构即可)
<div class="container"> <div class="full-size-card card">大卡片</div> <div class="inactive-sheets"> <div class="card">小卡片1</div> <div class="card">小卡片2</div> <div class="card">小卡片3</div> </div> </div>
CSS(关键修正与补充)
.container { position: relative; /* 作为绝对定位元素的参照容器 */ padding: 20px; } .card { transition: all 0.3s ease; /* 统一过渡动画参数 */ position: relative; /* 默认相对定位,维持正常流布局 */ border: 1px solid #ccc; padding: 10px; cursor: pointer; } .full-size-card { width: 400px; height: 200px; margin-bottom: 20px; } .inactive-sheets { display: flex; gap: 10px; } .inactive-sheets .card { width: 120px; height: 80px; } /* 过渡阶段临时绝对定位类 */ .card-transition { position: absolute !important; margin: 0 !important; }
jQuery 代码(修复定位逻辑)
$(function () { $('.inactive-sheets .card').on('click', function() { const $target = $(this); const $currentFull = $('.full-size-card'); const $container = $('.container'); // 1. 获取元素相对于容器的位置与尺寸(避免页面滚动影响) const containerRect = $container[0].getBoundingClientRect(); const currentRect = $currentFull[0].getBoundingClientRect(); const targetRect = $target[0].getBoundingClientRect(); const currentPos = { left: currentRect.left - containerRect.left, top: currentRect.top - containerRect.top, width: currentRect.width, height: currentRect.height }; const targetPos = { left: targetRect.left - containerRect.left, top: targetRect.top - containerRect.top, width: targetRect.width, height: targetRect.height }; // 2. 给元素添加绝对定位,脱离正常流 $currentFull.addClass('card-transition'); $target.addClass('card-transition'); // 3. 强制浏览器重绘,确保过渡动画触发 void $currentFull[0].offsetWidth; // 4. 设置目标位置与尺寸,启动过渡 $currentFull.css({ left: targetPos.left, top: targetPos.top, width: targetPos.width, height: targetPos.height }); $target.css({ left: currentPos.left, top: currentPos.top, width: currentPos.width, height: currentPos.height }); // 5. 过渡结束后交换DOM并重置样式 $currentFull.one('transitionend', function() { // 交换DOM位置:大卡片移到小卡片容器,目标卡片移到顶部 $currentFull.removeClass('full-size-card card-transition').appendTo('.inactive-sheets'); $target.removeClass('card-transition').addClass('full-size-card').prependTo('.container'); // 清除行内样式,让CSS规则重新生效 $currentFull.add($target).removeAttr('style'); }); }); });
关键细节解释
- 容器参照定位:基于容器的
getBoundingClientRect计算相对偏移,解决页面滚动导致的位置计算错误 - 强制重绘:
void $currentFull[0].offsetWidth触发浏览器重绘,避免CSS过渡直接跳转到目标状态 - 过渡后DOM交换:等动画完全结束后再移动DOM元素,确保过渡效果完整展示;移动后清除行内样式,让CSS规则接管正常布局
原生JS替代方案
如果无需依赖jQuery,逻辑完全一致,仅API替换为原生:
document.addEventListener('DOMContentLoaded', function() { const container = document.querySelector('.container'); const inactiveCards = document.querySelectorAll('.inactive-sheets .card'); inactiveCards.forEach(card => { card.addEventListener('click', function() { const targetCard = this; const currentFull = document.querySelector('.full-size-card'); const containerRect = container.getBoundingClientRect(); const currentRect = currentFull.getBoundingClientRect(); const targetRect = targetCard.getBoundingClientRect(); const currentPos = { left: currentRect.left - containerRect.left, top: currentRect.top - containerRect.top, width: currentRect.width, height: currentRect.height }; const targetPos = { left: targetRect.left - containerRect.left, top: targetRect.top - containerRect.top, width: targetRect.width, height: targetRect.height }; currentFull.classList.add('card-transition'); targetCard.classList.add('card-transition'); void currentFull.offsetWidth; currentFull.style.left = `${targetPos.left}px`; currentFull.style.top = `${targetPos.top}px`; currentFull.style.width = `${targetPos.width}px`; currentFull.style.height = `${targetPos.height}px`; targetCard.style.left = `${currentPos.left}px`; targetCard.style.top = `${currentPos.top}px`; targetCard.style.width = `${currentPos.width}px`; targetCard.style.height = `${currentPos.height}px`; currentFull.addEventListener('transitionend', function handler() { currentFull.classList.remove('full-size-card', 'card-transition'); targetCard.classList.remove('card-transition'); targetCard.classList.add('full-size-card'); document.querySelector('.inactive-sheets').appendChild(currentFull); container.prepend(targetCard); currentFull.removeAttribute('style'); targetCard.removeAttribute('style'); currentFull.removeEventListener('transitionend', handler); }, { once: true }); }); }); });
内容的提问来源于stack exchange,提问作者dinx
相关产品推荐
相关产品推荐

