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

如何实现两个div盒子沿半圆弧路径交换位置的CSS动画

实现方案

核心思路

  • 基础布局用 Flex 实现多个 div 水平等距排列
  • 交换动画核心是让两个元素分别沿上下两个半圆路径反向移动,避免碰撞
  • 动画结束后真实交换两个 DOM 元素的位置,复位动画状态方便下次触发

完整可运行代码

HTML 结构

<div class="container">
  <div class="box">1</div>
  <div class="box">2</div>
  <div class="box">3</div>
  <div class="box">4</div>
  <div class="box">5</div>
</div>
<button id="swapBtn">随机交换两个盒子</button>

CSS 样式

.container {
  display: flex;
  gap: 20px;
  margin: 100px auto;
  padding: 20px;
  width: fit-content;
  position: relative;
}
.box {
  width: 80px;
  height: 80px;
  background: #4e8cff;
  color: #fff;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 24px;
  border-radius: 8px;
  position: relative;
  offset-rotate: 0deg;
  transition: none;
}
/* 沿上半圆移动动画 */
.animate-top {
  animation: moveTop 1s ease-in-out forwards;
}
/* 沿下半圆移动动画 */
.animate-bottom {
  animation: moveBottom 1s ease-in-out forwards;
}
@keyframes moveTop {
  0% {
    transform: translate(0, 0);
  }
  50% {
    transform: translate(calc(var(--distance) / 2), calc(-1 * var(--radius)));
  }
  100% {
    transform: translate(var(--distance), 0);
  }
}
@keyframes moveBottom {
  0% {
    transform: translate(0, 0);
  }
  50% {
    transform: translate(calc(var(--distance) / -2), var(--radius));
  }
  100% {
    transform: translate(calc(var(--distance) * -1), 0);
  }
}
#swapBtn {
  display: block;
  margin: 0 auto;
  padding: 10px 20px;
  font-size: 16px;
  cursor: pointer;
}

JS 逻辑

const swapBtn = document.getElementById('swapBtn');
const container = document.querySelector('.container');
let boxes = Array.from(document.querySelectorAll('.box'));

swapBtn.addEventListener('click', () => {
  // 随机选取两个不重复的盒子
  const [idx1, idx2] = getTwoRandomIndex(boxes.length);
  let boxA = boxes[idx1];
  let boxB = boxes[idx2];
  // 保证boxA是位置更靠左的盒子
  if (boxA.offsetLeft > boxB.offsetLeft) {
    [boxA, boxB] = [boxB, boxA];
  }
  // 计算两个盒子的水平间距、半圆半径
  const distance = boxB.offsetLeft - boxA.offsetLeft;
  const radius = distance / 2;
  // 注入动画需要的CSS变量
  boxA.style.setProperty('--distance', `${distance}px`);
  boxA.style.setProperty('--radius', `${radius}px`);
  boxB.style.setProperty('--distance', `${distance}px`);
  boxB.style.setProperty('--radius', `${radius}px`);
  // 触发动画
  boxA.classList.add('animate-top');
  boxB.classList.add('animate-bottom');
  // 动画结束后交换DOM位置,复位样式
  setTimeout(() => {
    if (idx1 < idx2) {
      container.insertBefore(boxB, boxA);
    } else {
      container.insertBefore(boxA, boxB);
    }
    // 清除动画状态
    boxA.classList.remove('animate-top');
    boxB.classList.remove('animate-bottom');
    boxA.style.removeProperty('--distance');
    boxA.style.removeProperty('--radius');
    boxB.style.removeProperty('--distance');
    boxB.style.removeProperty('--radius');
    // 更新盒子数组顺序
    [boxes[idx1], boxes[idx2]] = [boxes[idx2], boxes[idx1]];
  }, 1000); // 和CSS动画时长保持一致
});

// 生成两个不重复的随机索引
function getTwoRandomIndex(max) {
  let idx1 = Math.floor(Math.random() * max);
  let idx2 = Math.floor(Math.random() * max);
  while (idx1 === idx2) {
    idx2 = Math.floor(Math.random() * max);
  }
  return [idx1, idx2];
}

关键说明

  • 采用transform组合实现半圆路径,兼容性比offset-path方案更好,无需适配低版本浏览器
  • 动画时长、圆弧弧度、盒子样式都可以通过调整对应CSS参数自定义
  • 动画过程中所有偏移都是元素自身的相对位移,不会影响其他盒子的布局

内容的提问来源于stack exchange,提问作者Harsh Vardhan Singh

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.03 02:30:00