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

如何用CSS实现圆形元素移动动画?解决越界问题

解决CSS动画实现圆形屏幕内移动不超出边界的问题

核心思路是用CSS变量动态存储圆形可移动的最大范围(窗口尺寸减去圆形自身尺寸),在CSS动画中通过这些变量结合随机值计算位置,同时用JS初始化和更新变量(适配窗口 resize),替代原来的JS定时修改位置逻辑。


修改后的完整代码

HTML(仅调整弹窗关闭逻辑,原结构不变)

<main>
  <div class="button">
    <input onclick="check()" type="checkbox" name="popup__input" id="popup__input" class="popup__check">
  </div>
  <div class="popup" name="popup" id="popup">
    <label class="popup__label">
            <form>
                <div class="form__input">
                    <input type="text" id="fname" name="fname"><br><br>
                </div>
                <div class="close-button__container">
                    <button class="close__button" onclick="closePopup()">&times;</button>
                </div>
                <button class="form__button" type="submit" form="nameform" value="Submit">Відправити</button>
            </form>
        </label>
  </div>
  <div class="overlay">
  </div>
</main>

CSS

*,
*::before,
*::after {
  margin: 0;
  padding: 0;
  border: none;
  box-sizing: border-box;
}

main {
  width: 100vw;
  height: 100vh;
  position: relative;
  overflow: hidden; /* 防止极端情况溢出 */
}

