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

如何使Popup Div可堆叠?非前端框架下多弹窗去重叠方案

弹窗堆叠排列实现方案

问题

通过函数向主div插入弹窗,单次调用功能正常,但多次调用后所有弹窗相互重叠,需要实现多个弹窗依次上下堆叠显示,且不依赖任何前端框架。

解决方案

问题根源

当前所有弹窗使用position: absolute且固定top: 25px,导致所有弹窗定位在同一位置,出现重叠。需要动态计算每个新弹窗的垂直偏移量,同时处理弹窗关闭后的位置重排。

具体修改

  1. 动态计算弹窗位置
    在插入新弹窗前,统计当前已激活的弹窗数量,新弹窗的top值为初始top值 + 已激活数量 × (弹窗高度+间距),确保每个新弹窗都在上一个弹窗下方显示。
  2. 精准操作当前弹窗
    原代码使用document.querySelector(".toast")会选中页面中第一个弹窗,改为获取刚插入的弹窗元素(wrapper.lastElementChild),确保操作对象是当前新生成的弹窗。
  3. 弹窗关闭后重排位置
    当某个弹窗关闭并移除后,重新遍历剩余的活跃弹窗,逐个更新它们的top值,保持堆叠顺序。

修改后的完整代码

HTML

<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" integrity="sha512-iecdLmaskl7CVkqkXNQ/ZH/XLlvWZOJyj7Yy7tcenmpD1ypASozpmT/E0iPtmFIB46ZmdtAc9eNBvH0H/ZpiBw==" crossorigin="anonymous" referrerpolicy="no-referrer" />
<div id="wrapper">
  <button type="button" onclick="showPopUp('test','body')">test</button>
</div>

CSS

* {
  margin: 0;
  padding: 0;
  overflow: hidden;
}

#wrapper {
  height: 100vh;
  width: 100vw;
  position: relative; /* 确保弹窗基于wrapper定位 */
}

/*popup part */
.toast:not(.showing):not(.show) {
  opacity: 1;
}

.toast {
  position: absolute;
  z-index: 10;
  right: 30px;
  border-radius: 12px;
  background: #fff;
  padding: 20px 35px 20px 25px;
  box-shadow: 0 6px 20px -5px rgba(0, 0, 0, 0.1);
  overflow: hidden;
  transform: translateX(calc(100% + 30px));
  transition: all 0.5s cubic-bezier(0.68, -0.55, 0.265, 1.35);
  margin-top: 10px; /* 弹窗之间的间距 */
}

.toast.active {
  transform: translateX(0%);
}

.toast .toast-content {
  display: flex;
  align-items: center;
}

.toast-content .check {
  display: flex;
  align-items: center;
  justify-content: center;
  height: 35px;
  min-width: 35px;
  background-color: #4070f4;
  color: #fff;
  font-size: 20px;
  border-radius: 50%;
}

.toast-content .message {
  display: flex;
  flex-direction: column;
  margin: 0 20px;
}

.message .text {
  font-size: 16px;
  font-weight: 400;
  color: #666666;
}

.message .text.text-1 {
  font-weight: 600;
  color: #333;
}

.toast .close {
  position: absolute;
  top: 10px;
  right: 15px;
  padding: 5px;
  cursor: pointer;
  opacity: 0.7;
}

.toast .close:hover {
  opacity: 1;
}

.toast .progress {
  position: absolute;
  bottom: 0;
  left: 0;
  height: 3px;
  width: 100%;
}

.toast .progress:before {
  content: "";
  position: absolute;
  bottom: 0;
  right: 0;
  height: 100%;
  width: 100%;
  background-color: #4070f4;
}

.progress.active:before {
  animation: progress 5s linear forwards;
}

@keyframes progress {
  100% {
    right: 100%;
  }
}

button {
  padding: 12px 20px;
  font-size: 20px;
  outline: none;
  border: none;
  background-color: #4070f4;
  color: #fff;
  border-radius: 6px;
  cursor: pointer;
  transition: 0.3s;
}

button:hover {
  background-color: #0e4bf1;
}

.toast.active~button {
  pointer-events: none;
}

JavaScript

const wrapper = document.getElementById('wrapper');
const POPUP_OFFSET = 90; // 弹窗高度+间距的总和,可根据实际样式调整
const INIT_TOP = 25; // 初始top值

function rearrangeToasts() {
  const activeToasts = document.querySelectorAll('.toast.active');
  activeToasts.forEach((toast, index) => {
    toast.style.top = `${INIT_TOP + index * POPUP_OFFSET}px`;
  });
}

function showPopUp(title, body) {
  const popup_data = `
    <div class="toast active">
      <div class="toast-content">
        <i class="fas fa-solid fa-check check"></i>
        <div class="message">
          <span class="text text-1">${title}</span>
          <span class="text text-2">${body}</span>
        </div>
      </div>
      <i class="fa-solid fa-xmark close"></i>
      <div class="progress active"></div>
    </div>
  `;

  wrapper.insertAdjacentHTML('beforeend', popup_data);
  const toast = wrapper.lastElementChild;
  const closeIcon = toast.querySelector(".close");
  const progress = toast.querySelector(".progress");

  // 计算并设置当前弹窗的top值
  const activeCount = document.querySelectorAll('.toast.active').length;
  toast.style.top = `${INIT_TOP + (activeCount - 1) * POPUP_OFFSET}px`;

  let timer1 = setTimeout(() => {
    toast.classList.remove("active");
    setTimeout(() => {
      toast.remove();
      rearrangeToasts();
    }, 500); // 等待动画结束后移除并重排
  }, 5000);

  let timer2 = setTimeout(() => {
    progress.classList.remove("active");
  }, 5300);

  closeIcon.addEventListener("click", () => {
    toast.classList.remove("active");
    setTimeout(() => {
      progress.classList.remove("active");
      toast.remove();
      rearrangeToasts();
    }, 300);
    clearTimeout(timer1);
    clearTimeout(timer2);
  });
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.24 00:02:06