skip to Main Content

Hi all I have this popup that im inserting into the main div via function. Everything works okay if I call it only once, but what I want to do is to call it on every error so that every error has its own popup. If I do that now every popup is overlaying.

How can I make this popup stackable so they can be under each other if there is more than one?

The code is here

https://jsfiddle.net/s86nw4ct/6/

note I’m not using any frontend framework

let timer1, timer2;
const wrapper = document.getElementById('wrapper')



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 id="text_title" class="text text-1">${title}</span>
                    <span id="text_body" 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 = document.querySelector(".toast");
  (closeIcon = document.querySelector(".close")),
  (progress = document.querySelector(".progress"));
  toast.classList.add("active");
  progress.classList.add("active");

  timer1 = setTimeout(() => {
    toast.classList.remove("active");
  }, 5000);

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

  closeIcon.addEventListener("click", () => {
    toast.classList.remove("active");

    setTimeout(() => {
      progress.classList.remove("active");
    }, 300);

    clearTimeout(timer1);
    clearTimeout(timer2);
  });
}
* {
  margin: 0;
  padding: 0;
  overflow: hidden;
}

#wrapper {
  height: 100vh;
  width: 100vw;
}


/*popup part */

.toast:not(.showing):not(.show) {
  /*bootsratp overide */
  opacity: 1;
}

.toast {
  position: absolute;
  z-index: 10;
  top: 25px;
  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);
}

.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;
}
<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>

2

Answers


  1. You have every .toast element at the exact same absolute position, so you are stacking them all on top of each other. A better way to do this is to have a wrapping/container element for all the toast elements. You set the absolute position of this, and then just add the toast elements inside of that, and they do not need to have any positioning on them, they can just have the standard block level element stacking.

    let timer1, timer2;
    const wrapper = document.getElementById('toast-container')
    
    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 id="text_title" class="text text-1">${title}</span>
                        <span id="text_body" 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 = document.querySelector(".toast");
      (closeIcon = document.querySelector(".close")),
      (progress = document.querySelector(".progress"));
      toast.classList.add("active");
      progress.classList.add("active");
    
      timer1 = setTimeout(() => {
        toast.classList.remove("active");
      }, 5000);
    
      timer2 = setTimeout(() => {
        progress.classList.remove("active");
      }, 5300);
    
      closeIcon.addEventListener("click", () => {
        toast.classList.remove("active");
    
        setTimeout(() => {
          progress.classList.remove("active");
        }, 300);
    
        clearTimeout(timer1);
        clearTimeout(timer2);
      });
    }
    * {
      margin: 0;
      padding: 0;
      overflow: hidden;
    }
    
    #wrapper {
      height: 100vh;
      width: 100vw;
    }
    
    
    /*popup part */
    
    #toast-container {
      position: absolute;
      z-index: 10;
      top: 25px;
      right: 30px;
    }
    
    .toast:not(.showing):not(.show) {
      /*bootsratp overide */
      opacity: 1;
    }
    
    .toast {
      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);
    }
    
    .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;
    }
    <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">
      <div id="toast-container"></div>
      <button type="button" onclick="showPopUp('test','body')">test</button>
    </div>
    Login or Signup to reply.
  2. You could identify each toaster uniquely by using a counter and an Id for each new toaster, and use it to adjust the position of each one. In this code, I am querying all active toasters to know how far down I should put a new one. And when a toaster times out, it selects all the other ones and push them up.

    let timer1, timer2;
    const wrapper = document.getElementById('wrapper')
    let counter = 0;
    let distance = 80;
    
    function updateToastPosition(){
      let allToasts = document.querySelectorAll(".toast");
      allToasts.forEach(element =>{
        let pos = parseInt(element.style.top.split("px")[0]);
        element.style.top = (pos-distance) + "px";
      });
    }
    
    function showPopUp(title, body){
        const popup_data = 
            `<div id="toast_${counter}"class="toast active">
                <div class="toast-content">
                    <i class="fas fa-solid fa-check check"></i>
                    <div class="message">
                        <span id="text_title" class="text text-1">${title}</span>
                        <span id="text_body" 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 toastElement = document.getElementById("toast_"+counter);
        
        let allToasts = document.querySelectorAll(".toast.active");
        toastElement.style.top = 25 + (allToasts.length - 1) * distance + "px";
    
        //const toast = document.querySelector(".toast");
        const closeIcon = document.querySelector(`#toast_${counter} > .close`);
            const progress = document.querySelector(`#toast_${counter} > .progress`);
        toastElement.classList.add("active");
        progress.classList.add("active");
            console.log("123");
        timer1 = setTimeout(() => {
            toastElement.classList.remove("active");
                    updateToastPosition();
        }, 5000);
    
        timer2 = setTimeout(() => {
            progress.classList.remove("active");
        }, 5300);
    
        closeIcon.addEventListener("click", () => {
            toastElement.classList.remove("active");
    
            setTimeout(() => {
                progress.classList.remove("active");
            }, 300);
                    updateToastPosition();
            clearTimeout(timer1);
            clearTimeout(timer2);
        });
        counter++;
    }
    
    

    JsFiddle working example: https://jsfiddle.net/rw4c3a9z/1/

    Edit: Now the close button should work as well, I changed the selector to only get the close button and progress bar that are inside a specific toast.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search