.button {
  width: 200px;
  height: 200px;
  border-radius: 100%;
  background: linear-gradient(#e66465, #9198e5);
  position: absolute;
  /* 使用CSS动画替代JS定时 */
  animation: moveCircle 1s infinite steps(1);
}

/* 定义移动动画,使用CSS变量计算范围 */
@keyframes moveCircle {
  0%, 100% {
    top: calc(var(--max-top) * var(--random-y));
    left: calc(var(--max-left) * var(--random-x));
  }
}

input[type=text],
select {
  width: 100%;
  max-width: 758px;
  height: 60px;
  padding: 12px 20px;
  margin: 40px 0;
  font-size: 60px;
  display: flex;
  border: 1px solid #ccc;
  box-sizing: border-box;
}

form {
  position: absolute;
  display: flex;
  justify-content: center;
}

.form__input {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding-left: 45px;
  padding-right: 45px;
}

.close__button {
  width: auto;
  position: absolute;
  top: 10px;
  right: 10px;
  cursor: pointer;
  border: none;
  outline: none;
  font-size: 2rem;
  font-weight: bold;
  background-color: transparent;
}

.form__button {
  width: 550px;
  height: 55px;
  margin-top: 110px;
  position: absolute;
  font-size: 40px;
}

.overlay {
  display: none;
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  background: rgba(0, 0, 0, 0.5);
}

.popup {
  display: none;
  position: fixed;
  top: 50%;
  left: 50%;
  z-index: 10;
  transform: translate(-50%, -50%);
  border: 1px solid #000;
  border-radius: 10px;
  width: 850px;
  height: 200px;
  background: rgb(208, 201, 201);
  padding: 20px;
}

.popup__check {
  position: absolute;
  width: 100%;
  height: 100%;
  border-radius: 100%;
  cursor: pointer;
  z-index: 3;
  appearance: none;
  -webkit-appearance: none;
  -moz-appearance: none;
}

/* 媒体查询保留原逻辑 */
@media (max-width: 1024.98px) {
  .button {
    width: 80px;
    height: 80px;
  }
}

@media (max-width: 890px) {
  .popup {
    width: 750px;
  }
}

@media (max-width: 768.98px) {
  input[type=text],
  select {
    width: 500px;
    height: 50px;
    padding: 12px 20px;
    margin: 50px 0;
    font-size: 40px;
  }
  .form__button {
    width: 350px;
    height: 45px;
    margin-top: 105px;
    font-size: 30px;
  }
  .popup {
    width: 600px;
  }
}

@media (max-width: 620.98px) {
  .popup {
    width: 480px;
  }
  input[type=text],
  select {
    width: 395px;
    height: 50px;
    padding: 12px 20px;
    margin: 50px 0;
    font-size: 40px;
  }
  .form__input {
    padding-left: 0;
    padding-right: 0;
  }
  form {
    margin-left: 10%;
  }
}

@media (max-width: 507.98px) {
  input[type=text],
  select {
    width: 280px;
    height: 40px;
    padding: 12px 20px;
    margin: 45px 0;
    font-size: 25px;
  }
  .form__button {
    width: 240px;
    height: 35px;
    margin-top: 95px;
    font-size: 20px;
  }
  .popup {
    width: 360px;
  }
}

@media (max-width: 400.98px) {
  input[type=text],
  select {
    width: 290px;
    height: 30px;
    padding: 12px 20px;
    margin: 35px 0;
    font-size: 20px;
  }
  .form__button {
    width: 200px;
    height: 30px;
    margin-top: 80px;
    font-size: 15px;
  }
  .popup {
    width: 360px;
    height: 150px;
  }
}

@media (max-width: 358.98px) {
  input[type=text],
  select {
    width: 230px;
    height: 30px;
    padding: 12px 20px;
    margin: 35px 0;
    font-size: 20px;
  }
  .form__button {
    width: 180px;
    height: 30px;
    margin-top: 80px;
    font-size: 15px;
  }
  .popup {
    width: 290px;
    height: 150px;
  }
}

@media (max-width: 300.98px) {
  input[type=text],
  select {
    width: 200px;
    height: 30px;
    padding: 12px 20px;
    margin: 35px 0;
    font-size: 20px;
  }
  .form__button {
    width: 140px;
    height: 30px;
    margin-top: 80px;
    font-size: 15px;
  }
  .popup {
    width: 255px;
    height: 150px;
  }
}

JavaScript

const elem = document.querySelector('.button');
const popup = document.querySelector('.popup');
const popupInput = document.getElementById('popup__input');

// 初始化并更新CSS变量
function updateMovementBounds() {
  const circleSize = {
    width: elem.offsetWidth,
    height: elem.offsetHeight
  };
  const maxTop = window.innerHeight - circleSize.height;
  const maxLeft = window.innerWidth - circleSize.width;
  
  // 设置CSS变量
  document.documentElement.style.setProperty('--max-top', `${maxTop}px`);
  document.documentElement.style.setProperty('--max-left', `${maxLeft}px`);
}

// 每次动画帧生成新的随机值
function generateRandomPositions() {
  document.documentElement.style.setProperty('--random-x', Math.random());
  document.documentElement.style.setProperty('--random-y', Math.random());
}

// 弹窗控制逻辑(修复原bug)
function check() {
  if (popupInput.checked) {
    popup.style.display = 'block';
  } else {
    popup.style.display = 'none';
  }
}

function closePopup() {
  popupInput.checked = false;
  popup.style.display = 'none';
}

// 初始化
updateMovementBounds();
generateRandomPositions();

// 监听窗口resize更新边界
window.addEventListener('resize', updateMovementBounds);

// 每隔1秒生成新随机值(和动画时长对应)
setInterval(generateRandomPositions, 1000);

关键修改说明

  1. CSS变量与动画结合:

    • --max-top和--max-left存储圆形可移动的最大坐标(窗口尺寸-圆形尺寸),确保圆形不会超出屏幕。
    • --random-x和--random-y存储0-1的随机值,结合calc()计算最终位置。
    • 动画用steps(1)实现跳变效果,替代JS的定时切换位置。
  2. JS逻辑简化:

    • 移除原changePosition直接修改style的逻辑,改为更新CSS变量。
    • 监听窗口resize事件,动态更新边界变量,适配窗口尺寸变化。
    • 修复原check函数的bug:将赋值=改为判断==,并简化弹窗DOM操作(原代码中getElementsByClassName返回集合,else分支未循环导致报错)。
  3. 弹窗样式优化:

    • 调整关闭按钮的位置和样式,使其正常显示可点击。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.10 04:52:35